UNPKG

memory-card

Version:

ES6 Map like Async API, with Swagger API Backend Support.

483 lines (388 loc) 11.2 kB
/* eslint-disable no-use-before-define */ import type { AsyncMapLike } from 'async-map-like' import { log, VERSION, } from './config.js' import { getStorage, StorageBackend, StorageBackendOptions, } from './storage/mod.js' import type { MemoryCardPayload, } from './types.js' export const NAMESPACE_MULTIPLEX_SEPRATOR = '\r' export const NAMESPACE_KEY_SEPRATOR = '\n' const NAMESPACE_MULTIPLEX_SEPRATOR_REGEX = new RegExp(NAMESPACE_MULTIPLEX_SEPRATOR) const NAMESPACE_KEY_SEPRATOR_REGEX = new RegExp(NAMESPACE_KEY_SEPRATOR) export interface MemoryCardOptions { name? : string, storageOptions? : StorageBackendOptions, // ////////// multiplex?: { parent : MemoryCard, name : string, } } export interface MemoryCardJsonObject { payload: MemoryCardPayload, options: MemoryCardOptions, } export class MemoryCard implements AsyncMapLike<any, any> { /** * * * Static * * */ static VERSION = VERSION static fromJSON (textOrObj: string | MemoryCardJsonObject): AsyncMapLike<any, any> { log.verbose('MemoryCard', 'static fromJSON(...)') let jsonObj: MemoryCardJsonObject if (typeof textOrObj === 'string') { jsonObj = JSON.parse(textOrObj) } else { jsonObj = textOrObj } const card = new this(jsonObj.options) card.payload = jsonObj.payload return card } protected static multiplex<T extends typeof MemoryCard> ( this: T, memory : MemoryCard, name : string, ): T['prototype'] { log.verbose('MemoryCard', 'static multiplex(%s, %s)', memory, name) // if (!memory.options) { // throw new Error('can not multiplex a un-named MemoryCard') // } const subMemory = new this({ ...memory.options, multiplex: { name, parent: memory, }, }) return subMemory } /** * * * Instance * * */ name?: string protected parent? : MemoryCard protected payload? : MemoryCardPayload protected storage? : StorageBackend protected multiplexNameList : string[] private options?: MemoryCardOptions constructor ( options?: string | MemoryCardOptions, ) { log.verbose('MemoryCard', 'constructor(%s)', JSON.stringify(options), ) if (typeof options === 'string') { options = { name: options } } this.options = options this.name = options && options.name if (options && options.multiplex) { this.parent = options.multiplex.parent this.payload = this.parent.payload this.multiplexNameList = [ ...this.parent.multiplexNameList, options.multiplex.name, ] } else { // payload should be undefined before load() this.payload = undefined this.multiplexNameList = [] } } toString () { let mpString = '' if (this.multiplexNameList.length > 0) { mpString = this.multiplexNameList .map(mpName => `.multiplex(${mpName})`) .join('') } return `MemoryCard<${this.options?.name ?? ''}>${mpString}` } version (): string { return VERSION } private async getStorage (): Promise<undefined | StorageBackend> { log.verbose('MemoryCard', 'getStorage() for storage type: %s', (this.options && this.options.storageOptions && this.options.storageOptions.type ) || 'N/A', ) if (!this.options) { return } const storage = await getStorage( this.options.name, this.options.storageOptions, ) return storage } async load (): Promise<void> { log.verbose('MemoryCard', 'load() from storage: %s', this.storage || 'N/A') if (this.isMultiplex()) { log.verbose('MemoryCard', 'load() should not be called on a multiplex MemoryCard. NOOP') return } if (this.payload) { throw new Error('memory had already loaded before.') } if (!this.storage) { this.storage = await this.getStorage() } if (this.storage) { this.payload = await this.storage.load() } else { log.verbose('MemoryCard', 'load() no storage') this.payload = {} } } async save (): Promise<void> { log.verbose('MemoryCard', 'save()') if (this.isMultiplex()) { if (!this.parent) { throw new Error('multiplex memory no parent') } return this.parent.save() } log.verbose('MemoryCard', '<%s>%s save() to %s', this.name || '', this.multiplexPath(), this.storage || 'N/A', ) if (!this.payload) { throw new Error('no payload, please call load() first.') } if (!this.storage) { log.verbose('MemoryCard', 'save() no storage, NOOP') return } await this.storage.save(this.payload) } /** * * Multiplexing related functions START * */ protected isMultiplexKey (key: string): boolean { if (NAMESPACE_MULTIPLEX_SEPRATOR_REGEX.test(key) && NAMESPACE_KEY_SEPRATOR_REGEX.test(key) ) { const namespace = this.multiplexNamespace() return key.startsWith(namespace) } return false } protected multiplexNamespace (): string { if (!this.isMultiplex()) { throw new Error('not a multiplex memory') } const namespace = NAMESPACE_MULTIPLEX_SEPRATOR + this.multiplexNameList.join(NAMESPACE_MULTIPLEX_SEPRATOR) return namespace } protected resolveKey (name: string): string { if (this.isMultiplex()) { const namespace = this.multiplexNamespace() return [ namespace, name, ].join(NAMESPACE_KEY_SEPRATOR) } else { return name } } isMultiplex (): boolean { return this.multiplexNameList.length > 0 } protected multiplexPath (): string { return this.multiplexNameList.join('/') } multiplex (name: string): this { log.verbose('MemoryCard', 'multiplex(%s)', name) // FIXME: as any ? return (this.constructor as any).multiplex(this, name) } /** * * Multiplexing related functions END * */ async destroy (): Promise<void> { log.verbose('MemoryCard', 'destroy() storage: %s', this.storage || 'N/A') if (this.isMultiplex()) { throw new Error('can not destroy on a multiplexed memory') } await this.clear() if (this.storage) { await this.storage.destroy() this.storage = undefined } // to prevent to use a destroied card this.payload = undefined } /** * * ES6 Map API (Async Version) * * BEGIN * */ /** * size */ get size (): Promise<number> { log.verbose('MemoryCard', '<%s> size', this.multiplexPath()) if (!this.payload) { throw new Error('no payload, please call load() first.') } let count if (this.isMultiplex()) { count = Object.keys(this.payload) .filter(key => this.isMultiplexKey(key)) .length } else { count = Object.keys(this.payload).length } return Promise.resolve(count) } async get<T = any> (name: string): Promise<undefined | T> { log.verbose('MemoryCard', '<%s> get(%s)', this.multiplexPath(), name) if (!this.payload) { throw new Error('no payload, please call load() first.') } const key = this.resolveKey(name) return this.payload[key] as any } async set<T = any> (name: string, data: T): Promise<this> { log.verbose('MemoryCard', '<%s> set(%s, %s)', this.multiplexPath(), name, data) if (!this.payload) { throw new Error('no payload, please call load() first.') } const key = this.resolveKey(name) this.payload[key] = data as any return this } async * [Symbol.asyncIterator]<T = any> (): AsyncIterableIterator<[string, T]> { log.verbose('MemoryCard', '<%s> *[Symbol.asyncIterator]()', this.multiplexPath()) yield * this.entries() } async * entries<T = any> (): AsyncIterableIterator<[string, T]> { log.verbose('MemoryCard', '<%s> *entries()', this.multiplexPath()) if (!this.payload) { throw new Error('no payload, please call load() first.') } for await (const relativeKey of this.keys()) { const absoluteKey = this.resolveKey(relativeKey) const data: T = this.payload[absoluteKey] as any const pair: [string, T] = [relativeKey, data] yield pair } } async clear (): Promise<void> { log.verbose('MemoryCard', '<%s> clear()', this.multiplexPath()) if (!this.payload) { throw new Error('no payload, please call load() first.') } if (this.isMultiplex()) { for (const key in this.payload) { if (this.isMultiplexKey(key)) { delete this.payload[key] } } } else { this.payload = {} } } async delete (name: string): Promise<boolean> { log.verbose('MemoryCard', '<%s> delete(%s)', this.multiplexPath(), name) if (!this.payload) { throw new Error('no payload, please call load() first.') } const key = this.resolveKey(name) if (key in this.payload) { delete this.payload[key] return true } else { return false } } async has (key: string): Promise<boolean> { log.verbose('MemoryCard', '<%s> has(%s)', this.multiplexPath(), key) if (!this.payload) { throw new Error('no payload, please call load() first.') } const absoluteKey = this.resolveKey(key) return absoluteKey in this.payload } async * keys (): AsyncIterableIterator<string> { log.verbose('MemoryCard', '<%s> keys()', this.multiplexPath()) if (!this.payload) { throw new Error('no payload, please call load() first.') } for (const key of Object.keys(this.payload)) { // console.log('key', key) if (this.isMultiplex()) { if (this.isMultiplexKey(key)) { const namespace = this.multiplexNamespace() // `+1` means there's another NAMESPACE_KEY_SEPRATOR we need to trim const mpKey = key.substr(namespace.length + 1) yield mpKey } continue } yield key } } async * values<T = any> (): AsyncIterableIterator<T> { log.verbose('MemoryCard', '<%s> values()', this.multiplexPath()) if (!this.payload) { throw new Error('no payload, please call load() first.') } for await (const relativeKey of this.keys()) { const absoluteKey = this.resolveKey(relativeKey) yield this.payload[absoluteKey] as any } } async forEach<T> ( callbackfn: ( value: T, key: string, map: any, ) => void, thisArg?: any, ): Promise<void> { for await (const [key, value] of this.entries()) { callbackfn.call(thisArg, value, key, this) } } /** * Huan(202108): FIXME * I totally forget what are the following two methods are... */ [Symbol.iterator] () : AsyncIterableIterator<[string, number]> { return {} as any } get [Symbol.toStringTag] () { return Promise.resolve('MemoryCardStringTag') } /** * * ES6 Map API (Async Version) * * END * */ } export default MemoryCard