@@ -85,41 +85,50 @@ export interface SecondaryIndexProps {
8585 indexName : string ;
8686
8787 /**
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
8990 */
90- partitionKey : Attribute ;
91+ projectionType ?: ProjectionType ;
9192
9293 /**
93- * The attribute of a sort key for the secondary index.
94+ * The non-key attributes that are projected into the secondary index.
9495 * @default undefined
9596 */
96- sortKey ?: Attribute ;
97+ nonKeyAttributes ?: string [ ] ;
98+ }
9799
100+ export interface GlobalSecondaryIndexProps extends SecondaryIndexProps {
98101 /**
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.
101103 */
102- projectionType ?: ProjectionType ;
104+ partitionKey : Attribute ;
103105
104106 /**
105- * The non-key attributes that are projected into the secondary index.
107+ * The attribute of a sort key for the global secondary index.
106108 * @default undefined
107109 */
108- nonKeyAttributes ?: string [ ] ;
110+ sortKey ?: Attribute ;
109111
110112 /**
111- * The read capacity for the secondary index.
113+ * The read capacity for the global secondary index.
112114 * @default 5
113115 */
114116 readCapacity ?: number ;
115117
116118 /**
117- * The write capacity for the secondary index.
119+ * The write capacity for the global secondary index.
118120 * @default 5
119121 */
120122 writeCapacity ?: number ;
121123}
122124
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+
123132/* tslint:disable:max-line-length */
124133export interface AutoScalingProps {
125134 /**
@@ -169,9 +178,14 @@ export class Table extends Construct {
169178 private readonly keySchema = new Array < dynamodb . TableResource . KeySchemaProperty > ( ) ;
170179 private readonly attributeDefinitions = new Array < dynamodb . TableResource . AttributeDefinitionProperty > ( ) ;
171180 private readonly globalSecondaryIndexes = new Array < dynamodb . TableResource . GlobalSecondaryIndexProperty > ( ) ;
181+ private readonly localSecondaryIndexes = new Array < dynamodb . TableResource . LocalSecondaryIndexProperty > ( ) ;
172182
183+ private readonly secondaryIndexNames : string [ ] = [ ] ;
173184 private readonly nonKeyAttributes : string [ ] = [ ] ;
174185
186+ private tablePartitionKey : Attribute | undefined = undefined ;
187+ private tableSortKey : Attribute | undefined = undefined ;
188+
175189 private readScalingPolicyResource ?: applicationautoscaling . ScalingPolicyResource ;
176190 private writeScalingPolicyResource ?: applicationautoscaling . ScalingPolicyResource ;
177191
@@ -183,6 +197,7 @@ export class Table extends Construct {
183197 keySchema : this . keySchema ,
184198 attributeDefinitions : this . attributeDefinitions ,
185199 globalSecondaryIndexes : this . globalSecondaryIndexes ,
200+ localSecondaryIndexes : this . localSecondaryIndexes ,
186201 pointInTimeRecoverySpecification : props . pitrEnabled ? { pointInTimeRecoveryEnabled : props . pitrEnabled } : undefined ,
187202 provisionedThroughput : { readCapacityUnits : props . readCapacity || 5 , writeCapacityUnits : props . writeCapacity || 5 } ,
188203 sseSpecification : props . sseEnabled ? { sseEnabled : props . sseEnabled } : undefined ,
@@ -205,55 +220,85 @@ export class Table extends Construct {
205220 }
206221 }
207222
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+ */
208229 public addPartitionKey ( attribute : Attribute ) : this {
209230 this . addKey ( attribute , HASH_KEY_TYPE ) ;
231+ this . tablePartitionKey = attribute ;
210232 return this ;
211233 }
212234
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+ */
213241 public addSortKey ( attribute : Attribute ) : this {
214242 this . addKey ( attribute , RANGE_KEY_TYPE ) ;
243+ this . tableSortKey = attribute ;
215244 return this ;
216245 }
217246
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 ) {
219253 if ( this . globalSecondaryIndexes . length === 5 ) {
220254 // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-secondary-indexes
221255 throw new RangeError ( 'a maximum number of global secondary index per table is 5' ) ;
222256 }
223257
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 ) ;
233259
234- // build key schema for index
260+ // build key schema and projection for index
235261 const gsiKeySchema = this . buildIndexKeySchema ( props . partitionKey , props . sortKey ) ;
262+ const gsiProjection = this . buildIndexProjection ( props ) ;
236263
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 ) ;
246265 this . globalSecondaryIndexes . push ( {
247266 indexName : props . indexName ,
248267 keySchema : gsiKeySchema ,
249- projection : {
250- projectionType : props . projectionType ? props . projectionType : ProjectionType . All ,
251- nonKeyAttributes : props . nonKeyAttributes ? props . nonKeyAttributes : undefined
252- } ,
268+ projection : gsiProjection ,
253269 provisionedThroughput : { readCapacityUnits : props . readCapacity || 5 , writeCapacityUnits : props . writeCapacity || 5 }
254270 } ) ;
255271 }
256272
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+
257302 public addReadAutoScaling ( props : AutoScalingProps ) {
258303 this . readScalingPolicyResource = this . buildAutoScaling ( this . readScalingPolicyResource , 'Read' , props ) ;
259304 }
@@ -262,18 +307,41 @@ export class Table extends Construct {
262307 this . writeScalingPolicyResource = this . buildAutoScaling ( this . writeScalingPolicyResource , 'Write' , props ) ;
263308 }
264309
310+ /**
311+ * Validate the table construct.
312+ *
313+ * @returns an array of validation error message
314+ */
265315 public validate ( ) : string [ ] {
266316 const errors = new Array < string > ( ) ;
267- if ( ! this . findKey ( HASH_KEY_TYPE ) ) {
317+
318+ if ( ! this . tablePartitionKey ) {
268319 errors . push ( 'a partition key must be specified' ) ;
269320 }
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+
270325 return errors ;
271326 }
272327
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+
273341 /**
274342 * Validate non-key attributes by checking limits within secondary index, which may vary in future.
275343 *
276- * @param { string[] } nonKeyAttributes a list of non-key attribute names
344+ * @param nonKeyAttributes a list of non-key attribute names
277345 */
278346 private validateNonKeyAttributes ( nonKeyAttributes : string [ ] ) {
279347 if ( this . nonKeyAttributes . length + nonKeyAttributes . length > 20 ) {
@@ -313,17 +381,40 @@ export class Table extends Construct {
313381 }
314382
315383 private buildIndexKeySchema ( partitionKey : Attribute , sortKey ?: Attribute ) : dynamodb . TableResource . KeySchemaProperty [ ] {
384+ this . registerAttribute ( partitionKey ) ;
316385 const indexKeySchema : dynamodb . TableResource . KeySchemaProperty [ ] = [
317- { attributeName : partitionKey . name , keyType : HASH_KEY_TYPE }
386+ { attributeName : partitionKey . name , keyType : HASH_KEY_TYPE }
318387 ] ;
319388
320389 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 } ) ;
322392 }
323393
324394 return indexKeySchema ;
325395 }
326396
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+
327418 private buildAutoScaling ( scalingPolicyResource : applicationautoscaling . ScalingPolicyResource | undefined ,
328419 scalingType : string ,
329420 props : AutoScalingProps ) {
@@ -411,7 +502,7 @@ export class Table extends Construct {
411502 /**
412503 * Register the key attribute of table or secondary index to assemble attribute definitions of TableResourceProps.
413504 *
414- * @param { Attribute } attribute the key attribute of table or secondary index
505+ * @param attribute the key attribute of table or secondary index
415506 */
416507 private registerAttribute ( attribute : Attribute ) {
417508 const name = attribute . name ;
0 commit comments