Skip to content

Commit eeeca92

Browse files
committed
refactor(decorator): keep ModelMetadata properties and indexes defined
1 parent ccc135f commit eeeca92

File tree

9 files changed

+103
-118
lines changed

9 files changed

+103
-118
lines changed

src/decorator/decorators.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('Decorators should add correct metadata', () => {
6565
})
6666

6767
it('with no properties', () => {
68-
expect(modelOptions.properties).toBeUndefined()
68+
expect(modelOptions.properties).toEqual([])
6969
})
7070
})
7171

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
}
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+
}

src/decorator/impl/model/model.decorator.ts

Lines changed: 66 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ModelMetadata } from '../../metadata/model-metadata.model'
77
import { PropertyMetadata } from '../../metadata/property-metadata.model'
88
import { SecondaryIndex } from '../index/secondary-index'
99
import { KEY_PROPERTY } from '../property/key-property.const'
10+
import { modelErrors } from './errors.const'
1011
import { KEY_MODEL } from './key-model.const'
1112
import { ModelData } from './model-data.model'
1213

@@ -21,27 +22,26 @@ export function Model(opts: ModelData = {}): ClassDecorator {
2122
// get all the properties with @Property() annotation (or @PartitionKey(),...)
2223
// if given class has own properties, all inherited properties are already set and we can get the properties with 'getOwnMetadata'.
2324
// otherwise when the given class does not have own properties, there's no 'ownMetadata' but we need to get them from the class it extends with 'getMetadata'
24-
const properties: Array<PropertyMetadata<any>> = Reflect.hasOwnMetadata(KEY_PROPERTY, constructor)
25-
? Reflect.getOwnMetadata(KEY_PROPERTY, constructor)
26-
: Reflect.getMetadata(KEY_PROPERTY, constructor)
25+
const properties: Array<PropertyMetadata<any>> =
26+
(Reflect.hasOwnMetadata(KEY_PROPERTY, constructor)
27+
? Reflect.getOwnMetadata(KEY_PROPERTY, constructor)
28+
: Reflect.getMetadata(KEY_PROPERTY, constructor)) || []
2729

2830
// get partition key
29-
const partitionKeys = properties
30-
? properties.filter(property => property.key && property.key.type === 'HASH')
31-
: null
32-
const partitionKeyName: string | null = partitionKeys && partitionKeys.length ? partitionKeys[0].nameDb : null
31+
const partitionKeys = properties.filter(property => property.key && property.key.type === 'HASH')
32+
33+
const partitionKeyName: string | null = partitionKeys.length ? partitionKeys[0].nameDb : null
3334

3435
/*
3536
* get the local and global secondary indexes
3637
*/
37-
const globalSecondaryIndexes: any = getGlobalSecondaryIndexes(properties) || []
38-
const localSecondaryIndexes: any = getLocalSecondaryIndexes(partitionKeyName, properties) || []
38+
const globalSecondaryIndexes: any = getGlobalSecondaryIndexes(properties)
39+
const localSecondaryIndexes: any = getLocalSecondaryIndexes(partitionKeyName, properties)
3940
const indexes: Map<string, SecondaryIndex<any>> = new Map([...globalSecondaryIndexes, ...localSecondaryIndexes])
4041

41-
const transientProperties =
42-
properties && properties.length
43-
? properties.filter(property => property.transient === true).map(property => property.name)
44-
: []
42+
const transientProperties = properties.length
43+
? properties.filter(property => property.transient === true).map(property => property.name)
44+
: []
4545

4646
const metaData: ModelMetadata<any> = {
4747
clazz: constructor,
@@ -77,46 +77,36 @@ function testForLSI<T>(property: PropertyMetadata<T>): property is PropertyMetad
7777
/**
7878
* @hidden
7979
*/
80-
function getGlobalSecondaryIndexes(properties: Array<PropertyMetadata<any>>): Map<string, SecondaryIndex<any>> | null {
81-
if (properties && properties.length) {
82-
return properties.filter(testForGSI).reduce((map, property): Map<string, SecondaryIndex<any>> => {
83-
let gsi: SecondaryIndex<any>
84-
Object.keys(property.keyForGSI).forEach(indexName => {
85-
if (map.has(indexName)) {
86-
gsi = map.get(indexName)
87-
} else {
88-
gsi = <SecondaryIndex<any>>{}
89-
}
90-
91-
switch (property.keyForGSI[indexName]) {
92-
case 'HASH':
93-
if (gsi.partitionKey) {
94-
throw new Error(
95-
`there is already a partition key defined for global secondary index ${indexName} (property name: ${property.nameDb})`,
96-
)
97-
}
98-
99-
gsi.partitionKey = property.nameDb
100-
break
101-
case 'RANGE':
102-
if (gsi.sortKey) {
103-
throw new Error(
104-
`there is already a sort key defined for global secondary index ${indexName} (property name: ${property.nameDb})`,
105-
)
106-
}
107-
108-
gsi.sortKey = property.nameDb
109-
break
110-
}
111-
112-
map.set(indexName, gsi)
113-
})
114-
115-
return map
116-
}, new Map())
117-
} else {
118-
return null
119-
}
80+
function getGlobalSecondaryIndexes(properties: Array<PropertyMetadata<any>>): Map<string, SecondaryIndex<any>> {
81+
return properties.filter(testForGSI).reduce((map, property): Map<string, SecondaryIndex<any>> => {
82+
let gsi: SecondaryIndex<any>
83+
Object.keys(property.keyForGSI).forEach(indexName => {
84+
if (map.has(indexName)) {
85+
gsi = map.get(indexName)
86+
} else {
87+
gsi = <SecondaryIndex<any>>{}
88+
}
89+
90+
switch (property.keyForGSI[indexName]) {
91+
case 'HASH':
92+
if (gsi.partitionKey) {
93+
throw new Error(modelErrors.gsiMultiplePk(indexName, property.nameDb))
94+
}
95+
gsi.partitionKey = property.nameDb
96+
break
97+
case 'RANGE':
98+
if (gsi.sortKey) {
99+
throw new Error(modelErrors.gsiMultipleSk(indexName, property.nameDb))
100+
}
101+
gsi.sortKey = property.nameDb
102+
break
103+
}
104+
105+
map.set(indexName, gsi)
106+
})
107+
108+
return map
109+
}, new Map())
120110
}
121111

122112
/**
@@ -125,35 +115,27 @@ function getGlobalSecondaryIndexes(properties: Array<PropertyMetadata<any>>): Ma
125115
function getLocalSecondaryIndexes(
126116
basePartitionKey: string | null,
127117
properties: Array<PropertyMetadata<any>>,
128-
): Map<string, SecondaryIndex<any>> | null {
129-
if (properties && properties.length) {
130-
return properties.filter(testForLSI).reduce((map, property): Map<string, SecondaryIndex<any>> => {
131-
let lsi: SecondaryIndex<any>
132-
133-
property.sortKeyForLSI.forEach(indexName => {
134-
if (map.has(indexName)) {
135-
throw new Error(
136-
`only one sort key can be defined for the same local secondary index, ${property.nameDb} is already defined as sort key for index ${indexName}`,
137-
)
138-
}
139-
140-
if (!basePartitionKey) {
141-
throw new Error(
142-
'a local secondary index requires the partition key to be defined, use the @PartitionKey decorator',
143-
)
144-
}
145-
146-
lsi = {
147-
partitionKey: basePartitionKey,
148-
sortKey: property.nameDb,
149-
}
150-
151-
map.set(indexName, lsi)
152-
})
153-
154-
return map
155-
}, new Map())
156-
} else {
157-
return null
158-
}
118+
): Map<string, SecondaryIndex<any>> {
119+
return properties.filter(testForLSI).reduce((map, property): Map<string, SecondaryIndex<any>> => {
120+
let lsi: SecondaryIndex<any>
121+
122+
property.sortKeyForLSI.forEach(indexName => {
123+
if (map.has(indexName)) {
124+
throw new Error(modelErrors.lsiMultipleSk(indexName, property.nameDb))
125+
}
126+
127+
if (!basePartitionKey) {
128+
throw new Error(modelErrors.lsiRequiresPk(indexName, property.nameDb))
129+
}
130+
131+
lsi = {
132+
partitionKey: basePartitionKey,
133+
sortKey: property.nameDb,
134+
}
135+
136+
map.set(indexName, lsi)
137+
})
138+
139+
return map
140+
}, new Map())
159141
}

src/decorator/metadata/metadata.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,15 @@ export class Metadata<T> {
3232
modelOpts: ModelMetadata<M>,
3333
propertyName: keyof M,
3434
): PropertyMetadata<M> | undefined {
35-
return (
36-
(modelOpts.properties &&
37-
modelOpts.properties.find(property => property.name === propertyName || property.nameDb === propertyName)) ||
38-
undefined
39-
)
35+
return modelOpts.properties.find(property => property.name === propertyName || property.nameDb === propertyName)
4036
}
4137

4238
constructor(modelConstructor: ModelConstructor<T>) {
4339
this.modelOptions = Reflect.getMetadata(KEY_MODEL, modelConstructor)
4440
}
4541

4642
forProperty(propertyKey: keyof T | string): PropertyMetadata<T> | undefined {
47-
if (!this.modelOptions.properties) {
43+
if (this.modelOptions.properties.length === 0) {
4844
return
4945
}
5046
if (typeof propertyKey === 'string' && NESTED_ATTR_PATH_REGEX.test(<string>propertyKey)) {
@@ -90,7 +86,11 @@ export class Metadata<T> {
9086
if (indexName) {
9187
const index = this.getIndex(indexName)
9288
if (index) {
93-
return index.partitionKey
89+
if (index.partitionKey) {
90+
return index.partitionKey
91+
} else {
92+
throw new Error('the index exists but no partition key for it was defined. use @GSIPartitionKey(indexName)')
93+
}
9494
} else {
9595
throw new Error(`there is no index defined for name ${indexName}`)
9696
}
@@ -133,24 +133,15 @@ export class Metadata<T> {
133133
* @returns {SecondaryIndex[]} Returns all the secondary indexes if exists or an empty array if none is defined
134134
*/
135135
getIndexes(): Array<SecondaryIndex<T>> {
136-
if (this.modelOptions.indexes && this.modelOptions.indexes.size) {
137-
return Array.from(this.modelOptions.indexes.values())
138-
} else {
139-
return []
140-
}
136+
return Array.from(this.modelOptions.indexes.values())
141137
}
142138

143139
/**
144140
* @param {string} indexName
145141
* @returns {SecondaryIndex} Returns the index if one with given name exists, null otherwise
146142
*/
147143
getIndex(indexName: string): SecondaryIndex<T> | null {
148-
if (this.modelOptions.indexes) {
149-
const index = this.modelOptions.indexes.get(indexName)
150-
return index ? index : null
151-
}
152-
153-
return null
144+
return this.modelOptions.indexes.get(indexName) || null
154145
}
155146
}
156147

@@ -162,9 +153,9 @@ function filterBy<T, R>(
162153
predicate: (property: PropertyMetadata<any>) => boolean,
163154
defaultValue: R,
164155
): Array<PropertyMetadata<any>> | R {
165-
if (modelOptions && modelOptions.properties) {
156+
if (modelOptions) {
166157
const properties = modelOptions.properties.filter(predicate)
167-
if (properties && properties.length) {
158+
if (properties.length) {
168159
return properties
169160
}
170161
}

src/decorator/metadata/model-metadata.model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export interface ModelMetadata<T> {
1111
clazzName: string
1212
clazz: any
1313
tableName: string
14-
properties?: Array<PropertyMetadata<T>>
14+
properties: Array<PropertyMetadata<T>>
1515
transientProperties?: Array<string | number | symbol>
1616

1717
// local and global secondary indexes maps the name to the index definition (partition and optional sort key depending on index type)
18-
indexes?: Map<string, SecondaryIndex<T>>
18+
indexes: Map<string, SecondaryIndex<T>>
1919
}

src/decorator/util.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ export const getMetadataType = makeMetadataGetter<ModelConstructor<any>>(KEY_TYP
2727
/**
2828
* @hidden
2929
*/
30-
export function makeMetadataGetter<T>(metadataKey: string): (target: any, targetKey?: string) => T {
31-
return (target: any, targetKey?: string) => {
32-
if (targetKey) {
33-
return Reflect.getMetadata(metadataKey, target, targetKey)
34-
} else {
35-
return Reflect.getMetadata(metadataKey, target)
36-
}
37-
}
30+
export function makeMetadataGetter<T>(metadataKey: string): (target: any, targetKey: string) => T {
31+
return (target: any, targetKey: string) => Reflect.getMetadata(metadataKey, target, targetKey)
3832
}

src/mapper/mapper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ function testForKey<T>(p: PropertyMetadata<T>): p is PropertyMetadata<T> & { key
170170
export function createToKeyFn<T>(modelConstructor: ModelConstructor<T>): (item: Partial<T>) => Attributes<T> {
171171
const metadata = metadataForModel(modelConstructor)
172172
const properties = metadata.modelOptions.properties
173-
if (!properties) {
174-
throw new Error('metadata properties is not defined')
173+
if (!properties.length) {
174+
throw new Error('no properties defined on metadata')
175175
}
176176

177177
const keyProperties = properties.filter(testForKey)

test/helper/get-meta-data-property.function.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ export function getMetaDataProperty<T, K extends keyof T>(
44
modelOptions: ModelMetadata<T>,
55
propertyKey: K,
66
): PropertyMetadata<T[K]> | undefined {
7-
return <any>(modelOptions.properties || []).find(property => property.name === propertyKey)
7+
return <any>modelOptions.properties.find(property => property.name === propertyKey)
88
}

0 commit comments

Comments
 (0)