import get from 'lodash/get' ; import isISOString from 'validator/lib/isISO8601' ; import { Context } from '../context' ; import { CollectionNotFoundException , InvalidIdException , InvalidIndexedFieldValueException , InvalidParentIdException , } from './exceptions' ; import { DocumentWithId , WrappedDocument } from './common' ; import newId from './new_id' ; import { KeyPath , describeKeyPath , AccessPatternOptions , } from './access_pattern' ; import { CollectionDefinition , ChildCollectionDefinition , RootCollectionDefinition , } from './collection_definition' ; internal export const SEPARATOR = '|-|' ; internal export type IndexedValue = string | undefined ; internal export const assemblePrimaryKeyValue = ( collectionName , _id , separator = SEPARATOR ) : string => `${ collectionName }${ separator }${ _id }` ; internal export const assembleIndexedValue = ( keyType , collectionName , values , separator = SEPARATOR ) : IndexedValue => { if ( values . length === 0 ) { return collectionName ; } else if ( values . every ( ( value ) => typeof value === 'undefined' ) ) { return keyType === 'partition' ? collectionName : undefined ; } return `${ collectionName }${ separator }${ values . map ( ( x ) => ( typeof x === 'string' ? x : '' ) ) . join ( separator ) }` ; } ; internal export const getRootCollection = ( context , collectionName ) : RootCollectionDefinition => { const c = context . rootDefinitions . get ( collectionName ) ; if ( ! c ) throw new CollectionNotFoundException ( collectionName ) ; return c ; } ; internal export const getChildCollection = ( context , collectionName ) : ChildCollectionDefinition => { const c = context . childDefinitions . get ( collectionName ) ; if ( ! c ) throw new CollectionNotFoundException ( collectionName ) ; return c ; } ; internal export const getCollection = ( context , collectionName ) : CollectionDefinition => { const c = context . definitions . get ( collectionName ) ; if ( ! c ) throw new CollectionNotFoundException ( collectionName ) ; return c ; } ; internal export const transformTTLValue = ( ttlValue ) : number | undefined => { let transformedValue ; if ( typeof ttlValue === 'object' && ttlValue instanceof Date ) { transformedValue = Math . ceil ( ttlValue . getTime ( ) / 1000 ) ; } else if ( typeof ttlValue === 'number' ) { transformedValue = Math . ceil ( ttlValue / 1000 ) ; } else if ( typeof ttlValue === 'string' && isISOString ( ttlValue ) ) { transformedValue = Math . ceil ( new Date ( ttlValue ) . getTime ( ) / 1000 ) ; } return transformedValue ; } ; internal export const extractTransformedTTLValue = < DocumentType extends DocumentWithId > ( value , valuePath ) : number | undefined => { const ttlValue = get ( value , valuePath ) ; return transformTTLValue ( ttlValue ) ; } ; internal export const constructKeyValue = < DocumentType extends DocumentWithId > ( type , collectionName , separator , valuePaths , options , value ) : string | number | undefined => { if ( type === 'ttl' ) { return extractTransformedTTLValue ( value , valuePaths [ 0 ] ) ; } const values = valuePaths . map ( ( valuePath ) => { const extractedValue = get ( value , valuePath ) ; if ( typeof extractedValue !== 'undefined' && typeof extractedValue !== 'string' ) { throw new InvalidIndexedFieldValueException ( `Indexed value at path ${ describeKeyPath ( valuePath ) } was not a string for collection ${ collectionName }` , { keyPath : valuePath , collection : collectionName } ) ; } const transformedValue = options . stringNormalizer && extractedValue ? options . stringNormalizer ( valuePath , extractedValue ) : extractedValue ; return transformedValue ; } ) ; return assembleIndexedValue ( type , collectionName , values , separator ) ; } ; internal export const toWrapped = < DocumentType extends DocumentWithId > ( collection , value ) : WrappedDocument < DocumentType > => { let updatedValue ; if ( typeof value . _id !== 'undefined' ) { if ( typeof value . _id !== 'string' ) { throw new InvalidIdException ( value . _id ) ; } updatedValue = value as DocumentType ; } else { const _id = collection . idGenerator ? collection . idGenerator ( ) : newId ( ) ; updatedValue = { ... value , _id } as DocumentType ; } const extractedKeys = collection . wrapperExtractKeys . map ( ( { type , key , valuePaths , options } ) => { const keyValue = constructKeyValue ( type , collection . name , collection . layout . indexKeySeparator ?? SEPARATOR , valuePaths , options , updatedValue ) ; if ( typeof keyValue !== undefined ) { return { [ key ] : keyValue } ; } } ) . filter ( ( x ) => typeof x !== 'undefined' ) ; let partitionKeyValue ; let sortKeyValue ; if ( collection . type === 'child' ) { const parentId = get ( value , collection . foreignKeyPath ) ; if ( typeof parentId !== 'string' ) { throw new InvalidParentIdException ( parentId , collection . name , collection . parentCollectionName ) ; } partitionKeyValue = assemblePrimaryKeyValue ( collection . parentCollectionName , parentId , collection . layout . indexKeySeparator ) ; sortKeyValue = assemblePrimaryKeyValue ( collection . name , updatedValue . _id , collection . layout . indexKeySeparator ) ; } else { partitionKeyValue = sortKeyValue = assemblePrimaryKeyValue ( collection . name , updatedValue . _id , collection . layout . indexKeySeparator ) ; } const wrapped = Object . assign ( { [ collection . layout . primaryKey . partitionKey ] : partitionKeyValue , [ collection . layout . primaryKey . sortKey ] : sortKeyValue , value : updatedValue , type : collection . name , } , ... extractedKeys ) ; return wrapped ; } ; internal export const unwrap = < DocumentType extends DocumentWithId > ( document ) : DocumentType => { return document . value ; } ; internal export const invertMap = ( map ) : { [ key ] : string ; } => Object . assign ( { } , ... Array . from ( map . entries ( ) , ( [ k , v ] ) => ( { [ v ] : k } ) ) ) ; internal export const isSubsetOfKeyPath = ( mainPath , subsetPath ) : boolean => subsetPath . every ( ( key , index ) => mainPath [ index ] === key ) ; internal export const findMatchingPath = ( keyPaths , path ) : KeyPath | undefined => { for ( const keyPath of keyPaths ) { if ( isSubsetOfKeyPath ( path , keyPath ) ) { return keyPath ; } } } ;