import {Collection} from '../collection/Collection' import {uuid4} from './data' import {AsyncLocalStorage} from 'async_hooks' /** * Type structure for a single item in the global registry. */ export type GlobalRegistrant = { key: string | symbol, value: any } export class AccessGlobalRegistryOutsideAsyncLifecycleError extends Error { constructor() { super('Attempted to access global registry outside of an async context.') } } /** * A convenient class to manage global variables. */ export class GlobalRegistry { protected readonly storage: AsyncLocalStorage> = new AsyncLocalStorage>() /** Run a closure with a global registry. */ public run(closure: () => T): T { return this.storage.run(new Collection(), (): T => { this.setGlobal('registry_uuid', uuid4()) return closure() }) } /** * Store the given `value` associated by `key`. * @param key * @param value */ public setGlobal(key: string | symbol, value: unknown): this { const existing = this.getCollection().firstWhere('key', '=', key) if ( existing ) { existing.value = value } else { this.getCollection().push({ key, value, }) } return this } /** * Retrieve the value of the given `key`, if it exists in the store. * @param key */ public getGlobal(key: string | symbol): any { return this.getCollection().firstWhere('key', '=', key)?.value } /** Get the globals collection or throw an error if outside async context. */ protected getCollection(): Collection { const coll = this.storage.getStore() if ( !coll ) { throw new AccessGlobalRegistryOutsideAsyncLifecycleError() } return coll } } /** * Export a singleton global registry for all code to share. * * Exporting an instance here guarantees that all code that import it will get the same instance. */ export const globalRegistry = new GlobalRegistry()