From 96afe58a876d6b89802040ed7122af166b038768 Mon Sep 17 00:00:00 2001 From: cjoelrun Date: Fri, 5 Jun 2020 12:16:33 -0500 Subject: [PATCH 1/2] array key --- src/main/codegen/generateGSIs.ts | 1 + src/main/codegen/generateModels.ts | 6 ++- src/main/codegen/generateTables.ts | 14 ++++-- src/main/codegen/types.ts | 4 +- src/main/codegen/util.ts | 24 +++++++++ src/main/dynamo/Beyonce.ts | 2 +- src/main/dynamo/GSI.ts | 2 +- src/main/dynamo/Model.ts | 72 +++++++++++++++++++++------ src/main/dynamo/QueryBuilder.ts | 2 +- src/main/dynamo/keys.ts | 16 +++--- src/test/codegen/generateCode.test.ts | 42 ++++++++++++++++ src/test/dynamo/Beyonce.test.ts | 21 +++++++- 12 files changed, 171 insertions(+), 35 deletions(-) diff --git a/src/main/codegen/generateGSIs.ts b/src/main/codegen/generateGSIs.ts index 41fbd0a..00a2d47 100644 --- a/src/main/codegen/generateGSIs.ts +++ b/src/main/codegen/generateGSIs.ts @@ -1,4 +1,5 @@ import { Fields, Table } from "./types" +import {buildKey, replaceKeyDollar} from "./util" export function generateGSIs(tables: Table[]): string { return tables.map(generateGSIsForTable).join("\n\n") diff --git a/src/main/codegen/generateModels.ts b/src/main/codegen/generateModels.ts index 1213315..742b194 100644 --- a/src/main/codegen/generateModels.ts +++ b/src/main/codegen/generateModels.ts @@ -1,4 +1,5 @@ import { Model } from "./types" +import { buildKey } from "./util" export function generateModels(models: Model[]) { return models.map(generateModel) @@ -10,7 +11,8 @@ function generateModel(model: Model): string { return `export const ${model.name}Model = ${model.tableName}Table .model<${model.name}>(ModelType.${model.name}) - .partitionKey("${pkPrefix}", "${pk.replace("$", "")}") - .sortKey("${skPrefix}", "${sk.replace("$", "")}") + .partitionKey("${pkPrefix}", ${buildKey(pk)}) + .sortKey("${skPrefix}", ${buildKey(sk)}) ` } + diff --git a/src/main/codegen/generateTables.ts b/src/main/codegen/generateTables.ts index 457b4de..4e571ec 100644 --- a/src/main/codegen/generateTables.ts +++ b/src/main/codegen/generateTables.ts @@ -1,4 +1,5 @@ import { Table } from "./types" +import { buildKeyArray } from "./util" export function generateTables(tables: Table[]): string { const tableCode = tables.map((table) => { @@ -24,13 +25,18 @@ function generateEncryptionBlacklist(table: Table): string { .forEach((model) => { const [, pk] = model.keys.partitionKey const [, sk] = model.keys.sortKey - encryptionBlacklistSet.add(pk.replace("$", "")) - encryptionBlacklistSet.add(sk.replace("$", "")) + + const pks = buildKeyArray(model.keys.partitionKey[1]) + const sks = buildKeyArray(model.keys.sortKey[1]) + pks.forEach((key: string) => encryptionBlacklistSet.add(key)) + sks.forEach((key: string) => encryptionBlacklistSet.add(key)) }) table.gsis.forEach(({ name, partitionKey, sortKey }) => { - encryptionBlacklistSet.add(partitionKey.replace("$", "")) - encryptionBlacklistSet.add(sortKey.replace("$", "")) + const pk = buildKeyArray(partitionKey) + const sk = buildKeyArray(sortKey) + pk.forEach((key: string) => encryptionBlacklistSet.add(key)) + sk.forEach((key: string) => encryptionBlacklistSet.add(key)) }) return JSON.stringify(Array.from(encryptionBlacklistSet)) diff --git a/src/main/codegen/types.ts b/src/main/codegen/types.ts index 56309f0..72de30a 100644 --- a/src/main/codegen/types.ts +++ b/src/main/codegen/types.ts @@ -21,8 +21,8 @@ export type Keys = { partitionKey: [string, string]; sortKey: [string, string] } export type Fields = { [fieldName: string]: string } export type GSIDefinition = { - partitionKey: string - sortKey: string + partitionKey: string | string[] + sortKey: string | string[] } export type Table = { diff --git a/src/main/codegen/util.ts b/src/main/codegen/util.ts index a2258cf..3c3cba5 100644 --- a/src/main/codegen/util.ts +++ b/src/main/codegen/util.ts @@ -11,3 +11,27 @@ export function groupBy( return Object.entries(groupedItems) } + +export function replaceKeyDollar(key: string | string[]) { + if (Array.isArray(key)) { + return key.map((k) => k.replace("$", "")) + } + return key.replace("$", "") +} + +export function buildKey(key: string | string[]) { + const cleanKey = replaceKeyDollar(key); + if (Array.isArray(cleanKey)) { + const keys = cleanKey.map((k) => `"${k}"`).join(', ') + return `[${keys}]` + } + return `"${cleanKey.replace("$", "")}"` +} + +export function buildKeyArray(key: string | string[]) { + const keyOrKeys = replaceKeyDollar(key) + if (Array.isArray(keyOrKeys)) { + return keyOrKeys + } + return [keyOrKeys] +} \ No newline at end of file diff --git a/src/main/dynamo/Beyonce.ts b/src/main/dynamo/Beyonce.ts index a39e975..4e697d0 100644 --- a/src/main/dynamo/Beyonce.ts +++ b/src/main/dynamo/Beyonce.ts @@ -120,7 +120,7 @@ export class Beyonce { }) .promise() - if (unprocessedKeys !== undefined) { + if (unprocessedKeys?.entries !== undefined) { console.error("Some keys didn't process", unprocessedKeys) } diff --git a/src/main/dynamo/GSI.ts b/src/main/dynamo/GSI.ts index 954d70f..092c97c 100644 --- a/src/main/dynamo/GSI.ts +++ b/src/main/dynamo/GSI.ts @@ -20,7 +20,7 @@ export class GSI> { this.modelTags = models.map((_) => _.modelTag) } - key(partitionKey: string): PartitionKey> { + key(partitionKey: string | string[]): PartitionKey> { return new PartitionKey(this.partitionKeyName, partitionKey, this.modelTags) } } diff --git a/src/main/dynamo/Model.ts b/src/main/dynamo/Model.ts index e64675e..8508420 100644 --- a/src/main/dynamo/Model.ts +++ b/src/main/dynamo/Model.ts @@ -1,20 +1,21 @@ import { PartitionAndSortKey, PartitionKeyAndSortKeyPrefix } from "./keys" import { Table } from "./Table" import { TaggedModel } from "./types" +import { key } from "aws-sdk/clients/signer" export class Model< T extends TaggedModel, U extends keyof T, V extends keyof T -> { + > { constructor( private table: Table, private partitionKeyPrefix: string, - private partitionKeyField: U, + private partitionKeyField: U | U[], private sortKeyPrefix: string, - private sortKeyField: V, + private sortKeyField: V | V[], readonly modelTag: string - ) {} + ) { } key( params: { [X in U]: string } & { [Y in V]: string } @@ -27,10 +28,14 @@ export class Model< } = this return new PartitionAndSortKey( this.table.partitionKeyName, - this.buildKey(partitionKeyPrefix, params[partitionKeyField]), + this.buildKey( + partitionKeyPrefix, + this._buildPartitionKey(params, partitionKeyField)), this.table.sortKeyName, - this.buildKey(sortKeyPrefix, params[sortKeyField]), + this.buildKey( + sortKeyPrefix, + this._buildSortKey(params, sortKeyField)), this.modelTag ) } @@ -39,7 +44,9 @@ export class Model< const { partitionKeyPrefix, partitionKeyField } = this return new PartitionKeyAndSortKeyPrefix( this.table.partitionKeyName, - this.buildKey(partitionKeyPrefix, params[partitionKeyField]), + this.buildKey( + partitionKeyPrefix, + this._buildPartitionKey(params, partitionKeyField)), this.table.sortKeyName, this.sortKeyPrefix, this.modelTag @@ -59,9 +66,10 @@ export class Model< const pk = this.buildKey( partitionKeyPrefix, - fieldsWithTag[partitionKeyField] - ) - const sk = this.buildKey(sortKeyPrefix, fieldsWithTag[sortKeyField]) + this._buildPartitionKey(fieldsWithTag, partitionKeyField)) + const sk = this.buildKey( + sortKeyPrefix, + this._buildSortKey(fieldsWithTag, sortKeyField)) return { ...fieldsWithTag, @@ -70,13 +78,49 @@ export class Model< } } - private buildKey(prefix: string, key: string): string { + /* TODO: Had issue mapping unioned fields of T & V */ + private _buildPartitionKey(model: { [X in U]: string }, fields: U | U[]): string | string[] { + if (Array.isArray(fields)) { + const pkey: string[] = []; + for (const field of fields) { + const value = model[field] + if (value) { + pkey.push(value) + } else { + break + } + }; + return pkey + } + return model[fields as U] + } + + private _buildSortKey(model: { [Y in V]: string }, fields: V | V[]): string | string[] { + if (Array.isArray(fields)) { + const skey: string[] = []; + for (const field of fields) { + const value = model[field] + if (value) { + skey.push(value) + } else { + break + } + }; + return skey + } + return model[fields as V] + } + + private buildKey(prefix: string, key: string | string[]): string { + if (Array.isArray(key)) { + return `${prefix}-${key.join('-')}` + } return `${prefix}-${key}` } } export class PartitionKeyBuilder { - constructor(private table: Table, private modelTag: string) {} + constructor(private table: Table, private modelTag: string) { } partitionKey( prefix: string, partitionKeyField: U @@ -94,9 +138,9 @@ export class SortKeyBuilder { constructor( private table: Table, private partitionKeyPrefix: string, - private partitionKeyField: U, + private partitionKeyField: U | U[], private modelTag: string - ) {} + ) { } sortKey(prefix: string, sortKeyField: V): Model { return new Model( diff --git a/src/main/dynamo/QueryBuilder.ts b/src/main/dynamo/QueryBuilder.ts index aa61318..bd69c9b 100644 --- a/src/main/dynamo/QueryBuilder.ts +++ b/src/main/dynamo/QueryBuilder.ts @@ -49,7 +49,7 @@ type RawQueryResults = { /** Builds and executes parameters for a DynamoDB Query operation */ export class QueryBuilder extends QueryExpressionBuilder< T -> { + > { private scanIndexForward: boolean = true private modelTags: string[] = getModelTags(this.config) diff --git a/src/main/dynamo/keys.ts b/src/main/dynamo/keys.ts index 3c9dc3b..ee32236 100644 --- a/src/main/dynamo/keys.ts +++ b/src/main/dynamo/keys.ts @@ -3,31 +3,31 @@ import { TaggedModel } from "./types" export class PartitionKey { constructor( readonly partitionKeyName: string, - readonly partitionKey: string, + readonly partitionKey: string | string[], readonly modelTags: T["model"][] - ) {} + ) { } } export class PartitionKeyAndSortKeyPrefix { constructor( readonly partitionKeyName: string, - readonly partitionKey: string, + readonly partitionKey: string | string[], readonly sortKeyName: string, readonly sortKeyPrefix: string, readonly modelTag: T["model"] - ) {} + ) { } } export class PartitionAndSortKey { constructor( readonly partitionKeyName: string, - readonly partitionKey: string, + readonly partitionKey: string | string[], readonly sortKeyName: string, - readonly sortKey: string, + readonly sortKey: string | string[], readonly modelTag: T["model"] - ) {} + ) { } // Required to avoid TypeScript's structural typing from collapsing // PartitionAndSortKey and PartitionAndSortKey into the same object. - private dummy(item: T) {} + private dummy(item: T) { } } diff --git a/src/test/codegen/generateCode.test.ts b/src/test/codegen/generateCode.test.ts index 8cbfd69..231039b 100644 --- a/src/test/codegen/generateCode.test.ts +++ b/src/test/codegen/generateCode.test.ts @@ -322,3 +322,45 @@ Tables: expect(lines).toContainEqual(`name: BestNameEvah`) }) + +it("should generate a complex key model", () => { + const result = generateCode(` +Tables: + Library: + Partitions: + Authors: + Author: + partitionKey: [Author, $id] + sortKey: [Author, [$id, $name]] + id: string + name: string +`) + + expect(result).toEqual(`import { Table } from "@ginger.io/beyonce" + +export const LibraryTable = new Table({ + name: "Library", + partitionKeyName: "pk", + sortKeyName: "sk", + encryptionBlacklist: ["id", "name"] +}) + +export enum ModelType { + Author = "Author" +} + +export interface Author { + model: ModelType.Author + id: string + name: string +} + +export const AuthorModel = LibraryTable.model(ModelType.Author) + .partitionKey("Author", "id") + .sortKey("Author", ["id", "name"]) + +export type Model = Author + +export const AuthorsPartition = LibraryTable.partition([AuthorModel]) +`) +}) \ No newline at end of file diff --git a/src/test/dynamo/Beyonce.test.ts b/src/test/dynamo/Beyonce.test.ts index 7502acf..95699cd 100644 --- a/src/test/dynamo/Beyonce.test.ts +++ b/src/test/dynamo/Beyonce.test.ts @@ -88,7 +88,7 @@ describe("Beyonce", () => { })) const db = new Beyonce(table, new DynamoDB({ region: "us-west-2" })) - ;(db as any).client.get = mockGet + ; (db as any).client.get = mockGet await db.get(MusicianModel.key({ id: musician.id }), { consistentRead: true, @@ -156,7 +156,7 @@ describe("Beyonce", () => { })) const db = new Beyonce(table, new DynamoDB({ region: "us-west-2" })) - ;(db as any).client.batchGet = mockGet + ; (db as any).client.batchGet = mockGet await db.batchGet({ keys: [MusicianModel.key({ id: musician.id })], @@ -500,6 +500,23 @@ async function testBatchWriteWithTransaction(jayZ?: JayZ) { }) } +async function blah(jayZ?: JayZ) { + const db = await setup(jayZ) + const [musician, song1, song2] = aMusicianWithTwoSongs() + await db.batchPutWithTransaction({ items: [musician, song1, song2] }) + + const results = await db + .query(MusicianPartition.key({ id: musician.id, })) + .exec() + + sortById(results.song) + expect(results).toEqual({ + musician: [musician], + song: [song1, song2], + }) +} + + function sortById(items: T[]): T[] { return items.sort((a, b) => { if (a.id === b.id) { From 90c450cecdf63a562ea339087a8b350b07303a18 Mon Sep 17 00:00:00 2001 From: cjoelrun Date: Mon, 8 Jun 2020 18:37:47 -0500 Subject: [PATCH 2/2] tests --- src/main/codegen/types.ts | 4 +- src/main/dynamo/Model.ts | 28 +++++++++++-- src/main/dynamo/types.ts | 2 + src/test/codegen/generateCode.test.ts | 26 ++++++------ src/test/dynamo/Beyonce.test.ts | 57 ++++++++++++++++++--------- src/test/dynamo/models.ts | 33 +++++++++++++++- 6 files changed, 115 insertions(+), 35 deletions(-) diff --git a/src/main/codegen/types.ts b/src/main/codegen/types.ts index 72de30a..56309f0 100644 --- a/src/main/codegen/types.ts +++ b/src/main/codegen/types.ts @@ -21,8 +21,8 @@ export type Keys = { partitionKey: [string, string]; sortKey: [string, string] } export type Fields = { [fieldName: string]: string } export type GSIDefinition = { - partitionKey: string | string[] - sortKey: string | string[] + partitionKey: string + sortKey: string } export type Table = { diff --git a/src/main/dynamo/Model.ts b/src/main/dynamo/Model.ts index 8508420..079d388 100644 --- a/src/main/dynamo/Model.ts +++ b/src/main/dynamo/Model.ts @@ -1,6 +1,6 @@ import { PartitionAndSortKey, PartitionKeyAndSortKeyPrefix } from "./keys" import { Table } from "./Table" -import { TaggedModel } from "./types" +import { TaggedModel, AtLeastOne } from "./types" import { key } from "aws-sdk/clients/signer" export class Model< @@ -40,6 +40,28 @@ export class Model< ) } + partialKey( + params: AtLeastOne<{ [X in U]: string }> & AtLeastOne<{ [Y in V]: string }> + ): PartitionKeyAndSortKeyPrefix { + const { + partitionKeyPrefix, + sortKeyPrefix, + partitionKeyField, + sortKeyField, + } = this + return new PartitionKeyAndSortKeyPrefix( + this.table.partitionKeyName, + this.buildKey( + partitionKeyPrefix, + this._buildPartitionKey(params, partitionKeyField)), + this.table.sortKeyName, + this.buildKey( + sortKeyPrefix, + this._buildSortKey(params, sortKeyField)), + this.modelTag + ) + } + partitionKey(params: { [X in U]: string }): PartitionKeyAndSortKeyPrefix { const { partitionKeyPrefix, partitionKeyField } = this return new PartitionKeyAndSortKeyPrefix( @@ -79,7 +101,7 @@ export class Model< } /* TODO: Had issue mapping unioned fields of T & V */ - private _buildPartitionKey(model: { [X in U]: string }, fields: U | U[]): string | string[] { + private _buildPartitionKey(model: AtLeastOne<{ [X in U]: string }>, fields: U | U[]): string | string[] { if (Array.isArray(fields)) { const pkey: string[] = []; for (const field of fields) { @@ -142,7 +164,7 @@ export class SortKeyBuilder { private modelTag: string ) { } - sortKey(prefix: string, sortKeyField: V): Model { + sortKey(prefix: string, sortKeyField: V | V[]): Model { return new Model( this.table, this.partitionKeyPrefix, diff --git a/src/main/dynamo/types.ts b/src/main/dynamo/types.ts index 062d0bc..781b5dc 100644 --- a/src/main/dynamo/types.ts +++ b/src/main/dynamo/types.ts @@ -11,3 +11,5 @@ export type ExtractKeyType = T extends PartitionAndSortKey export type GroupedModels = { [K in T["model"]]: T extends { model: K } ? T[] : never } + +export type AtLeastOne }> = Partial & U[keyof U] diff --git a/src/test/codegen/generateCode.test.ts b/src/test/codegen/generateCode.test.ts index 231039b..2573d61 100644 --- a/src/test/codegen/generateCode.test.ts +++ b/src/test/codegen/generateCode.test.ts @@ -326,10 +326,10 @@ Tables: it("should generate a complex key model", () => { const result = generateCode(` Tables: - Library: + ComplexLibrary: Partitions: - Authors: - Author: + ComplexAuthors: + ComplexAuthor: partitionKey: [Author, $id] sortKey: [Author, [$id, $name]] id: string @@ -338,29 +338,33 @@ Tables: expect(result).toEqual(`import { Table } from "@ginger.io/beyonce" -export const LibraryTable = new Table({ - name: "Library", +export const ComplexLibraryTable = new Table({ + name: "ComplexLibrary", partitionKeyName: "pk", sortKeyName: "sk", encryptionBlacklist: ["id", "name"] }) export enum ModelType { - Author = "Author" + ComplexAuthor = "ComplexAuthor" } -export interface Author { - model: ModelType.Author +export interface ComplexAuthor { + model: ModelType.ComplexAuthor id: string name: string } -export const AuthorModel = LibraryTable.model(ModelType.Author) +export const ComplexAuthorModel = ComplexLibraryTable.model( + ModelType.ComplexAuthor +) .partitionKey("Author", "id") .sortKey("Author", ["id", "name"]) -export type Model = Author +export type Model = ComplexAuthor -export const AuthorsPartition = LibraryTable.partition([AuthorModel]) +export const ComplexAuthorsPartition = ComplexLibraryTable.partition([ + ComplexAuthorModel +]) `) }) \ No newline at end of file diff --git a/src/test/dynamo/Beyonce.test.ts b/src/test/dynamo/Beyonce.test.ts index 95699cd..1a2debf 100644 --- a/src/test/dynamo/Beyonce.test.ts +++ b/src/test/dynamo/Beyonce.test.ts @@ -1,3 +1,4 @@ + import { FixedDataKeyProvider, JayZ } from "@ginger.io/jay-z" import crypto from "crypto" import { @@ -9,6 +10,9 @@ import { MusicianPartition, Song, SongModel, + aAlbum, + AlbumModel, + someAlbums, } from "./models" import { setup } from "./util" import { Beyonce } from "../../main/dynamo/Beyonce" @@ -207,6 +211,14 @@ describe("Beyonce", () => { await testQueryWithReverseAndLimit() }) + it("should put and retrieve an item using pk + complex sk", async () => { + await testPutAndRetreiveItemWithComplexKey() + }) + + it("should query using using pk + partial complex sk", async () => { + await testQueryPartialComplexKey() + }) + // With JayZ encryption it("should put and retrieve an item using pk + sk with jayZ", async () => { const jayZ = await createJayZ() @@ -312,13 +324,13 @@ async function testPutAndRetrieveMultipleItems(jayZ?: JayZ) { const result = await db .query(MusicianPartition.key({ id: musician.id })) .exec() - expect(result).toEqual({ musician: [musician], song: [song1, song2] }) + expect(result).toEqual({ musician: [musician], song: [song1, song2], album: [] }) } async function testEmptyQuery(jayZ?: JayZ) { const db = await setup(jayZ) const result = await db.query(MusicianPartition.key({ id: "foo-1" })).exec() - expect(result).toEqual({ musician: [], song: [] }) + expect(result).toEqual({ musician: [], song: [], album: [] }) } async function testQueryWithPaginatedResults(jayZ?: JayZ) { @@ -353,7 +365,7 @@ async function testQueryWithFilter(jayZ?: JayZ) { .where("model", "=", ModelType.Song) .exec() - expect(result).toEqual({ musician: [], song: [song1, song2] }) + expect(result).toEqual({ musician: [], song: [song1, song2], album: [] }) } async function testQueryForSingleTypeOfModel(jayZ?: JayZ) { @@ -382,7 +394,7 @@ async function testQueryWithCombinedAttributeFilters(jayZ?: JayZ) { .orAttributeNotExists("mp3") .exec() - expect(result).toEqual({ musician: [musician], song: [song1, song2] }) + expect(result).toEqual({ musician: [musician], song: [song1, song2], album: [] }) } async function testQueryWithLimit(jayZ?: JayZ) { @@ -395,7 +407,7 @@ async function testQueryWithLimit(jayZ?: JayZ) { .iterator({ pageSize: 1 }) .next() - expect(response1.items).toEqual({ musician: [musician], song: [] }) + expect(response1.items).toEqual({ musician: [musician], song: [], album: [] }) const { value: response2 } = await db .query(MusicianPartition.key({ id: musician.id })) @@ -405,7 +417,7 @@ async function testQueryWithLimit(jayZ?: JayZ) { }) .next() - expect(response2.items).toEqual({ musician: [], song: [song1] }) + expect(response2.items).toEqual({ musician: [], song: [song1], album: [] }) } async function testQueryWithReverseAndLimit(jayZ?: JayZ) { @@ -419,7 +431,7 @@ async function testQueryWithReverseAndLimit(jayZ?: JayZ) { .iterator({ pageSize: 1 }) .next() - expect(value.items).toEqual({ musician: [], song: [song2] }) + expect(value.items).toEqual({ musician: [], song: [song2], album: [] }) } async function testBatchGet(jayZ?: JayZ) { @@ -497,25 +509,34 @@ async function testBatchWriteWithTransaction(jayZ?: JayZ) { expect(results).toEqual({ musician: [musician], song: [song1, song2], + album: [], }) } -async function blah(jayZ?: JayZ) { +async function testPutAndRetreiveItemWithComplexKey(jayZ?: JayZ) { const db = await setup(jayZ) - const [musician, song1, song2] = aMusicianWithTwoSongs() - await db.batchPutWithTransaction({ items: [musician, song1, song2] }) + const album = aAlbum() + await db.put(album) - const results = await db - .query(MusicianPartition.key({ id: musician.id, })) - .exec() + const result = await db.get(AlbumModel.key({ + musicianId: album.musicianId, + id: album.id, + year: album.year + })) - sortById(results.song) - expect(results).toEqual({ - musician: [musician], - song: [song1, song2], - }) + expect(result).toEqual(album) } +async function testQueryPartialComplexKey(jayZ?: JayZ) { + const db = await setup(jayZ) + const albums = someAlbums() + await db.batchPutWithTransaction({ items: albums }) + + const result = await db + .query(AlbumModel.partialKey({ musicianId: '1', year: '1994' })) + .exec() + expect(result).toEqual({ album: [albums[0], albums[1]] }) +} function sortById(items: T[]): T[] { return items.sort((a, b) => { diff --git a/src/test/dynamo/models.ts b/src/test/dynamo/models.ts index eb45924..34ebbdd 100644 --- a/src/test/dynamo/models.ts +++ b/src/test/dynamo/models.ts @@ -9,6 +9,7 @@ export const table = new Table({ export enum ModelType { Musician = "musician", Song = "song", + Album = "album" } export interface Musician { @@ -28,6 +29,14 @@ export interface Song { mp3: Buffer } +export interface Album { + model: ModelType.Album + id: string + name: string + musicianId: string, + year: string, +} + export const MusicianModel = table .model(ModelType.Musician) .partitionKey(ModelType.Musician, "id") @@ -38,7 +47,12 @@ export const SongModel = table .partitionKey(ModelType.Musician, "musicianId") .sortKey(ModelType.Song, "id") -export const MusicianPartition = table.partition([MusicianModel, SongModel]) +export const AlbumModel = table + .model(ModelType.Album) + .partitionKey(ModelType.Musician, "musicianId") + .sortKey(ModelType.Album, ["year", "id"]) + +export const MusicianPartition = table.partition([MusicianModel, SongModel, AlbumModel]) export const byModelAndIdGSI = table .gsi("byModelAndId") @@ -77,3 +91,20 @@ export function aMusicianWithTwoSongs(): [Musician, Song, Song] { return [musician, song1, song2] } + +export function aAlbum(album?: Partial): Album { + const defaultAlbum = { + id: "1", + name: "Dummy", + musicianId: "1", + year: "1994" + } + return AlbumModel.create({ ...defaultAlbum, ...album }) +} + +export function someAlbums(): Album[] { + return [ + aAlbum(), + aAlbum({ id: "2", name: "Dummy Live" }), + aAlbum({ id: "3", name: "Third", year: "2008" })] +}