import { Store , CreateDocumentParams , ReadDocumentParams , UpdateDocumentParams , DeleteDocumentParams , FindDocumentsParams , CountDocumentsParams , MigrateCollectionParams , MigrateCollectionResult , Document , Expression , Path } from '@layr/store' ; import type { Operator , SortDescriptor } from '@layr/storable' ; import type { NormalizedIdentifierDescriptor } from '@layr/component' ; import pull from 'lodash/pull' ; import get from 'lodash/get' ; import set from 'lodash/set' ; import unset from 'lodash/unset' ; import sortOn from 'sort-on' ; type Collection = Document [ ] ; type CollectionMap = { [ name ] : Collection ; } ; /**
 * *Inherits from [`Store`](https://layrjs.com/docs/v2/reference/store).*
 *
 * A [`Store`](https://layrjs.com/docs/v2/reference/store) that uses the memory to "persist" its registered [storable components](https://layrjs.com/docs/v2/reference/storable#storable-component-class). Since the stored data is wiped off every time the execution environment is restarted, a `MemoryStore` shouldn't be used for a real application.
 *
 * #### Usage
 *
 * Create a `MemoryStore` instance, register some [storable components](https://layrjs.com/docs/v2/reference/storable#storable-component-class) into it, and then use any [`StorableComponent`](https://layrjs.com/docs/v2/reference/storable#storable-component-class)'s method to load, save, delete, or find components from the store.
 *
 * See an example of use in the [`MongoDBStore`](https://layrjs.com/docs/v2/reference/mongodb-store) class.
 */ export class MemoryStore extends Store { param options . initialCollections returns example category constructor ( options = { } ) { const { initialCollections = { } , ... otherOptions } = options ; super ( otherOptions ) ; this . _collections = initialCollections ; } category _collections : CollectionMap ; _getCollection ( name ) { let collection = this . _collections [ name ] ; if ( collection === undefined ) { collection = [ ] ; this . _collections [ name ] = collection ; } return collection ; } async createDocument ( { collectionName , identifierDescriptor , document } ) { const collection = this . _getCollection ( collectionName ) ; const existingDocument = await this . _readDocument ( { collection , identifierDescriptor } ) ; if ( existingDocument !== undefined ) { return false ; } collection . push ( document ) ; return true ; } async readDocument ( { collectionName , identifierDescriptor } ) { const collection = this . _getCollection ( collectionName ) ; const document = await this . _readDocument ( { collection , identifierDescriptor } ) ; return document ; } async _readDocument ( { collection , identifierDescriptor } ) { const [ [ identifierName , identifierValue ] ] = Object . entries ( identifierDescriptor ) ; const document = collection . find ( ( document ) => document [ identifierName ] === identifierValue ) ; return document ; } async updateDocument ( { collectionName , identifierDescriptor , documentPatch } ) { const collection = this . _getCollection ( collectionName ) ; const existingDocument = await this . _readDocument ( { collection , identifierDescriptor } ) ; if ( existingDocument === undefined ) { return false ; } const { $set , $unset } = documentPatch ; if ( $set !== undefined ) { for ( const [ path , value ] of Object . entries ( $set ) ) { set ( existingDocument , path , value ) ; } } if ( $unset !== undefined ) { for ( const [ path , value ] of Object . entries ( $unset ) ) { if ( value ) { unset ( existingDocument , path ) ; } } } return true ; } async deleteDocument ( { collectionName , identifierDescriptor } ) { const collection = this . _getCollection ( collectionName ) ; const document = await this . _readDocument ( { collection , identifierDescriptor } ) ; if ( document === undefined ) { return false ; } pull ( collection , document ) ; return true ; } async findDocuments ( { collectionName , expressions , sort , skip , limit } ) { const collection = this . _getCollection ( collectionName ) ; const documents = await this . _findDocuments ( { collection , expressions , sort , skip , limit } ) ; return documents ; } async _findDocuments ( { collection , expressions , sort , skip , limit } ) { let documents = filterDocuments ( collection , expressions ) ; documents = sortDocuments ( documents , sort ) ; documents = skipDocuments ( documents , skip ) ; documents = limitDocuments ( documents , limit ) ; return documents ; } async countDocuments ( { collectionName , expressions } ) { const collection = this . _getCollection ( collectionName ) ; const documents = await this . _findDocuments ( { collection , expressions } ) ; return documents . length ; } async migrateCollection ( { collectionName } ) { const result = { name : collectionName , createdIndexes : [ ] , droppedIndexes : [ ] } ; return result ; } } function filterDocuments ( documents , expressions ) { if ( expressions . length === 0 ) { return documents ; } return documents . filter ( ( document ) => documentIsMatchingExpressions ( document , expressions ) ) ; } function documentIsMatchingExpressions ( document , expressions ) { for ( const [ path , operator , operand ] of expressions ) { const attributeValue = path !== '' ? get ( document , path ) : document ; if ( evaluateExpression ( attributeValue , operator , operand , { path } ) === false ) { return false ; } } return true ; } function evaluateExpression ( attributeValue , operator , operand , { path } ) { if ( operator === '$equal' ) { return attributeValue ?. valueOf ( ) === operand ?. valueOf ( ) ; } if ( operator === '$notEqual' ) { return attributeValue ?. valueOf ( ) !== operand ?. valueOf ( ) ; } if ( operator === '$greaterThan' ) { return attributeValue > operand ; } if ( operator === '$greaterThanOrEqual' ) { return attributeValue >= operand ; } if ( operator === '$lessThan' ) { return attributeValue < operand ; } if ( operator === '$lessThanOrEqual' ) { return attributeValue <= operand ; } if ( operator === '$in' ) { return operand . includes ( attributeValue ) ; } if ( operator === '$includes' ) { if ( typeof attributeValue !== 'string' ) { return false ; } return attributeValue . includes ( operand ) ; } if ( operator === '$startsWith' ) { if ( typeof attributeValue !== 'string' ) { return false ; } return attributeValue . startsWith ( operand ) ; } if ( operator === '$endsWith' ) { if ( typeof attributeValue !== 'string' ) { return false ; } return attributeValue . endsWith ( operand ) ; } if ( operator === '$matches' ) { if ( typeof attributeValue !== 'string' ) { return false ; } return operand . test ( attributeValue ) ; } if ( operator === '$some' ) { if ( ! Array . isArray ( attributeValue ) ) { return false ; } const subdocuments = attributeValue ; const subexpressions = operand ; return subdocuments . some ( ( subdocument ) => documentIsMatchingExpressions ( subdocument , subexpressions ) ) ; } if ( operator === '$every' ) { if ( ! Array . isArray ( attributeValue ) ) { return false ; } const subdocuments = attributeValue ; const subexpressions = operand ; return subdocuments . every ( ( subdocument ) => documentIsMatchingExpressions ( subdocument , subexpressions ) ) ; } if ( operator === '$length' ) { if ( ! Array . isArray ( attributeValue ) ) { return false ; } return attributeValue . length === operand ; } if ( operator === '$not' ) { const subexpressions = operand ; return ! documentIsMatchingExpressions ( attributeValue , subexpressions ) ; } if ( operator === '$and' ) { const andSubexpressions = operand as any [ ] ; return andSubexpressions . every ( ( subexpressions ) => documentIsMatchingExpressions ( attributeValue , subexpressions ) ) ; } if ( operator === '$or' ) { const orSubexpressions = operand as any [ ] ; return orSubexpressions . some ( ( subexpressions ) => documentIsMatchingExpressions ( attributeValue , subexpressions ) ) ; } if ( operator === '$nor' ) { const norSubexpressions = operand as any [ ] ; return ! norSubexpressions . some ( ( subexpressions ) => documentIsMatchingExpressions ( attributeValue , subexpressions ) ) ; } throw new Error ( `A query contains an operator that is not supported (operator: '${ operator }', path: '${ path }')` ) ; } function sortDocuments ( documents , sort ) { if ( sort === undefined ) { return documents ; } const properties = Object . entries ( sort ) . map ( ( [ name , direction ] ) => { let property = name ; if ( direction . toLowerCase ( ) === 'desc' ) { property = `-${ property }` ; } return property ; } ) ; return sortOn ( documents , properties ) ; } function skipDocuments ( documents , skip ) { if ( skip === undefined ) { return documents ; } return documents . slice ( skip ) ; } function limitDocuments ( documents , limit ) { if ( limit === undefined ) { return documents ; } return documents . slice ( 0 , limit ) ; }