@@ -85,41 +85,50 @@ export interface SecondaryIndexProps {
85
85
indexName : string ;
86
86
87
87
/**
88
- * The attribute of a partition key for the secondary index.
88
+ * The set of attributes that are projected into the secondary index.
89
+ * @default ALL
89
90
*/
90
- partitionKey : Attribute ;
91
+ projectionType ?: ProjectionType ;
91
92
92
93
/**
93
- * The attribute of a sort key for the secondary index.
94
+ * The non-key attributes that are projected into the secondary index.
94
95
* @default undefined
95
96
*/
96
- sortKey ?: Attribute ;
97
+ nonKeyAttributes ?: string [ ] ;
98
+ }
97
99
100
+ export interface GlobalSecondaryIndexProps extends SecondaryIndexProps {
98
101
/**
99
- * The set of attributes that are projected into the secondary index.
100
- * @default ALL
102
+ * The attribute of a partition key for the global secondary index.
101
103
*/
102
- projectionType ?: ProjectionType ;
104
+ partitionKey : Attribute ;
103
105
104
106
/**
105
- * The non-key attributes that are projected into the secondary index.
107
+ * The attribute of a sort key for the global secondary index.
106
108
* @default undefined
107
109
*/
108
- nonKeyAttributes ?: string [ ] ;
110
+ sortKey ?: Attribute ;
109
111
110
112
/**
111
- * The read capacity for the secondary index.
113
+ * The read capacity for the global secondary index.
112
114
* @default 5
113
115
*/
114
116
readCapacity ?: number ;
115
117
116
118
/**
117
- * The write capacity for the secondary index.
119
+ * The write capacity for the global secondary index.
118
120
* @default 5
119
121
*/
120
122
writeCapacity ?: number ;
121
123
}
122
124
125
+ export interface LocalSecondaryIndexProps extends SecondaryIndexProps {
126
+ /**
127
+ * The attribute of a sort key for the local secondary index.
128
+ */
129
+ sortKey : Attribute ;
130
+ }
131
+
123
132
/* tslint:disable:max-line-length */
124
133
export interface AutoScalingProps {
125
134
/**
@@ -169,9 +178,14 @@ export class Table extends Construct {
169
178
private readonly keySchema = new Array < dynamodb . TableResource . KeySchemaProperty > ( ) ;
170
179
private readonly attributeDefinitions = new Array < dynamodb . TableResource . AttributeDefinitionProperty > ( ) ;
171
180
private readonly globalSecondaryIndexes = new Array < dynamodb . TableResource . GlobalSecondaryIndexProperty > ( ) ;
181
+ private readonly localSecondaryIndexes = new Array < dynamodb . TableResource . LocalSecondaryIndexProperty > ( ) ;
172
182
183
+ private readonly secondaryIndexNames : string [ ] = [ ] ;
173
184
private readonly nonKeyAttributes : string [ ] = [ ] ;
174
185
186
+ private tablePartitionKey : Attribute | undefined = undefined ;
187
+ private tableSortKey : Attribute | undefined = undefined ;
188
+
175
189
private readScalingPolicyResource ?: applicationautoscaling . ScalingPolicyResource ;
176
190
private writeScalingPolicyResource ?: applicationautoscaling . ScalingPolicyResource ;
177
191
@@ -183,6 +197,7 @@ export class Table extends Construct {
183
197
keySchema : this . keySchema ,
184
198
attributeDefinitions : this . attributeDefinitions ,
185
199
globalSecondaryIndexes : this . globalSecondaryIndexes ,
200
+ localSecondaryIndexes : this . localSecondaryIndexes ,
186
201
pointInTimeRecoverySpecification : props . pitrEnabled ? { pointInTimeRecoveryEnabled : props . pitrEnabled } : undefined ,
187
202
provisionedThroughput : { readCapacityUnits : props . readCapacity || 5 , writeCapacityUnits : props . writeCapacity || 5 } ,
188
203
sseSpecification : props . sseEnabled ? { sseEnabled : props . sseEnabled } : undefined ,
@@ -205,55 +220,85 @@ export class Table extends Construct {
205
220
}
206
221
}
207
222
223
+ /**
224
+ * Add a partition key of table.
225
+ *
226
+ * @param attribute the partition key attribute of table
227
+ * @returns a reference to this object so that method calls can be chained together
228
+ */
208
229
public addPartitionKey ( attribute : Attribute ) : this {
209
230
this . addKey ( attribute , HASH_KEY_TYPE ) ;
231
+ this . tablePartitionKey = attribute ;
210
232
return this ;
211
233
}
212
234
235
+ /**
236
+ * Add a sort key of table.
237
+ *
238
+ * @param attribute the sort key of table
239
+ * @returns a reference to this object so that method calls can be chained together
240
+ */
213
241
public addSortKey ( attribute : Attribute ) : this {
214
242
this . addKey ( attribute , RANGE_KEY_TYPE ) ;
243
+ this . tableSortKey = attribute ;
215
244
return this ;
216
245
}
217
246
218
- public addGlobalSecondaryIndex ( props : SecondaryIndexProps ) {
247
+ /**
248
+ * Add a global secondary index of table.
249
+ *
250
+ * @param props the property of global secondary index
251
+ */
252
+ public addGlobalSecondaryIndex ( props : GlobalSecondaryIndexProps ) {
219
253
if ( this . globalSecondaryIndexes . length === 5 ) {
220
254
// https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-secondary-indexes
221
255
throw new RangeError ( 'a maximum number of global secondary index per table is 5' ) ;
222
256
}
223
257
224
- if ( props . projectionType === ProjectionType . Include && ! props . nonKeyAttributes ) {
225
- // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-projectionobject.html
226
- throw new Error ( `non-key attributes should be specified when using ${ ProjectionType . Include } projection type` ) ;
227
- }
228
-
229
- if ( props . projectionType !== ProjectionType . Include && props . nonKeyAttributes ) {
230
- // this combination causes validation exception, status code 400, while trying to create CFN stack
231
- throw new Error ( `non-key attributes should not be specified when not using ${ ProjectionType . Include } projection type` ) ;
232
- }
258
+ this . validateIndexName ( props . indexName ) ;
233
259
234
- // build key schema for index
260
+ // build key schema and projection for index
235
261
const gsiKeySchema = this . buildIndexKeySchema ( props . partitionKey , props . sortKey ) ;
262
+ const gsiProjection = this . buildIndexProjection ( props ) ;
236
263
237
- // register attribute to check if a given configuration is valid
238
- this . registerAttribute ( props . partitionKey ) ;
239
- if ( props . sortKey ) {
240
- this . registerAttribute ( props . sortKey ) ;
241
- }
242
- if ( props . nonKeyAttributes ) {
243
- this . validateNonKeyAttributes ( props . nonKeyAttributes ) ;
244
- }
245
-
264
+ this . secondaryIndexNames . push ( props . indexName ) ;
246
265
this . globalSecondaryIndexes . push ( {
247
266
indexName : props . indexName ,
248
267
keySchema : gsiKeySchema ,
249
- projection : {
250
- projectionType : props . projectionType ? props . projectionType : ProjectionType . All ,
251
- nonKeyAttributes : props . nonKeyAttributes ? props . nonKeyAttributes : undefined
252
- } ,
268
+ projection : gsiProjection ,
253
269
provisionedThroughput : { readCapacityUnits : props . readCapacity || 5 , writeCapacityUnits : props . writeCapacity || 5 }
254
270
} ) ;
255
271
}
256
272
273
+ /**
274
+ * Add a local secondary index of table.
275
+ *
276
+ * @param props the property of local secondary index
277
+ */
278
+ public addLocalSecondaryIndex ( props : LocalSecondaryIndexProps ) {
279
+ if ( this . localSecondaryIndexes . length === 5 ) {
280
+ // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-secondary-indexes
281
+ throw new RangeError ( 'a maximum number of local secondary index per table is 5' ) ;
282
+ }
283
+
284
+ if ( ! this . tablePartitionKey ) {
285
+ throw new Error ( 'a partition key of the table must be specified first through addPartitionKey()' ) ;
286
+ }
287
+
288
+ this . validateIndexName ( props . indexName ) ;
289
+
290
+ // build key schema and projection for index
291
+ const lsiKeySchema = this . buildIndexKeySchema ( this . tablePartitionKey , props . sortKey ) ;
292
+ const lsiProjection = this . buildIndexProjection ( props ) ;
293
+
294
+ this . secondaryIndexNames . push ( props . indexName ) ;
295
+ this . localSecondaryIndexes . push ( {
296
+ indexName : props . indexName ,
297
+ keySchema : lsiKeySchema ,
298
+ projection : lsiProjection
299
+ } ) ;
300
+ }
301
+
257
302
public addReadAutoScaling ( props : AutoScalingProps ) {
258
303
this . readScalingPolicyResource = this . buildAutoScaling ( this . readScalingPolicyResource , 'Read' , props ) ;
259
304
}
@@ -262,18 +307,41 @@ export class Table extends Construct {
262
307
this . writeScalingPolicyResource = this . buildAutoScaling ( this . writeScalingPolicyResource , 'Write' , props ) ;
263
308
}
264
309
310
+ /**
311
+ * Validate the table construct.
312
+ *
313
+ * @returns an array of validation error message
314
+ */
265
315
public validate ( ) : string [ ] {
266
316
const errors = new Array < string > ( ) ;
267
- if ( ! this . findKey ( HASH_KEY_TYPE ) ) {
317
+
318
+ if ( ! this . tablePartitionKey ) {
268
319
errors . push ( 'a partition key must be specified' ) ;
269
320
}
321
+ if ( this . localSecondaryIndexes . length > 0 && ! this . tableSortKey ) {
322
+ errors . push ( 'a sort key of the table must be specified to add local secondary indexes' ) ;
323
+ }
324
+
270
325
return errors ;
271
326
}
272
327
328
+ /**
329
+ * Validate index name to check if a duplicate name already exists.
330
+ *
331
+ * @param indexName a name of global or local secondary index
332
+ */
333
+ private validateIndexName ( indexName : string ) {
334
+ if ( this . secondaryIndexNames . includes ( indexName ) ) {
335
+ // a duplicate index name causes validation exception, status code 400, while trying to create CFN stack
336
+ throw new Error ( `a duplicate index name, ${ indexName } , is not allowed` ) ;
337
+ }
338
+ this . secondaryIndexNames . push ( indexName ) ;
339
+ }
340
+
273
341
/**
274
342
* Validate non-key attributes by checking limits within secondary index, which may vary in future.
275
343
*
276
- * @param { string[] } nonKeyAttributes a list of non-key attribute names
344
+ * @param nonKeyAttributes a list of non-key attribute names
277
345
*/
278
346
private validateNonKeyAttributes ( nonKeyAttributes : string [ ] ) {
279
347
if ( this . nonKeyAttributes . length + nonKeyAttributes . length > 20 ) {
@@ -313,17 +381,40 @@ export class Table extends Construct {
313
381
}
314
382
315
383
private buildIndexKeySchema ( partitionKey : Attribute , sortKey ?: Attribute ) : dynamodb . TableResource . KeySchemaProperty [ ] {
384
+ this . registerAttribute ( partitionKey ) ;
316
385
const indexKeySchema : dynamodb . TableResource . KeySchemaProperty [ ] = [
317
- { attributeName : partitionKey . name , keyType : HASH_KEY_TYPE }
386
+ { attributeName : partitionKey . name , keyType : HASH_KEY_TYPE }
318
387
] ;
319
388
320
389
if ( sortKey ) {
321
- indexKeySchema . push ( { attributeName : sortKey . name , keyType : RANGE_KEY_TYPE } ) ;
390
+ this . registerAttribute ( sortKey ) ;
391
+ indexKeySchema . push ( { attributeName : sortKey . name , keyType : RANGE_KEY_TYPE } ) ;
322
392
}
323
393
324
394
return indexKeySchema ;
325
395
}
326
396
397
+ private buildIndexProjection ( props : SecondaryIndexProps ) : dynamodb . TableResource . ProjectionProperty {
398
+ if ( props . projectionType === ProjectionType . Include && ! props . nonKeyAttributes ) {
399
+ // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-projectionobject.html
400
+ throw new Error ( `non-key attributes should be specified when using ${ ProjectionType . Include } projection type` ) ;
401
+ }
402
+
403
+ if ( props . projectionType !== ProjectionType . Include && props . nonKeyAttributes ) {
404
+ // this combination causes validation exception, status code 400, while trying to create CFN stack
405
+ throw new Error ( `non-key attributes should not be specified when not using ${ ProjectionType . Include } projection type` ) ;
406
+ }
407
+
408
+ if ( props . nonKeyAttributes ) {
409
+ this . validateNonKeyAttributes ( props . nonKeyAttributes ) ;
410
+ }
411
+
412
+ return {
413
+ projectionType : props . projectionType ? props . projectionType : ProjectionType . All ,
414
+ nonKeyAttributes : props . nonKeyAttributes ? props . nonKeyAttributes : undefined
415
+ } ;
416
+ }
417
+
327
418
private buildAutoScaling ( scalingPolicyResource : applicationautoscaling . ScalingPolicyResource | undefined ,
328
419
scalingType : string ,
329
420
props : AutoScalingProps ) {
@@ -411,7 +502,7 @@ export class Table extends Construct {
411
502
/**
412
503
* Register the key attribute of table or secondary index to assemble attribute definitions of TableResourceProps.
413
504
*
414
- * @param { Attribute } attribute the key attribute of table or secondary index
505
+ * @param attribute the key attribute of table or secondary index
415
506
*/
416
507
private registerAttribute ( attribute : Attribute ) {
417
508
const name = attribute . name ;
0 commit comments