import { RavenError } from "../error/raven-error.ts"; import { GLOBAL_SCOPE, currentAppStorage, getOrCreateScopeMap, requestStorage, type ScopeKey, } from "./storage.ts"; export interface StateView { get(): T | undefined; getOrFailed(): T; } export interface StateOptions { name?: string; } let stateCounter = 0; export abstract class ScopedState { public readonly symbol: symbol; public readonly name: string; constructor(options?: StateOptions) { this.name = options?.name ?? `state:${++stateCounter}`; this.symbol = Symbol(this.name); } public abstract get(): T | undefined; public abstract in(scopeKey: ScopeKey): StateView; public getOrFailed(): T { const value = this.get(); if (value === undefined) { throw RavenError.ERR_STATE_NOT_INITIALIZED(this.name); } return value; } } class AppStateView implements StateView { constructor( private readonly descriptor: AppState, private readonly scope: ScopeKey, ) {} public get(): T | undefined { const app = currentAppStorage.getStore(); return app?.scopedStateMaps.get(this.scope)?.get(this.descriptor.symbol); } public getOrFailed(): T { const value = this.get(); if (value === undefined) { throw RavenError.ERR_STATE_NOT_INITIALIZED(this.descriptor.name); } return value; } } export class AppState extends ScopedState { private readonly views = new Map>(); public override get(): T | undefined { const app = currentAppStorage.getStore(); return app?.scopedStateMaps.get(GLOBAL_SCOPE)?.get(this.symbol); } public override in(scopeKey: ScopeKey): AppStateView { if (!this.views.has(scopeKey)) { this.views.set(scopeKey, new AppStateView(this, scopeKey)); } return this.views.get(scopeKey)!; } } class RequestStateView implements StateView { constructor( private readonly descriptor: RequestState, private readonly scope: ScopeKey, ) {} public get(): T | undefined { const store = requestStorage.getStore(); return store?.get(this.scope)?.get(this.descriptor.symbol); } public getOrFailed(): T { const value = this.get(); if (value === undefined) { throw RavenError.ERR_STATE_NOT_INITIALIZED(this.descriptor.name); } return value; } } export class RequestState extends ScopedState { private readonly views = new Map>(); public override get(): T | undefined { const store = requestStorage.getStore(); return store?.get(GLOBAL_SCOPE)?.get(this.symbol); } public override in(scopeKey: ScopeKey): RequestStateView { if (!this.views.has(scopeKey)) { this.views.set(scopeKey, new RequestStateView(this, scopeKey)); } return this.views.get(scopeKey)!; } } export function defineAppState(options?: StateOptions): AppState { return new AppState(options); } export function defineRequestState(options?: StateOptions): RequestState { return new RequestState(options); } export type StateSetter = (state: ScopedState, value: T) => void; export function internalSet(state: ScopedState, value: T): void { if (state instanceof AppState) { const app = currentAppStorage.getStore(); if (!app) { throw RavenError.ERR_STATE_CANNOT_SET(state.name); } getOrCreateScopeMap(app.scopedStateMaps, GLOBAL_SCOPE).set(state.symbol, value); return; } if (state instanceof RequestState) { const reqMap = requestStorage.getStore(); if (!reqMap) { throw RavenError.ERR_STATE_CANNOT_SET(state.name); } getOrCreateScopeMap(reqMap, GLOBAL_SCOPE).set(state.symbol, value); } }