import { ActivateSymbol, DeactivateSymbol, SetStateSymbol } from './const' import type { DisposeFn } from './types' export type OrchModelEventMap = { change: (state: Readonly, oldState: Readonly) => void activate: () => void deactivate: () => void } export type OrchModelEventListenerMap = { [K in keyof OrchModelEventMap]: Set[K]> } export type OrchModelEventListenFnMap = { [K in keyof OrchModelEventMap]: (fn: OrchModelEventMap[K]) => DisposeFn } export type OrchModelStatus = 'initialized' | 'active' | 'inactive' export class OrchModel { #state: State #status: OrchModelStatus readonly #listeners: OrchModelEventListenerMap readonly on: OrchModelEventListenFnMap constructor(state: State) { this.#state = state this.#status = 'initialized' this.#listeners = { change: new Set(), activate: new Set(), deactivate: new Set() } this.on = _on(this.#listeners) } get status() { return this.#status } getState(): Readonly { return this.#state } protected setState = this[SetStateSymbol]; [DeactivateSymbol]() { if (this.status === 'inactive') { throw new Error('the model is already inactive.') } notify(this.#listeners.deactivate) this.#status = 'inactive' } [ActivateSymbol]() { if (this.status === 'active') { throw new Error('the model is already active.') } this.#status = 'active' notify(this.#listeners.activate) } [SetStateSymbol](newState: State) { if (this.status !== 'active') { throw new Error(`Unable to update state, since the model is in "${this.status}" status`) } const oldState = this.getState() if (newState !== oldState) { this.#state = newState notify(this.#listeners.change, newState, oldState) } } } export function activate>(model: M) { if (model.status !== 'active') { model[ActivateSymbol]() } } export function deactivate>(model: M) { if (model.status !== 'inactive') { model[DeactivateSymbol]() } } function _on(listeners: OrchModelEventListenerMap) { return Object.fromEntries( Object.entries(listeners).map(([key, cache]) => [ key, (fn: (...params: unknown[]) => void) => { cache.add(fn) return () => cache.delete(fn) }, ]), ) as unknown as OrchModelEventListenFnMap } function notify

(fns: Set<(...params: P) => void>, ...params: P) { fns.forEach((fn) => fn(...params)) }