alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
142 lines (112 loc) • 3.24 kB
text/typescript
import type { AsyncLocalStorage } from "node:async_hooks";
export type AsyncLocalStorageData = any;
export type StateScope = "current" | "app" | "parent";
export const ALS_PARENT = Symbol("als.parent");
export class AlsProvider {
static create = (): AsyncLocalStorage<AsyncLocalStorageData> | undefined => {
return undefined;
};
public als?: AsyncLocalStorage<AsyncLocalStorageData>;
constructor() {
this.als = AlsProvider.create();
}
public createContextId(): string {
return crypto.randomUUID();
}
public run<R>(callback: () => R, data: Record<string, any> = {}): R {
if (!this.als) {
return callback();
}
const parent = this.als.getStore() ?? undefined;
// Intentional mutation: set defaults on the input object to avoid
// allocating an intermediate copy. Callers always pass a fresh literal.
data.registry ??= new Map();
data.context ??= this.createContextId();
return this.als.run({ ...data, [ALS_PARENT]: parent }, callback);
}
public exists(): boolean {
return !!this.get("context");
}
public get<T>(key: string, scope?: StateScope): T | undefined {
if (!this.als) {
return undefined;
}
const store = this.als.getStore();
if (!store) {
return undefined;
}
if (scope === "app") {
return undefined;
}
if (scope === "current") {
return key in store ? (store[key] as T) : undefined;
}
if (scope === "parent") {
return store[ALS_PARENT]?.[key] as T | undefined;
}
// Default: walk up the tree from current layer to root
if (key in store) {
return store[key] as T;
}
let current = store[ALS_PARENT];
while (current) {
if (key in current) {
return current[key] as T;
}
current = current[ALS_PARENT];
}
return undefined;
}
/**
* Return the raw ALS layer object that owns `key`, walking from the
* current layer up through parent forks — the same resolution order as
* the default-scope branch of {@link get}. Returns `undefined` when no
* active ALS layer (current or any ancestor) has the key.
*
* Used by `StateManager.register()` to decode a value in place, in
* whichever fork layer it physically lives, instead of flattening
* fork-scoped state into the app-level store.
*/
public getLayer(key: string): AsyncLocalStorageData | undefined {
if (!this.als) {
return undefined;
}
let current = this.als.getStore();
while (current) {
if (key in current) {
return current;
}
current = current[ALS_PARENT];
}
return undefined;
}
public has(key: string): boolean {
if (!this.als) {
return false;
}
const store = this.als.getStore();
if (!store) {
return false;
}
if (key in store) {
return true;
}
let current = store[ALS_PARENT];
while (current) {
if (key in current) {
return true;
}
current = current[ALS_PARENT];
}
return false;
}
public set<T>(key: string, value: T): void {
if (!this.als) {
return;
}
const store = this.als.getStore();
if (store) {
store[key] = value;
}
}
}