UNPKG

mancha

Version:

Javscript HTML rendering engine

186 lines (185 loc) 8.16 kB
import type { EffectMeta } from "./interfaces.js"; /** * Internal store properties that are always present. These are managed by the framework * and should not be set directly by users. */ export type InternalStoreState = { /** Reference to the parent store in a hierarchy. */ $parent?: SignalStore; /** Reference to the root renderer instance. */ $rootRenderer?: SignalStore; /** Reference to the root DOM node. */ $rootNode?: Node; } & { [key: `$$${string}`]: string | null; }; /** * Base type for user-defined store state. Uses `any` intentionally to allow flexible * user-defined state types without requiring explicit index signatures. */ export type StoreState = Record<string, any>; /** Type for expression evaluation function. */ type EvalFunction = (thisArg: SignalStoreProxy, args: Record<string, unknown>) => unknown; /** Type for observer entries that include the store context for proper binding. */ type ObserverEntry = { observer: Observer<unknown>; store: SignalStore; computedKey?: string; }; /** * Internal proxy type used within the store implementation. Uses `any` for dynamic property access. */ type SignalStoreProxy = SignalStore & InternalStoreState & { [key: string]: any; }; /** * The reactive context type exposed to effects and computed functions. * Includes the store's typed state T, internal state, and an index signature for dynamic access. */ export type ReactiveContext<T extends StoreState = StoreState> = SignalStore<T> & InternalStoreState & T & Record<string, unknown>; type Observer<T> = (this: SignalStoreProxy) => T; type KeyValueHandler = (this: SignalStoreProxy, key: string, value: unknown) => void; /** Symbol used to identify computed value markers. */ declare const COMPUTED_MARKER: unique symbol; /** Function type for computed value definitions. Receives reactive context as `$` parameter. */ export type ComputedFn<T extends StoreState, R> = (this: ReactiveContext<T>, $: ReactiveContext<T>) => R; /** Marker object returned by $computed() to signal that a value should be computed reactively. */ export interface ComputedMarker<R> { [COMPUTED_MARKER]: true; fn: ComputedFn<StoreState, R>; value?: R; dirty: boolean; effectFn?: Observer<unknown>; } /** Default notification debounce time in millis. */ export declare const REACTIVE_DEBOUNCE_MILLIS = 10; export declare function getAncestorValue(store: SignalStore | null, key: string): unknown; export declare function getAncestorKeyStore(store: SignalStore | null, key: string): SignalStore | null; export declare function setAncestorValue(store: SignalStore, key: string, value: unknown): void; export declare function setNestedProperty(obj: Record<string, unknown>, path: string, value: unknown): void; export declare class SignalStore<T extends StoreState = StoreState> { protected readonly evalkeys: string[]; protected readonly expressionCache: Map<string, EvalFunction>; protected readonly observers: Map<string, Set<ObserverEntry>>; protected readonly keyHandlers: Map<RegExp, Set<KeyValueHandler>>; readonly _store: Map<string, unknown>; _lock: Promise<void>; /** * Notification state per key. Value is a pending timeout, or "executing" * when observers are running. Used to debounce and prevent infinite loops. */ private readonly _notify; /** * Tracks nested computed evaluation depth. When > 0, we're inside a computed * function and writes to reactive properties should trigger a warning. */ private _computedDepth; constructor(data?: T); private wrapObject; watch<T>(key: string, observer: Observer<T>): void; addKeyHandler(pattern: RegExp, handler: KeyValueHandler): void; /** * Tags all observer entries matching the given observer function with a computed key. * Called after effect runs to mark which observers belong to which computed. */ private tagObserversForComputed; /** * Synchronously marks all computeds that depend on this key as dirty. * Uses the computedKey field on observer entries for O(1) key lookup. * Cascades through computed chains (if A depends on B, and B is marked dirty, * then A is also marked dirty). */ private markDependentComputedsDirty; notify(key: string, debounceMillis?: number): Promise<void>; get<T>(key: string, observer?: Observer<T>): unknown; private setupComputed; /** * Sets a value in the store. * @param key - The key to set. * @param value - The value to set (can be a computed marker). * @param local - If true, sets directly on this store bypassing ancestor lookup. * Use for creating local scope variables that shadow ancestors. */ set(key: string, value: unknown, local?: boolean): Promise<void>; del(key: string): Promise<void>; /** * Disposes this store by clearing all observers. * Call this when the store is no longer needed to prevent memory leaks. * Also removes any observers this store registered on ancestor stores. */ dispose(): void; keys(): string[]; /** * Checks if a key exists in THIS store only (not ancestors). * Use `get(key) !== null` to check if a key exists anywhere in the chain. */ has(key: string): boolean; /** * Returns observer statistics for performance reporting. */ getObserverStats(): { totalKeys: number; totalObservers: number; byKey: Record<string, number>; }; effect<R>(observer: (this: ReactiveContext<T>) => R, _meta?: EffectMeta): R; /** * Creates a computed property that automatically updates when its dependencies change. * The function is evaluated in a reactive effect, and the result is stored. When any * reactive property accessed within the function changes, it re-evaluates and updates. * * **Important:** This method returns a marker object at runtime, but is typed as * returning `R` to enable ergonomic property assignment without type casts. The return * value must be assigned to a store property (via `set()` or `$.prop =`) - do not use * it directly as a value. * * @example * // Using function() to access reactive `this`: * store.set('double', store.$computed(function() { return this.count * 2 })); * * // Using arrow function with $ parameter (for templates): * store.set('double', store.$computed(($) => $.count * 2)); * * // Direct property assignment (ergonomic typing): * store.$.doubled = store.$computed(($) => $.count * 2); */ $computed<R>(fn: ComputedFn<T, R>): R; private proxify; get $(): SignalStore<T> & InternalStoreState & T; /** * Creates an evaluation function for the provided expression. * @param expr The expression to be evaluated. * @returns The evaluation function. */ private makeEvalFunction; /** * Retrieves or creates a cached expression function for the provided expression. * @param expr - The expression to retrieve or create a cached function for. * @returns The cached expression function. */ private cachedExpressionFunction; eval(expr: string, args?: Record<string, unknown>): unknown; /** * Executes an async function and returns a reactive state object that tracks the result. * * @param fn - The async function to execute. * @param options - Optional arguments to pass to the function. * @returns A reactive state object with $pending, $result, and $error properties. * * @example * // In :data attribute - executes on mount * :data="{ users: $resolve(api.listUsers) }" * * // With options * :data="{ user: $resolve(api.getUser, { path: { id: userId } }) }" * * // In :on:click - executes on click * :on:click="result = $resolve(api.deleteUser, { path: { id } })" */ $resolve<T, O = unknown>(fn: (options?: O) => Promise<T>, options?: O): { $pending: boolean; $result: T | null; $error: Error | null; }; } export {};