import { expect, should } from 'chai' import * as Dyngoose from '../..' interface ITestMap { first: string middle: string last: string level: number nick?: string } interface ITestContactMap { name: { first: string last: string } address?: { line1: string city: string state: string } dob?: Date } @Dyngoose.$Table({ name: `MapTest-${Math.random()}`, }) export class MapTestTable extends Dyngoose.Table { @Dyngoose.$PrimaryKey('id') public static readonly primaryKey: Dyngoose.Query.PrimaryKey<MapTestTable, number, void> @Dyngoose.$DocumentClient() public static readonly documentClient: Dyngoose.DocumentClient<MapTestTable> @Dyngoose.Attribute.Number() id: number @Dyngoose.Attribute.Map({ attributes: { first: Dyngoose.Attribute.String(), middle: Dyngoose.Attribute.String(), last: Dyngoose.Attribute.String(), level: Dyngoose.Attribute.Number(), nick: Dyngoose.Attribute.String(), }, }) public person: ITestMap @Dyngoose.Attribute.Map({ attributes: { name: Dyngoose.Attribute.Map({ attributes: { first: Dyngoose.Attribute.String({ lowercase: true }), last: Dyngoose.Attribute.String({ uppercase: true }), }, }), address: Dyngoose.Attribute.Map({ attributes: { line1: Dyngoose.Attribute.String(), city: Dyngoose.Attribute.String(), state: Dyngoose.Attribute.String(), }, }), dob: Dyngoose.Attribute.Date({ dateOnly: true }), }, }) public contact: ITestContactMap } describe('AttributeType/Map', () => { before(async () => { await MapTestTable.createTable() }) after(async () => { await MapTestTable.deleteTable() }) it('should store the object as a map', async () => { const record = MapTestTable.new({ id: 1, person: { first: 'John', middle: 'Jacobs', last: 'Smith', level: 1, // nick is left empty to ensure optional properties work }, }) await record.save() const loaded = await MapTestTable.primaryKey.get(1) expect(loaded?.getAttributeDynamoValue('person')).to.deep.eq({ M: { first: { S: 'John' }, middle: { S: 'Jacobs' }, last: { S: 'Smith' }, level: { N: '1' }, }, }) expect(loaded?.person.first).to.eq('John') expect(loaded?.getAttributeDynamoValue('person')).to.deep.eq({ M: { first: { S: 'John' }, middle: { S: 'Jacobs' }, last: { S: 'Smith' }, level: { N: '1' }, }, }) expect(loaded?.toJSON()).to.deep.eq({ id: 1, person: { first: 'John', middle: 'Jacobs', last: 'Smith', level: 1, }, }) }) it('should allow you to query using child attributes', async () => { const record = MapTestTable.new({ id: 2, person: { first: 'Sally', middle: 'Shelly', last: 'Samuel', level: 1, // nick is left empty to ensure optional properties work }, }) await record.save() const result = await MapTestTable.search({ 'person.first': 'Sally', } as any).exec() expect(result.count).to.eq(1) expect(result[0].person.first).to.eq('Sally') expect(result.records[0].person.first).to.eq('Sally') // ensure you can look through the result as an array for (const doc of result) { expect(doc.person.first).to.eq('Sally') } const searchOutput = await MapTestTable.search() .filter('person', 'first').eq('Sally') .exec() expect(searchOutput.count).to.eq(1) expect(searchOutput[0].person.first).to.eq('Sally') }) it('should allow maps within maps', async () => { const record = MapTestTable.new({ id: 3, contact: { name: { first: 'Homer', last: 'Simpson', }, address: { line1: '742 Evergreen Terrace', city: 'Springfield', state: 'Simpcity', }, dob: new Date(1956, 4, 12), // May 12, 1956 }, }) expect(record.contact?.name.first).to.eq('homer') expect(record.contact?.name.last).to.eq('SIMPSON') await record.save() const loaded = await MapTestTable.primaryKey.get(3) should().exist(loaded) if (loaded != null) { expect(loaded.getAttributeDynamoValue('contact')).to.deep.eq({ M: { name: { M: { first: { S: 'homer' }, last: { S: 'SIMPSON' }, }, }, address: { M: { line1: { S: '742 Evergreen Terrace' }, city: { S: 'Springfield' }, state: { S: 'Simpcity' }, }, }, dob: { S: '1956-05-12', }, }, }) expect(loaded.contact.name.first).to.eq('homer') expect(loaded.contact.name.last).to.eq('SIMPSON') expect(loaded.toJSON()).to.deep.eq({ id: 3, contact: { name: { first: 'homer', last: 'SIMPSON', }, address: { line1: '742 Evergreen Terrace', city: 'Springfield', state: 'Simpcity', }, dob: '1956-05-12', }, }) } }) it('should allow you to query using deep child attributes', async () => { const record = MapTestTable.new({ id: 4, contact: { name: { first: 'Marge', last: 'Simpson', }, }, }) await record.save() const result = await MapTestTable.search({ 'contact.name.first': 'Marge', } as any).exec() expect(result.count).to.eq(1) expect(result.length).to.eq(1) expect(result[0].contact.name.first).to.eq('marge') const searchOutput = await MapTestTable.search() .filter('contact', 'name', 'first').eq('marge') .exec() expect(searchOutput.count).to.eq(1) expect(searchOutput.length).to.eq(1) expect(searchOutput[0].contact.name.first).to.eq('marge') }) it('should support use of fromJSON to support REST APIs and DB Seeding', async () => { const record = MapTestTable.fromJSON({ id: 3, contact: { name: { first: 'Homer', last: 'Simpson', }, address: { line1: '742 Evergreen Terrace', city: 'Springfield', state: 'Simpcity', }, dob: '1956-05-12', }, }) expect(record.contact.address?.line1).to.eq('742 Evergreen Terrace') expect(record.contact.dob).to.be.instanceOf(Date) expect(record.contact.dob?.toISOString()).to.eq('1956-05-12T00:00:00.000Z') }) })