Created
August 18, 2022 02:53
-
-
Save mostlylikeable/c0b7d3a7b9147a3e6a004c3cdf81c4d7 to your computer and use it in GitHub Desktop.
DynamoDb TypeScript Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { DynamoDB } from '@aws-sdk/client-dynamodb'; | |
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'; | |
const tableName = 'discography'; | |
const artistAlbumsIndex = 'artist-albums'; | |
const table = { | |
TableName: tableName, | |
BillingMode: 'PAY_PER_REQUEST', | |
AttributeDefinitions: [ | |
{ AttributeName: 'pk', AttributeType: 'S' }, | |
{ AttributeName: 'sk', AttributeType: 'S' }, | |
{ AttributeName: 'artist_key', AttributeType: 'S' }, // case-insensitive artist for lookups | |
{ AttributeName: 'album', AttributeType: 'S' }, | |
], | |
KeySchema: [ | |
{ AttributeName: 'pk', KeyType: 'HASH' }, | |
{ AttributeName: 'sk', KeyType: 'RANGE' }, | |
], | |
GlobalSecondaryIndexes: [ // so we can look up albums by artist | |
{ | |
IndexName: artistAlbumsIndex, | |
KeySchema: [ | |
{ AttributeName: 'artist_key', KeyType: 'HASH' }, | |
{ AttributeName: 'album', KeyType: 'RANGE' }, | |
], | |
Projection: { | |
ProjectionType: 'ALL', | |
}, | |
}, | |
], | |
}; | |
const createClient = (): DynamoDB => { | |
return new DynamoDB({ | |
region: 'us-east-1', | |
endpoint: 'http://localhost:4566', | |
credentials: { | |
accessKeyId: 'DUMMY', | |
secretAccessKey: 'DUMMY', | |
}, | |
}); | |
}; | |
export const runExample = async () => { | |
const client = createClient(); | |
try { | |
await client.createTable(table); | |
try { | |
const dynamo = await DynamoDBDocument.from(client); | |
await seed(dynamo); | |
await run(dynamo); | |
} finally { | |
await client.deleteTable({ TableName: tableName }); | |
} | |
} finally { | |
client.destroy(); | |
} | |
}; | |
const seed = async (dynamo: DynamoDBDocument) => { | |
const promises = albums.map(async (it) => await saveAlbum(dynamo, it)); | |
await Promise.all(promises); | |
}; | |
const run = async (dynamo: DynamoDBDocument) => { | |
// get all albums by artist | |
const discography = await albumsByArtist(dynamo, 'The Weekend'); | |
console.log('Discography', discography); | |
const album = await albumDetail(dynamo, 'The Weekend', 'Starboy'); | |
console.log('The Weekend - Starboy', album); | |
}; | |
const albumsByArtist = async (dynamo: DynamoDBDocument, artist: string): Promise<Album[]> => { | |
const items = await dynamo.query({ | |
TableName: tableName, | |
IndexName: artistAlbumsIndex, | |
KeyConditionExpression: 'artist_key = :artist', | |
ExpressionAttributeValues: { | |
':artist': artist.toLowerCase(), | |
}, | |
ScanIndexForward: false, | |
}); | |
return items?.Items?.map(i => new Album(i)) || []; | |
}; | |
const albumDetail = async (dynamo: DynamoDBDocument, artist: string, album: string): Promise<AlbumDetail> => { | |
const items = await dynamo.query({ | |
TableName: tableName, | |
KeyConditionExpression: 'pk = :pk', | |
ExpressionAttributeValues: { | |
':pk': `ALBUM#${artist.toLowerCase()}#${album.toLowerCase()}`, | |
}, | |
ScanIndexForward: false, | |
}); | |
const found = { songs: [] } as { album?: Album, songs: Song[] }; | |
items?.Items?.forEach(i => { | |
if (i.sk.startsWith('ALBUM#')) { | |
found.album = new Album(i); | |
} else { | |
found.songs.push(new Song(i)); | |
} | |
}); | |
required(found.album); | |
return { | |
...found.album!, | |
tracks: found.songs.sort((s1, s2) => s1.track - s2.track), | |
}; | |
}; | |
const saveAlbum = async (dynamo: DynamoDBDocument, album: AlbumDetail) => { | |
await dynamo.transactWrite({ | |
TransactItems: [ | |
{ | |
Put: { | |
TableName: tableName, | |
Item: { | |
pk: `ALBUM#${album.artist.toLowerCase()}#${album.album.toLowerCase()}`, | |
sk: `ALBUM#${album.artist.toLowerCase()}#${album.album.toLowerCase()}`, | |
artist: album.artist, | |
artist_key: album.artist.toLowerCase(), | |
album: album.album, | |
year: album.year, | |
label: album.label, | |
type: album.type, | |
} as AlbumItem, | |
}, | |
}, | |
...album.tracks.map((song) => ({ | |
Put: { | |
TableName: tableName, | |
Item: { | |
pk: `ALBUM#${album.artist.toLowerCase()}#${album.album.toLowerCase()}`, | |
sk: `SONG#${song.track}`, | |
track: song.track, | |
title: song.title, | |
} as SongItem, | |
}, | |
})), | |
], | |
}); | |
}; | |
class Album { | |
artist: string; | |
album: string; | |
year: number; | |
label: string; | |
type: 'LP' | 'EP' | 'S'; | |
constructor(partial: Partial<Album>) { | |
this.artist = required(partial.artist); | |
this.album = required(partial.album); | |
this.year = required(partial.year); | |
this.label = required(partial.label); | |
this.type = required(partial.type); | |
} | |
} | |
class Song { | |
track: number; | |
title: string; | |
constructor(partial: Partial<Song>) { | |
this.track = required(partial.track); | |
this.title = required(partial.title); | |
} | |
} | |
type AlbumDetail = Album & { | |
tracks: Song[]; | |
}; | |
type DynamoItem = { pk: string; sk: string }; | |
type AlbumItem = Omit<Album, 'songs'> & DynamoItem; | |
type SongItem = Song & DynamoItem; | |
const required = <T = any>(value: T | null | undefined): T => { | |
if (value === null || value === undefined) { | |
throw new Error('Value is required'); | |
} | |
return value as T; | |
}; | |
const albums: AlbumDetail[] = [ | |
{ | |
artist: 'The Weekend', | |
album: 'Starboy', | |
year: 2016, | |
label: 'Republic', | |
type: 'LP', | |
tracks: [ | |
{ track: 1, title: 'Starboy' }, | |
{ track: 2, title: 'Party Monster' }, | |
{ track: 3, title: 'False Alarm' }, | |
{ track: 4, title: 'Reminder' }, | |
{ track: 5, title: "Rockin'" }, | |
{ track: 6, title: 'Secrets' }, | |
{ track: 7, title: 'True Colors' }, | |
{ track: 8, title: 'Stargirl (Interlude)' }, | |
{ track: 9, title: 'Sidewalks' }, | |
{ track: 10, title: 'Six Feet Under' }, | |
{ track: 11, title: 'Love to Lay' }, | |
{ track: 12, title: 'A Lonely Night' }, | |
{ track: 13, title: 'Attention' }, | |
{ track: 14, title: 'Orderly Life' }, | |
{ track: 15, title: 'Nothing Without You' }, | |
{ track: 16, title: 'All I Know' }, | |
{ track: 17, title: 'Die for You' }, | |
{ track: 18, title: 'I Feel It Coming' }, | |
], | |
}, | |
{ | |
artist: 'The Weekend', | |
album: 'The Hills', | |
year: 2015, | |
label: 'Universal', | |
type: 'S', | |
tracks: [ | |
{ track: 1, title: 'The Hills' }, | |
], | |
}, | |
]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment