author Jeremy Daly <jeremy@jeremydaly.com>
 *  license import Entity from './Entity' ; import DynamoDb , { DocumentClient } from 'aws-sdk/clients/dynamodb' ; import { parseTable , ParsedTable } from '../lib/parseTable' ; import parseFilters from '../lib/expressionBuilder' ; import validateTypes from '../lib/validateTypes' ; import { FilterExpressions } from '../lib/expressionBuilder' ; import parseProjections , { ProjectionAttributes , ProjectionAttributesTable } from '../lib/projectionBuilder' ; import { ParsedEntity } from '../lib/parseEntity' ; import { error , conditonError } from '../lib/utils' ; import { Document } from 'aws-sdk/clients/textract' ; export interface TableConstructor { name : string ; alias ? : string | null ; partitionKey : string ; sortKey ? : string | null ; entityField ? : boolean | string ; attributes ? : TableAttributes ; indexes ? : TableIndexes ; autoExecute ? : boolean ; autoParse ? : boolean ; DocumentClient ? : DynamoDb . DocumentClient ; entities ? : { } ; removeNullAttributes ? : boolean ; } export type DynamoDBTypes = 'string' | 'boolean' | 'number' | 'list' | 'map' | 'binary' | 'set' ; export type DynamoDBSetTypes = 'string' | 'number' | 'binary' ; export interface executeParse { execute ? : boolean ; parse ? : boolean ; } export interface TableAttributeConfig { type : DynamoDBTypes ; setType ? : DynamoDBSetTypes ; } export interface TableAttributes { [ attr ] : DynamoDBTypes | TableAttributeConfig ; } export interface ParsedTableAttribute { [ attr ] : TableAttributeConfig & { mappings : { } ; } ; } export interface TableIndexes { [ index ] : { partitionKey ? : string ; sortKey ? : string ; } ; } export interface queryOptions { index ? : string ; limit ? : number ; reverse ? : boolean ; consistent ? : boolean ; capacity ? : DocumentClient . ReturnConsumedCapacity ; select ? : DocumentClient . Select ; eq ? : string | number ; lt ? : string | number ; lte ? : string | number ; gt ? : string | number ; gte ? : string | number ; between ? : [ string , string ] | [ number , number ] ; beginsWith ? : string ; filters ? : FilterExpressions ; attributes ? : ProjectionAttributes ; startKey ? : { } ; entity ? : string ; execute ? : boolean ; parse ? : boolean ; } export interface scanOptions { index ? : string ; limit ? : number ; consistent ? : boolean ; capacity ? : DocumentClient . ReturnConsumedCapacity ; select ? : DocumentClient . Select ; filters ? : FilterExpressions ; attributes ? : ProjectionAttributes ; startKey ? : { } ; segments ? : number ; segment ? : number ; entity ? : string ; execute ? : boolean ; parse ? : boolean ; } interface batchGetOptions { consistent ? : boolean ; capacity ? : DocumentClient . ReturnConsumedCapacity ; attributes ? : ProjectionAttributes ; include ? : string [ ] ; execute ? : boolean ; parse ? : boolean ; } interface batchGetParamsMeta { payload : any ; Tables : { [ key ] : Table ; } ; EntityProjections : { [ key ] : any ; } ; TableProjections : { [ key ] : string [ ] ; } ; } interface batchWriteOptions { capacity ? : DocumentClient . ReturnConsumedCapacity ; metrics ? : DocumentClient . ReturnItemCollectionMetrics ; execute ? : boolean ; parse ? : boolean ; } interface transactGetParamsOptions { capacity ? : DocumentClient . ReturnConsumedCapacity ; } type transactGetOptions = transactGetParamsOptions & executeParse ; interface transactWriteParamsOptions { capacity ? : DocumentClient . ReturnConsumedCapacity ; metrics ? : DocumentClient . ReturnItemCollectionMetrics ; token ? : string ; } type transactWriteOptions = transactWriteParamsOptions & executeParse ; interface transactGetParamsMeta { Entities : ( any | undefined ) [ ] ; payload : DocumentClient . TransactGetItemsInput ; } class Table { private _execute : boolean = true ; private _parse : boolean = true ; public _removeNulls : boolean = true ; private _docClient ? : DocumentClient ; private _entities : string [ ] = [ ] ; public Table ! : ParsedTable [ 'Table' ] ; public name ! : string ; public alias ? : string ; [ key ] : any ; constructor ( table ) { if ( typeof table !== 'object' || Array . isArray ( table ) ) error ( 'Please provide a valid table definition' ) ; Object . assign ( this , parseTable ( table ) ) ; } set autoExecute ( val ) { this . _execute = typeof val === 'boolean' ? val : true ; } get autoExecute ( ) { return this . _execute ; } set autoParse ( val ) { this . _parse = typeof val === 'boolean' ? val : true ; } get autoParse ( ) { return this . _parse ; } set removeNullAttributes ( val ) { this . _removeNulls = typeof val === 'boolean' ? val : true ; } get removeNullAttributes ( ) { return this . _removeNulls ; } get DocumentClient ( ) { return this . _docClient ; } set DocumentClient ( docClient ) { if ( docClient && docClient . get && docClient . put && docClient . delete && docClient . update ) { if ( docClient . options ! . convertEmptyValues !== false ) docClient . options ! . convertEmptyValues = true ; this . _docClient = docClient ; } else { error ( 'Invalid DocumentClient' ) ; } } param { Entity | Entity [ ] } Entity addEntity ( entity ) { let entities = Array . isArray ( entity ) ? entity : [ entity ] ; for ( let i in entities ) { let entity = entities [ i ] ; if ( entity instanceof Entity ) { if ( this . _entities && this . _entities . includes ( entity . name ) ) { error ( `Entity name '${ entity . name }' already exists` ) ; } const reservedWords = Object . getOwnPropertyNames ( this ) . concat ( Object . getOwnPropertyNames ( Object . getPrototypeOf ( this ) ) ) ; if ( reservedWords . includes ( entity . name ) ) { error ( `'${ entity . name }' is a reserved word and cannot be used to name an Entity` ) ; } if ( ! this . Table . sortKey && entity . schema . keys . sortKey ) { error ( `${ entity . name } entity contains a sortKey, but the Table does not` ) ; } else if ( this . Table . sortKey && ! entity . schema . keys . sortKey ) { error ( `${ entity . name } entity does not have a sortKey defined` ) ; } for ( const key in entity . schema . keys ) { const attr = entity . schema . keys [ key ] ; switch ( key ) { case 'partitionKey' : case 'sortKey' : if ( attr !== this . Table [ key ] && this . Table [ key ] ) { if ( ! entity . schema . attributes [ this . Table [ key ] ! ] ) { entity . schema . attributes [ this . Table [ key ] ! ] = Object . assign ( { } , entity . schema . attributes [ attr ] , { alias : attr } ) ; entity . schema . attributes [ attr ] . map = this . Table [ key ] ; } else { error ( `The Table's ${ key } name (${ this . Table [ key ] }) conflicts with an Entity attribute name` ) ; } } break ; default : if ( ! this . Table . indexes [ key ] ) error ( `'${ key }' is not a valid secondary index name` ) ; for ( const keyType in attr ) { if ( ! this . Table . indexes [ key ] [ keyType ] ) error ( `${ entity . name } contains a ${ keyType }, but it is not used by ${ key }` ) ; if ( attr [ keyType ] !== this . Table . indexes [ key ] [ keyType ] ) { if ( ! entity . schema . attributes [ this . Table . indexes [ key ] [ keyType ] ] ) { if ( entity . schema . attributes [ attr [ keyType ] ] . map && entity . schema . attributes [ attr [ keyType ] ] . map !== this . Table . indexes [ key ] [ keyType ] ) error ( `${ key }'s ${ keyType } cannot map to the '${ attr [ keyType ] }' alias because it is already mapped to another table attribute` ) ; entity . schema . attributes [ this . Table . indexes [ key ] [ keyType ] ] = Object . assign ( { } , entity . schema . attributes [ attr [ keyType ] ] , { alias : attr [ keyType ] } ) ; entity . schema . attributes [ attr [ keyType ] ] . map = this . Table . indexes [ key ] [ keyType ] ; } else { const config = entity . schema . attributes [ this . Table . indexes [ key ] [ keyType ] ] ; if ( ( ! config . partitionKey && ! config . sortKey ) || ( config . partitionKey && ! config . partitionKey . includes ( key ) ) || ( config . sortKey && ! config . sortKey . includes ( key ) ) ) { error ( `${ key }'s ${ keyType } name (${ this . Table . indexes [ key ] [ keyType ] }) conflicts with another Entity attribute name` ) ; } } } } if ( this . Table . indexes [ key ] . partitionKey && this . Table . indexes [ key ] . sortKey && ( ! entity . schema . attributes [ this . Table . indexes [ key ] . partitionKey ! ] || ! entity . schema . attributes [ this . Table . indexes [ key ] . sortKey ! ] ) ) { error ( `${ key } requires mappings for both the partitionKey and the sortKey` ) ; } break ; } } for ( let attr in entity . schema . attributes ) { if ( this . Table . entityField && ( attr === this . Table . entityField || attr === entity . _etAlias ) ) { error ( `Attribute or alias '${ attr }' conflicts with the table's 'entityField' mapping or entity alias` ) ; } else if ( this . Table . attributes [ attr ] ) { if ( this . Table . attributes [ attr ] . type && this . Table . attributes [ attr ] . type !== entity . schema . attributes [ attr ] . type ) error ( `${ entity . name } attribute type for '${ attr }' (${ entity . schema . attributes [ attr ] . type }) does not match table's type (${ this . Table . attributes [ attr ] . type })` ) ; this . Table . attributes [ attr ] . mappings [ entity . name ] = Object . assign ( { [ entity . schema . attributes [ attr ] . alias || attr ] : entity . schema . attributes [ attr ] . type } , entity . schema . attributes [ attr ] . type === 'set' ? { _setType : entity . schema . attributes [ attr ] . setType } : { } ) ; } else if ( ! entity . schema . attributes [ attr ] . map ) { this . Table . attributes [ attr ] = Object . assign ( { mappings : { [ entity . name ] : Object . assign ( { [ entity . schema . attributes [ attr ] . alias || attr ] : entity . schema . attributes [ attr ] . type } , entity . schema . attributes [ attr ] . type === 'set' ? { _setType : entity . schema . attributes [ attr ] . setType } : { } ) } } , entity . schema . attributes [ attr ] . partitionKey || entity . schema . attributes [ attr ] . sortKey ? { type : entity . schema . attributes [ attr ] . type } : null ) ; } } this . _entities . push ( entity . name ) ; this [ entity . name ] = entity ; entity . table = this ; } else { error ( 'Invalid Entity' ) ; } } } set entities ( entity ) { this . addEntity ( entity ) ; } get entities ( ) { return this . _entities ; } async query ( pk , options = { } , params = { } ) { const { payload , EntityProjections , TableProjections } = this . queryParams ( pk , options , params , true ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient ! . query ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , { Items : result . Items && result . Items . map ( item => { if ( this [ item [ String ( this . Table . entityField ) ] ] ) { return this [ item [ String ( this . Table . entityField ) ] ] . parse ( item , EntityProjections [ item [ String ( this . Table . entityField ) ] ] ? EntityProjections [ item [ String ( this . Table . entityField ) ] ] : TableProjections ? TableProjections : [ ] ) ; } else { return item ; } } ) } , result . LastEvaluatedKey ? { next : ( ) => { return this . query ( pk , Object . assign ( options , { startKey : result . LastEvaluatedKey } ) , params ) ; } } : null ) ; } else { return result ; } } else { return payload ; } } queryParams ( pk , options = { } , params = { } , projections = false ) { const { index , limit , reverse , consistent , capacity , select , eq , lt , lte , gt , gte , between , beginsWith , filters , attributes , startKey , entity , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid query options: ${ args . join ( ', ' ) }` ) ; if ( ( typeof pk !== 'string' && typeof pk !== 'number' ) || ( typeof pk === 'string' && pk . trim ( ) . length === 0 ) ) error ( `Query requires a string, number or binary 'partitionKey' as its first parameter` ) ; if ( index !== undefined && ! this . Table . indexes [ index ] ) error ( `'${ index }' is not a valid index name` ) ; if ( limit !== undefined && ( ! Number . isInteger ( limit ) || limit < 0 ) ) error ( `'limit' must be a positive integer` ) ; if ( reverse !== undefined && typeof reverse !== 'boolean' ) error ( `'reverse' requires a boolean` ) ; if ( consistent !== undefined && typeof consistent !== 'boolean' ) error ( `'consistent' requires a boolean` ) ; if ( select !== undefined && ( typeof select !== 'string' || ! [ 'ALL_ATTRIBUTES' , 'ALL_PROJECTED_ATTRIBUTES' , 'SPECIFIC_ATTRIBUTES' , 'COUNT' ] . includes ( select . toUpperCase ( ) ) ) ) error ( `'select' must be one of 'ALL_ATTRIBUTES', 'ALL_PROJECTED_ATTRIBUTES', 'SPECIFIC_ATTRIBUTES', OR 'COUNT'` ) ; if ( entity !== undefined && ( typeof entity !== 'string' || ! ( entity in this ) ) ) error ( `'entity' must be a string and a valid table Entity name` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( startKey && ( typeof startKey !== 'object' || Array . isArray ( startKey ) ) ) error ( `'startKey' requires a valid object` ) ; let ExpressionAttributeNames = { '#pk' : ( index && this . Table . indexes [ index ] . partitionKey ) || this . Table . partitionKey } ; let ExpressionAttributeValues = { ':pk' : pk } ; let KeyConditionExpression = '#pk = :pk' ; let FilterExpression ; let ProjectionExpression ; let EntityProjections = { } ; let TableProjections ; let operator , value , f = '' ; if ( eq ) { value = eq ; f = 'eq' ; operator = '=' ; } if ( lt ) { value = value ? conditonError ( f ) : lt ; f = 'lt' ; operator = '<' ; } if ( lte ) { value = value ? conditonError ( f ) : lte ; f = 'lte' ; operator = '<=' ; } if ( gt ) { value = value ? conditonError ( f ) : gt ; f = 'gt' ; operator = '>' ; } if ( gte ) { value = value ? conditonError ( f ) : gte ; f = 'gte' ; operator = '>=' ; } if ( beginsWith ) { value = value ? conditonError ( f ) : beginsWith ; f = 'beginsWith' ; operator = 'BEGINS_WITH' ; } if ( between ) { value = value ? conditonError ( f ) : between ; f = 'between' ; operator = 'BETWEEN' ; } if ( operator ) { const sk = index ? ( this . Table . indexes [ index ] . sortKey ? ( this . Table . attributes [ this . Table . indexes [ index ] . sortKey ! ] || { type : 'string' } ) : error ( `Conditional expressions require the index to have a sortKey` ) ) : this . Table . sortKey ? this . Table . attributes [ this . Table . sortKey ] : error ( `Conditional expressions require the table to have a sortKey` ) ; const validateType = validateTypes ( this . DocumentClient ! ) ; ExpressionAttributeNames [ '#sk' ] = ( index && this . Table . indexes [ index ] . sortKey ) || this . Table . sortKey ; if ( operator === 'BETWEEN' ) { if ( ! Array . isArray ( value ) || value . length !== 2 ) error ( `'between' conditions requires an array with two values.` ) ; ExpressionAttributeValues [ ':sk0' ] = validateType ( sk , f + '[0]' , value [ 0 ] ) ; ExpressionAttributeValues [ ':sk1' ] = validateType ( sk , f + '[1]' , value [ 1 ] ) ; KeyConditionExpression += ' and #sk between :sk0 and :sk1' ; } else { ExpressionAttributeValues [ ':sk' ] = validateType ( sk , f , value ) ; if ( operator === 'BEGINS_WITH' ) { KeyConditionExpression += ' and begins_with(#sk,:sk)' ; } else { KeyConditionExpression += ` and #sk ${ operator } :sk` ; } } } if ( filters ) { const { expression , names , values } = parseFilters ( filters , this , entity ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = Object . assign ( ExpressionAttributeNames , names ) ; ExpressionAttributeValues = Object . assign ( ExpressionAttributeValues , values ) ; FilterExpression = expression ; } } if ( attributes ) { const { names , projections , entities , tableAttrs } = parseProjections ( attributes , this , entity ! , true ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = Object . assign ( ExpressionAttributeNames , names ) ; ProjectionExpression = projections ; EntityProjections = entities ; TableProjections = tableAttrs ; } } const payload = Object . assign ( { TableName : this . name , KeyConditionExpression , ExpressionAttributeNames , ExpressionAttributeValues } , FilterExpression ? { FilterExpression } : null , ProjectionExpression ? { ProjectionExpression } : null , index ? { IndexName : index } : null , limit ? { Limit : String ( limit ) } : null , reverse ? { ScanIndexForward : ! reverse } : null , consistent ? { ConsistentRead : consistent } : null , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , select ? { Select : select . toUpperCase ( ) } : null , startKey ? { ExclusiveStartKey : startKey } : null , typeof params === 'object' ? params : null ) ; return projections ? { payload , EntityProjections , TableProjections } : payload ; } async scan ( options = { } , params = { } ) { const { payload , EntityProjections , TableProjections } = this . scanParams ( options , params , true ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient ! . scan ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , { Items : result . Items && result . Items . map ( item => { if ( this [ item [ String ( this . Table . entityField ) ] ] ) { return this [ item [ String ( this . Table . entityField ) ] ] . parse ( item , EntityProjections [ item [ String ( this . Table . entityField ) ] ] ? EntityProjections [ item [ String ( this . Table . entityField ) ] ] : TableProjections ? TableProjections : [ ] ) ; } else { return item ; } } ) } , result . LastEvaluatedKey ? { next : ( ) => { return this . scan ( Object . assign ( options , { startKey : result . LastEvaluatedKey } ) , params ) ; } } : null ) ; } else { return result ; } } else { return payload ; } } scanParams ( options = { } , params = { } , meta = false ) { const { index , limit , consistent , capacity , select , filters , attributes , segments , segment , startKey , entity , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid scan options: ${ args . join ( ', ' ) }` ) ; if ( index !== undefined && ! this . Table . indexes [ index ] ) error ( `'${ index }' is not a valid index name` ) ; if ( limit !== undefined && ( ! Number . isInteger ( limit ) || limit < 0 ) ) error ( `'limit' must be a positive integer` ) ; if ( consistent !== undefined && typeof consistent !== 'boolean' ) error ( `'consistent' requires a boolean` ) ; if ( select !== undefined && ( typeof select !== 'string' || ! [ 'ALL_ATTRIBUTES' , 'ALL_PROJECTED_ATTRIBUTES' , 'SPECIFIC_ATTRIBUTES' , 'COUNT' ] . includes ( select . toUpperCase ( ) ) ) ) error ( `'select' must be one of 'ALL_ATTRIBUTES', 'ALL_PROJECTED_ATTRIBUTES', 'SPECIFIC_ATTRIBUTES', OR 'COUNT'` ) ; if ( entity !== undefined && ( typeof entity !== 'string' || ! ( entity in this ) ) ) error ( `'entity' must be a string and a valid table Entity name` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( startKey && ( typeof startKey !== 'object' || Array . isArray ( startKey ) ) ) error ( `'startKey' requires a valid object` ) ; if ( segments !== undefined && ( ! Number . isInteger ( segments ) || segments < 1 ) ) error ( `'segments' must be an integer greater than 1` ) ; if ( segment !== undefined && ( ! Number . isInteger ( segment ) || segment < 0 || segment >= segments ! ) ) error ( `'segment' must be an integer greater than or equal to 0 and less than the total number of segments` ) ; if ( ( segments !== undefined && segment === undefined ) || ( segments === undefined && segment !== undefined ) ) error ( `Both 'segments' and 'segment' must be provided` ) ; let ExpressionAttributeNames = { } ; let ExpressionAttributeValues = { } ; let FilterExpression ; let ProjectionExpression ; let EntityProjections = { } ; let TableProjections ; if ( filters ) { const { expression , names , values } = parseFilters ( filters , this , entity ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = Object . assign ( ExpressionAttributeNames , names ) ; ExpressionAttributeValues = Object . assign ( ExpressionAttributeValues , values ) ; FilterExpression = expression ; } } if ( attributes ) { const { names , projections , entities , tableAttrs } = parseProjections ( attributes , this , entity ! , true ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = Object . assign ( ExpressionAttributeNames , names ) ; ProjectionExpression = projections ; EntityProjections = entities ; TableProjections = tableAttrs ; } } const payload = Object . assign ( { TableName : this . name , } , Object . keys ( ExpressionAttributeNames ) . length ? { ExpressionAttributeNames } : null , Object . keys ( ExpressionAttributeValues ) . length ? { ExpressionAttributeValues } : null , FilterExpression ? { FilterExpression } : null , ProjectionExpression ? { ProjectionExpression } : null , index ? { IndexName : index } : null , segments ? { TotalSegments : segments } : null , Number . isInteger ( segment ) ? { Segment : segment } : null , limit ? { Limit : String ( limit ) } : null , consistent ? { ConsistentRead : consistent } : null , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , select ? { Select : select . toUpperCase ( ) } : null , startKey ? { ExclusiveStartKey : startKey } : null , typeof params === 'object' ? params : null ) ; return meta ? { payload , EntityProjections , TableProjections } : payload ; } async batchGet ( items , options = { } , params = { } ) { const { payload , Tables , EntityProjections , TableProjections } = this . batchGetParams ( items , options , params , true ) as batchGetParamsMeta ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient ! . batchGet ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return this . parseBatchGetResponse ( result , Tables , EntityProjections , TableProjections , options ) ; } else { return result ; } } else { return payload ; } } parseBatchGetResponse ( result , Tables , EntityProjections , TableProjections , options = { } ) { return Object . assign ( result , result . Responses ? { Responses : Object . keys ( result . Responses ) . reduce ( ( acc , table ) => { return Object . assign ( acc , { [ ( Tables [ table ] && Tables [ table ] . alias ) || table ] : result . Responses [ table ] . map ( ( item ) => { if ( Tables [ table ] && Tables [ table ] [ item [ String ( Tables [ table ] . Table . entityField ) ] ] ) { return Tables [ table ] [ item [ String ( Tables [ table ] . Table . entityField ) ] ] . parse ( item , EntityProjections [ table ] && EntityProjections [ table ] [ item [ String ( Tables [ table ] . Table . entityField ) ] ] ? EntityProjections [ table ] [ item [ String ( Tables [ table ] . Table . entityField ) ] ] : TableProjections [ table ] ? TableProjections [ table ] : [ ] ) ; } else { return item ; } } ) } ) ; } , { } ) } : null , result . UnprocessedKeys && Object . keys ( result . UnprocessedKeys ) . length > 0 ? { next : async ( ) : Promise < any > => { const nextResult = await this . DocumentClient ! . batchGet ( Object . assign ( { RequestItems : result . UnprocessedKeys } , options . capacity ? { ReturnConsumedCapacity : options . capacity . toUpperCase ( ) } : null ) ) . promise ( ) ; return this . parseBatchGetResponse ( nextResult , Tables , EntityProjections , TableProjections , options ) ; } } : { next : ( ) => false } ) ; } batchGetParams ( _items , options = { } , params = { } , meta = false ) { let items = Array . isArray ( _items ) ? _items : [ _items ] ; if ( items . length === 0 ) error ( `No items supplied` ) ; const { capacity , consistent , attributes , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid batchGet options: ${ args . join ( ', ' ) }` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; let RequestItems = { } ; let Tables = { } ; let TableAliases = { } ; let EntityProjections = { } ; let TableProjections = { } ; for ( const i in items ) { const item = items [ i ] ; if ( item && item . Table && item . Table . Table && item . Key && typeof item . Key === 'object' && ! Array . isArray ( item . Key ) ) { const table = item . Table . name ; if ( ! RequestItems [ table ] ) { RequestItems [ table ] = { Keys : [ ] } ; Tables [ table ] = item . Table ; if ( item . Table . alias ) TableAliases [ item . Table . alias ] = table ; } RequestItems [ table ] . Keys . push ( item . Key ) ; } else { error ( `Item references must contain a valid Table object and Key` ) ; } } if ( consistent ) { if ( consistent === true ) { for ( const tbl in RequestItems ) RequestItems [ tbl ] . ConsistentRead = true ; } else if ( typeof consistent === 'object' && ! Array . isArray ( consistent ) ) { for ( const tbl in consistent as Object ) { const tbl_name = TableAliases [ tbl ] || tbl ; if ( RequestItems [ tbl_name ] ) { if ( typeof consistent [ tbl ] === 'boolean' ) { RequestItems [ tbl_name ] . ConsistentRead = consistent [ tbl ] ; } else { error ( `'consistent' values must be booleans (${ tbl })` ) ; } } else { error ( `There are no items for the table or table alias: ${ tbl }` ) ; } } } else { error ( `'consistent' must be a boolean or an map of table names` ) ; } } if ( attributes ) { let attrs = attributes ; if ( Array . isArray ( attributes ) ) { if ( Object . keys ( RequestItems ) . length === 1 ) { attrs = { [ Object . keys ( RequestItems ) [ 0 ] ] : attributes } ; } else { error ( `'attributes' must use a table map when requesting items from multiple tables` ) ; } } for ( const tbl in attrs as ProjectionAttributesTable ) { const tbl_name = TableAliases [ tbl ] || tbl ; if ( Tables [ tbl_name ] ) { const { names , projections , entities , tableAttrs } = parseProjections ( ( attrs as ProjectionAttributesTable ) [ tbl ] , Tables [ tbl_name ] , null , true ) ; RequestItems [ tbl_name ] . ExpressionAttributeNames = names ; RequestItems [ tbl_name ] . ProjectionExpression = projections ; EntityProjections [ tbl_name ] = entities ; TableProjections [ tbl_name ] = tableAttrs ; } else { error ( `There are no items for the table: ${ tbl }` ) ; } } } const payload = Object . assign ( { RequestItems } , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , typeof params === 'object' ? params : null ) ; return meta ? { payload , Tables , EntityProjections , TableProjections } : payload ; } async batchWrite ( items , options = { } , params = { } ) { const payload = this . batchWriteParams ( items , options , params ) as DocumentClient . BatchWriteItemInput ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient ! . batchWrite ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return this . parseBatchWriteResponse ( result , options ) ; } else { return result ; } } else { return payload ; } } private parseBatchWriteResponse ( result , options = { } ) { return Object . assign ( result , result . UnprocessedItems && Object . keys ( result . UnprocessedItems ) . length > 0 ? { next : async ( ) => { const nextResult = await this . DocumentClient ! . batchWrite ( Object . assign ( { RequestItems : result . UnprocessedItems } , options . capacity ? { ReturnConsumedCapacity : options . capacity . toUpperCase ( ) } : null , options . metrics ? { ReturnItemCollectionMetrics : options . metrics . toUpperCase ( ) } : null ) ) . promise ( ) ; return this . parseBatchWriteResponse ( nextResult , options ) ; } } : { next : ( ) => false } ) ; } param { object } items param { object } options param { object } params param { boolean } meta batchWriteParams ( _items , options = { } , params = { } , meta = false ) { let items = ( Array . isArray ( _items ) ? _items : [ _items ] ) . filter ( x => x ) ; if ( items . length === 0 ) error ( `No items supplied` ) ; const { capacity , metrics , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid batchWrite options: ${ args . join ( ', ' ) }` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( metrics !== undefined && ( typeof metrics !== 'string' || ! [ 'NONE' , 'SIZE' ] . includes ( metrics . toUpperCase ( ) ) ) ) error ( `'metrics' must be one of 'NONE' OR 'SIZE'` ) ; const RequestItems = { } ; for ( const i in items ) { const item = items [ i ] ; const table = Object . keys ( item ) [ 0 ] ; if ( ! RequestItems [ table ] ) RequestItems [ table ] = [ ] ; RequestItems [ table ] . push ( item [ table ] ) ; } const payload = Object . assign ( { RequestItems } , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , metrics ? { ReturnItemCollectionMetrics : metrics . toUpperCase ( ) } : null , typeof params === 'object' ? params : null ) ; const Tables = { } ; return meta ? { payload , Tables } : payload ; } param { object } items param { object } options async transactGet ( items = [ ] , options = { } , params = { } ) { const { payload , Entities } = this . transactGetParams ( items , options , true ) as transactGetParamsMeta ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient ! . transactGet ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , result . Responses ? { Responses : result . Responses ! . map ( ( res , i ) => { if ( res . Item ) { return { Item : Entities [ i ] . parse ? Entities [ i ] . parse ( res . Item ) : res . Item } ; } else { return { } ; } } ) } : null ) as DocumentClient . TransactGetItemsOutput ; } else { return result as DocumentClient . TransactGetItemsOutput ; } } else { return payload as DocumentClient . TransactGetItemsInput ; } } param { object } items param { object } options transactGetParams ( _items , options ? , meta ? ) ; transactGetParams ( _items , options , meta ) ; transactGetParams ( _items , options = { } , meta = false ) { let items = Array . isArray ( _items ) ? _items : _items ? [ _items ] : [ ] ; if ( items . length === 0 ) error ( `No items supplied` ) ; const { capacity , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid transactGet options: ${ args . join ( ', ' ) }` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; let Entities = [ ] ; const payload = Object . assign ( { TransactItems : items . map ( item => { let { Entity , ... _item } = item ; Entities . push ( Entity ) ; if ( ! ( 'Get' in _item ) || Object . keys ( _item ) . length > 1 ) error ( `Invalid transaction item. Use the 'getTransaction' method on an entity.` ) ; return _item ; } ) } , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null ) ; return ( meta ) ? { Entities , payload } : payload ; } param { object } items param { object } options async transactWrite ( items , options = { } , params = { } ) { const payload = this . transactWriteParams ( items , options ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient ! . transactWrite ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return result as DocumentClient . TransactWriteItemsOutput ; } else { return result as DocumentClient . TransactWriteItemsOutput ; } } else { return payload as DocumentClient . TransactWriteItemsInput ; } } param { object } items param { object } options transactWriteParams ( _items , options = { } ) { let items = Array . isArray ( _items ) ? _items : _items ? [ _items ] : [ ] ; if ( items . length === 0 ) error ( `No items supplied` ) ; const { capacity , metrics , token , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid transactWrite options: ${ args . join ( ', ' ) }` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( metrics !== undefined && ( typeof metrics !== 'string' || ! [ 'NONE' , 'SIZE' ] . includes ( metrics . toUpperCase ( ) ) ) ) error ( `'metrics' must be one of 'NONE' OR 'SIZE'` ) ; if ( token !== undefined && ( typeof token !== 'string' || token . trim ( ) . length === 0 || token . trim ( ) . length > 36 ) ) error ( `'token' must be a string up to 36 characters long ` ) ; const payload = Object . assign ( { TransactItems : items . map ( item => { if ( ( ! ( 'ConditionCheck' in item ) && ! ( 'Delete' in item ) && ! ( 'Put' in item ) && ! ( 'Update' in item ) ) || Object . keys ( item ) . length > 1 ) error ( `Invalid transaction item. Use the 'putTransaction', 'updateTransaction', 'deleteTransaction', or 'conditionCheck' methods on an entity.` ) ; return item ; } ) } , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , metrics ? { ReturnItemCollectionMetrics : metrics . toUpperCase ( ) } : null , token ? { ClientRequestToken : token . trim ( ) } : null ) ; return payload ; } async parse ( entity , input , include = [ ] ) { if ( ! this [ entity ] ) error ( `'${ entity }' is not a valid Entity` ) ; return this [ entity ] . parse ( input , include ) ; } async get ( entity , item = { } , options = { } , params = { } ) { if ( ! this [ entity ] ) error ( `'${ entity }' is not a valid Entity` ) ; return this [ entity ] . get ( item , options , params ) ; } async delete ( entity , item = { } , options = { } , params = { } ) { if ( ! this [ entity ] ) error ( `'${ entity }' is not a valid Entity` ) ; return this [ entity ] . delete ( item , options , params ) ; } async update ( entity , item = { } , options = { } , params = { } ) { if ( ! this [ entity ] ) error ( `'${ entity }' is not a valid Entity` ) ; return this [ entity ] . update ( item , options , params ) ; } async put ( entity , item = { } , options = { } , params = { } ) { if ( ! this [ entity ] ) error ( `'${ entity }' is not a valid Entity` ) ; return this [ entity ] . put ( item , options , params ) ; } } export default Table ;