import { Key , Converter , QueryInput } from 'aws-sdk/clients/dynamodb' ; import fromPairs from 'lodash/fromPairs' ; import { WrappedDocument , DocumentWithId } from '../base/common' ; import { Context } from '../context' ; import { unwrap , getCollection , getChildCollection , assemblePrimaryKeyValue , } from '../base/util' ; import { CollectionNotFoundException } from '../base/exceptions' ; import { createNameMapper , createValueMapper } from '../base/mappers' ; import debugDynamo from '../debug/debugDynamo' ; export type FindByIdWithChildrenResult < DocumentType extends DocumentWithId > = { /**
     * The root object.
     *
     * It may be blank if it is not in the current set of
     * results (this depends on how objects are organised
     * on your index, or if the root object exists at all).
     * If your result is paginated (i.e. there is a `nextToken`),
     * it is important to continue calling `findByIdWithChildren`
     * with the previous `nextToken` and keep checking `root`
     * on each set of results.
     */ root ? : DocumentType ; /**
     * The child objects, organised by collection names.
     *
     * Similar to the root object, you must check each page
     * of results for children, which will come back based
     * on index order and scan direction.
     *
     * NOTE: These are typed as DocumentWithId: you will need
     * to typecast each of the child collections based on the name
     * you specify
     */ children : { [ collection ] : DocumentWithId [ ] ; } ; /**
     * The pagination token. If this is not blank, you
     * need to call `findByIdWithChildren` with this
     * value to get the next page of results.
     */ nextToken ? : Key ; } ; param ctx param rootObjectCollectionName param rootObjectId param childCollectionNames param nextToken param options returns export const findByIdWithChildren = async < DocumentType extends DocumentWithId > ( ctx , rootCollectionName , rootObjectId , childCollectionNames ? , nextToken ? , options = { } ) : Promise < FindByIdWithChildrenResult < DocumentType > > => { const collection = getCollection ( ctx , rootCollectionName ) ; let childCollections ; if ( childCollectionNames ) { childCollections = childCollectionNames . map ( ( name ) => getChildCollection ( ctx , name ) ) ; for ( const childCollection of childCollections ) { if ( childCollection . parentCollectionName !== rootCollectionName ) { throw new CollectionNotFoundException ( childCollection . name ) ; } } } else { childCollections = [ ... ctx . childDefinitions . values ( ) ] . filter ( ( collection ) => collection . parentCollectionName === rootCollectionName ) ; } const allChildCollectionNames = childCollections . map ( ( collection ) => collection . name ) ; const allCollectionNames = [ rootCollectionName , ... allChildCollectionNames , ] . sort ( ) ; const firstCollection = allCollectionNames [ 0 ] ; const lastCollection = allCollectionNames [ allCollectionNames . length - 1 ] ; const lastCollectionBound = assemblePrimaryKeyValue ( lastCollection , '\uFFFF' , collection . layout . indexKeySeparator ) ; const nameMapper = createNameMapper ( ) ; const valueMapper = createValueMapper ( ) ; const { partitionKey , sortKey } = collection . layout . primaryKey ; const keyConditionExpression = `${ nameMapper . map ( partitionKey ) } = ${ valueMapper . map ( assemblePrimaryKeyValue ( rootCollectionName , rootObjectId , collection . layout . indexKeySeparator ) ) } ` + `AND ${ nameMapper . map ( sortKey ) } BETWEEN ${ valueMapper . map ( assemblePrimaryKeyValue ( firstCollection , '' , collection . layout . indexKeySeparator ) ) } AND ${ valueMapper . map ( lastCollectionBound ) }` ; const filterExpression = `${ nameMapper . map ( 'type' ) } IN (${ allCollectionNames . map ( ( c ) => valueMapper . map ( c ) ) . join ( ',' ) })` ; const request = { TableName : collection . layout . tableName , KeyConditionExpression : keyConditionExpression , ExpressionAttributeNames : nameMapper . get ( ) , ExpressionAttributeValues : valueMapper . get ( ) , ExclusiveStartKey : nextToken , Limit : options ?. limit , ScanIndexForward : options ?. scanForward ?? true , FilterExpression : filterExpression , } ; debugDynamo ( 'Query' , request ) ; const { Items = [ ] , LastEvaluatedKey } = await ctx . ddb . query ( request ) . promise ( ) ; let root ; const children = fromPairs ( allChildCollectionNames . map ( ( name ) => [ name , [ ] ] ) ) ; for ( const item of Items ) { const attributes = Converter . unmarshall ( item ) as WrappedDocument < any > ; if ( attributes . type === rootCollectionName ) { root = unwrap ( attributes ) as DocumentType ; } else { children [ attributes . type ] = children [ attributes . type ] ?? [ ] ; children [ attributes . type ] . push ( unwrap ( attributes ) ) ; } } return { root , children , nextToken : LastEvaluatedKey , } ; } ;