mancha
Version:
Javscript HTML rendering engine
186 lines (185 loc) • 8.16 kB
TypeScript
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 {};