Skip to content

Commit ab7175c

Browse files
Merge pull request #251 from shiftcode/#250-model-metadata
#250 model metadata
2 parents 9d8acc4 + d827d77 commit ab7175c

35 files changed

+859
-246
lines changed

src/decorator/decorators.spec.ts

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
SimpleModel,
2020
} from '../../test/models'
2121
import { Form } from '../../test/models/real-world'
22+
import { updateDynamoEasyConfig } from '../config/update-config.function'
23+
import { LogLevel } from '../logger/log-level.type'
2224
import { CollectionProperty } from './impl/collection/collection-property.decorator'
2325
import { GSIPartitionKey } from './impl/index/gsi-partition-key.decorator'
2426
import { GSISortKey } from './impl/index/gsi-sort-key.decorator'
@@ -65,7 +67,7 @@ describe('Decorators should add correct metadata', () => {
6567
})
6668

6769
it('with no properties', () => {
68-
expect(modelOptions.properties).toBeUndefined()
70+
expect(modelOptions.properties).toEqual([])
6971
})
7072
})
7173

@@ -317,6 +319,77 @@ describe('Decorators should add correct metadata', () => {
317319
})
318320
})
319321

322+
describe('multiple property decorators', () => {
323+
const REVERSE_INDEX = 'reverse-index'
324+
const OTHER_INDEX = 'other-index'
325+
const LSI_1 = 'lsi-1'
326+
const LSI_2 = 'lsi-2'
327+
328+
@Model()
329+
class ABC {
330+
@PartitionKey()
331+
@Property({ name: 'pk' })
332+
@GSISortKey(REVERSE_INDEX)
333+
id: string
334+
335+
@SortKey()
336+
@Property({ name: 'sk' })
337+
@GSIPartitionKey(REVERSE_INDEX)
338+
@GSISortKey(OTHER_INDEX)
339+
timestamp: number
340+
341+
@GSIPartitionKey(OTHER_INDEX)
342+
@LSISortKey(LSI_1)
343+
@LSISortKey(LSI_2)
344+
otherId: string
345+
}
346+
347+
let metaData: Metadata<ABC>
348+
349+
beforeEach(() => (metaData = metadataForModel(ABC)))
350+
351+
it('PartitionKey & Property & GSISortKey should combine the data', () => {
352+
const propData = metaData.forProperty('id')
353+
expect(propData).toEqual({
354+
key: { type: 'HASH' },
355+
name: 'id',
356+
nameDb: 'pk',
357+
typeInfo: { type: String },
358+
keyForGSI: { [REVERSE_INDEX]: 'RANGE' },
359+
})
360+
})
361+
it('SortKey & Property & GSIPartitionKey & GSISortKey should combine the data', () => {
362+
const propData = metaData.forProperty('timestamp')
363+
expect(propData).toEqual({
364+
key: { type: 'RANGE' },
365+
name: 'timestamp',
366+
nameDb: 'sk',
367+
typeInfo: { type: Number },
368+
keyForGSI: { [REVERSE_INDEX]: 'HASH', [OTHER_INDEX]: 'RANGE' },
369+
})
370+
})
371+
it('GSIPartitionKey & multiple LSISortkey should combine the data', () => {
372+
const propData = metaData.forProperty('otherId')
373+
expect(propData).toBeDefined()
374+
expect(propData!.name).toEqual('otherId')
375+
expect(propData!.nameDb).toEqual('otherId')
376+
expect(propData!.typeInfo).toEqual({ type: String })
377+
expect(propData!.keyForGSI).toEqual({ [OTHER_INDEX]: 'HASH' })
378+
expect(propData!.sortKeyForLSI).toContain(LSI_1)
379+
expect(propData!.sortKeyForLSI).toContain(LSI_2)
380+
})
381+
it('correctly defines the indexes', () => {
382+
const reverseIndex = metaData.getIndex(REVERSE_INDEX)
383+
const otherIndex = metaData.getIndex(OTHER_INDEX)
384+
const lsi1 = metaData.getIndex(LSI_1)
385+
const lsi2 = metaData.getIndex(LSI_2)
386+
expect(reverseIndex).toEqual({ partitionKey: 'sk', sortKey: 'pk' })
387+
expect(otherIndex).toEqual({ partitionKey: 'otherId', sortKey: 'sk' })
388+
expect(lsi1).toEqual({ partitionKey: 'pk', sortKey: 'otherId' })
389+
expect(lsi2).toEqual({ partitionKey: 'pk', sortKey: 'otherId' })
390+
})
391+
})
392+
320393
describe('enum (no Enum decorator)', () => {
321394
let metadata: Metadata<ModelWithEnum>
322395

@@ -544,17 +617,43 @@ describe('Decorators should add correct metadata', () => {
544617
})
545618

546619
describe('should throw when more than one partitionKey was defined in a model', () => {
547-
expect(() => {
620+
it('does so', () => {
621+
expect(() => {
622+
@Model()
623+
class InvalidModel {
624+
@PartitionKey()
625+
partKeyA: string
626+
627+
@PartitionKey()
628+
partKeyB: string
629+
}
630+
631+
return new InvalidModel()
632+
}).toThrow()
633+
})
634+
})
635+
636+
describe('decorate property multiple times identically', () => {
637+
let logReceiver: jest.Mock
638+
639+
beforeEach(() => {
640+
logReceiver = jest.fn()
641+
updateDynamoEasyConfig({ logReceiver })
642+
})
643+
644+
it('should not throw but warn, if the PartitionKey is two times annotated', () => {
548645
@Model()
549-
class InvalidModel {
646+
class NotCoolButOkModel {
550647
@PartitionKey()
551-
partKeyA: string
552-
553648
@PartitionKey()
554-
partKeyB: string
649+
doppeltGemoppelt: string
555650
}
556651

557-
return new InvalidModel()
558-
}).toThrow()
652+
const propertyMetaData = metadataForModel(NotCoolButOkModel).forProperty('doppeltGemoppelt')
653+
expect(propertyMetaData).toBeDefined()
654+
expect(propertyMetaData!.key).toEqual({ type: 'HASH' })
655+
expect(logReceiver).toBeCalledTimes(1)
656+
expect(logReceiver.mock.calls[0][0].level).toBe(LogLevel.WARNING)
657+
})
559658
})
560659
})

src/decorator/impl/index/util.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function initOrUpdateIndex(indexType: IndexType, indexData: IndexData, ta
3636
indexData,
3737
)
3838
break
39+
// `default` is actually unnecessary - but could only be removed by cast or nonNullAssertion of `propertyMetadata`
3940
default:
4041
throw new Error(`unsupported index type ${indexType}`)
4142
}

