import md5 from 'md5' ; import { invariant } from 'outvariant' ; import { StrictEventEmitter } from 'strict-event-emitter' ; import { Entity , ENTITY_TYPE , KeyType , ModelDictionary , PrimaryKeyType , PRIMARY_KEY , } from '../glossary' ; export const SERIALIZED_INTERNAL_PROPERTIES_KEY = 'SERIALIZED_INTERNAL_PROPERTIES' ; type Models < Dictionary extends ModelDictionary > = Record < keyof Dictionary , Map < PrimaryKeyType , Entity < Dictionary , any > > > ; export interface SerializedInternalEntityProperties { entityType : string ; primaryKey : PrimaryKeyType ; } export interface SerializedEntity extends Entity < any , any > { [ SERIALIZED_INTERNAL_PROPERTIES_KEY ] : SerializedInternalEntityProperties ; } export type DatabaseMethodToEventFn < ArgsType extends unknown [ ] > = ( sourceId : string , args : ArgsType ) => void ; export interface DatabaseEventsMap { create : DatabaseMethodToEventFn < [ modelName : KeyType , entity : SerializedEntity , customPrimaryKey ? : PrimaryKeyType ] > ; update : DatabaseMethodToEventFn < [ modelName : KeyType , prevEntity : SerializedEntity , nextEntity : SerializedEntity ] > ; delete : DatabaseMethodToEventFn < [ modelName : KeyType , primaryKey : PrimaryKeyType ] > ; } let callOrder = 0 ; export class Database < Dictionary extends ModelDictionary > { public id : string ; public events : StrictEventEmitter < DatabaseEventsMap > ; private models : Models < Dictionary > ; constructor ( dictionary ) { this . events = new StrictEventEmitter ( ) ; this . models = Object . keys ( dictionary ) . reduce < Models < Dictionary > > ( ( acc , modelName ) => { acc [ modelName ] = new Map < string , Entity < Dictionary , string > > ( ) ; return acc ; } , { } as Models < Dictionary > ) ; callOrder ++ ; this . id = this . generateId ( ) ; } /**
     * Generates a unique MD5 hash based on the database
     * module location and invocation order. Used to reproducibly
     * identify a database instance among sibling instances.
     */ private generateId ( ) { const { stack } = new Error ( ) ; const callFrame = stack ?. split ( '\n' ) [ 4 ] ; const salt = `${ callOrder }-${ callFrame ?. trim ( ) }` ; return md5 ( salt ) ; } private serializeEntity ( entity ) { return { ... entity , [ SERIALIZED_INTERNAL_PROPERTIES_KEY ] : { entityType : entity [ ENTITY_TYPE ] , primaryKey : entity [ PRIMARY_KEY ] } } ; } getModel < ModelName extends keyof Dictionary > ( name ) { return this . models [ name ] ; } create < ModelName extends keyof Dictionary > ( modelName , entity , customPrimaryKey ? ) { invariant ( entity [ ENTITY_TYPE ] , 'Failed to create a new "%s" record: provided entity has no type. %j' , modelName , entity ) ; invariant ( entity [ PRIMARY_KEY ] , 'Failed to create a new "%s" record: provided entity has no primary key. %j' , modelName , entity ) ; const primaryKey = customPrimaryKey || ( entity [ entity [ PRIMARY_KEY ] ] as string ) ; this . events . emit ( 'create' , this . id , [ modelName , this . serializeEntity ( entity ) , customPrimaryKey , ] ) ; return this . getModel ( modelName ) . set ( primaryKey , entity ) ; } update < ModelName extends keyof Dictionary > ( modelName , prevEntity , nextEntity ) { const prevPrimaryKey = prevEntity [ prevEntity [ PRIMARY_KEY ] ] as PrimaryKeyType ; const nextPrimaryKey = nextEntity [ prevEntity [ PRIMARY_KEY ] ] as PrimaryKeyType ; if ( nextPrimaryKey !== prevPrimaryKey ) { this . delete ( modelName , prevPrimaryKey ) ; } this . getModel ( modelName ) . set ( nextPrimaryKey , nextEntity ) ; this . events . emit ( 'update' , this . id , [ modelName , this . serializeEntity ( prevEntity ) , this . serializeEntity ( nextEntity ) , ] ) ; } delete < ModelName extends keyof Dictionary > ( modelName , primaryKey ) { this . getModel ( modelName ) . delete ( primaryKey ) ; this . events . emit ( 'delete' , this . id , [ modelName , primaryKey ] ) ; } has < ModelName extends keyof Dictionary > ( modelName , primaryKey ) { return this . getModel ( modelName ) . has ( primaryKey ) ; } count < ModelName extends string > ( modelName ) { return this . getModel ( modelName ) . size ; } listEntities < ModelName extends keyof Dictionary > ( modelName ) { return Array . from ( this . getModel ( modelName ) . values ( ) ) ; } }