src/decorator/impl/key/partition-key.decorator.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/**
22
* @module decorators
33
*/
4+
import { createOptModelLogger } from '../../../logger/logger'
45
import { PropertyMetadata } from '../../metadata/property-metadata.model'
56
import { initOrUpdateProperty } from '../property/init-or-update-property.function'
67
import { KEY_PROPERTY } from '../property/key-property.const'
78

9+
const logger = createOptModelLogger('@PartitionKey')
10+
811
export function PartitionKey(): PropertyDecorator {
912
return (target: any, propertyKey: string | symbol) => {
1013
if (typeof propertyKey === 'string') {
@@ -14,9 +17,11 @@ export function PartitionKey(): PropertyDecorator {
1417
const existingPartitionKeys = properties.filter(property => property.key && property.key.type === 'HASH')
1518
if (existingPartitionKeys.length) {
1619
if (properties.find(property => property.name === propertyKey)) {
17-
// just ignore this and go on, somehow the partition key gets defined
18-
// tslint:disable-next-line:no-console
19-
console.warn(`this is the second execution to define the partitionKey for property ${propertyKey}`)
20+
// just ignore this and go on, somehow the partition key gets defined two times
21+
logger.warn(
22+
`this is the second execution to define the partitionKey for property ${propertyKey}`,
23+
target.constructor,
24+
)
2025
} else {
2126
throw new Error(
2227
'only one partition key is allowed per model, if you want to define key for indexes use one of these decorators: ' +
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @module decorators
3+
*/
4+
5+
/**
6+
* @hidden
7+
*/
8+
export const modelErrors = {
9+
gsiMultiplePk: (indexName: string, propDbName: string) =>
10+
`there is already a partition key defined for global secondary index ${indexName} (property name: ${propDbName})`,
11+
gsiMultipleSk: (indexName: string, propDbName: string) =>
12+
`there is already a sort key defined for global secondary index ${indexName} (property name: ${propDbName})`,
13+
lsiMultipleSk: (indexName: string, propDbName: string) =>
14+
`only one sort key can be defined for the same local secondary index, ${propDbName} is already defined as sort key for index ${indexName}`,
15+
lsiRequiresPk: (indexName: string, propDbName: string) =>
16+
`the local secondary index ${indexName} requires the partition key to be defined`,
17+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// tslint:disable:max-classes-per-file
2+
import { GSIPartitionKey } from '../index/gsi-partition-key.decorator'
3+
import { GSISortKey } from '../index/gsi-sort-key.decorator'
4+
import { LSISortKey } from '../index/lsi-sort-key.decorator'
5+
import { PartitionKey } from '../key/partition-key.decorator'
6+
import { modelErrors } from './errors.const'
7+
import { Model } from './model.decorator'
8+
9+
const IX_NAME = 'anIndexName'
10+
11+
describe('@model decorator', () => {
12+
describe('getGlobalSecondaryIndexes', () => {
13+
// throws on applying decorator
14+
15+
it('throws when defining multiple partitionKeys for same gsi', () => {
16+
expect(() => {
17+
// @ts-ignore
18+
@Model()
19+
class FailModel {
20+
@GSIPartitionKey(IX_NAME)
21+
pk1: string
22+
@GSIPartitionKey(IX_NAME)
23+
pk2: string
24+
@GSISortKey(IX_NAME)
25+
sk1: string
26+
}
27+
}).toThrow(modelErrors.gsiMultiplePk(IX_NAME, 'pk2'))
28+
})
29+
it('throws when defining multiple sortKeys for same gsi', () => {
30+
expect(() => {
31+
// @ts-ignore
32+
@Model()
33+
class FailModel {
34+
@GSIPartitionKey(IX_NAME)
35+
pk1: string
36+
@GSISortKey(IX_NAME)
37+
sk1: string
38+
@GSISortKey(IX_NAME)
39+
sk2: string
40+
}
41+
}).toThrow(modelErrors.gsiMultipleSk(IX_NAME, 'sk2'))
42+
})
43+
})
44+
describe('getLocalSecondaryIndexes', () => {
45+
it('throws when defining LSI sortKey but no PartitionKey', () => {
46+
expect(() => {
47+
// @ts-ignore
48+
@Model()
49+
class FailModel {
50+
@LSISortKey(IX_NAME)
51+
sk1: string
52+
}
53+
}).toThrow(modelErrors.lsiRequiresPk(IX_NAME, 'sk1'))
54+
})
55+
it('throws when defining multiple sortKeys for same lsi', () => {
56+
expect(() => {
57+
// @ts-ignore
58+
@Model()
59+
class FailModel {
60+
@PartitionKey()
61+
pk1: string
62+
@LSISortKey(IX_NAME)
63+
sk1: string
64+
@LSISortKey(IX_NAME)
65+
sk2: string
66+
}
67+
}).toThrow(modelErrors.lsiMultipleSk(IX_NAME, 'sk2'))
68+
})
69+
})
70+
})

0 commit comments

Comments
 (0)