/** * reactive/types -- Type definitions for the TypedScope reactive proxy system. * * TypedScope wraps a ReactiveTarget (ScopeFacade) in a Proxy that provides * typed property access. All scope infrastructure methods are $-prefixed to * avoid collisions with user state keys. * * Dependency: type-only imports from engine/ and scope/ (zero runtime cost). */ import type { ExecutionEnv } from '../engine/types.js'; import type { ScopeRecorder } from '../scope/types.js'; export interface ReactiveTarget { getValue(key?: string): unknown; setValue(key: string, value: unknown, shouldRedact?: boolean, description?: string): void; updateValue(key: string, value: unknown, description?: string): void; deleteValue(key: string, description?: string): void; /** Returns all state keys without firing onRead. Used by ownKeys/has traps. */ getStateKeys?(): string[]; /** Check key existence without firing onRead. Used by has trap. */ hasKey?(key: string): boolean; /** Read state without firing onRead. Used by array proxy getCurrent(). */ getValueSilent?(key?: string): unknown; getArgs>(): T; getEnv(): Readonly; attachScopeRecorder(recorder: ScopeRecorder): void; detachScopeRecorder(recorderId: string): void; getScopeRecorders(): ScopeRecorder[]; addDebugInfo(key: string, value: unknown): void; addDebugMessage(value: unknown): void; addErrorInfo(key: string, value: unknown): void; addMetric(name: string, value: unknown): void; addEval(name: string, value: unknown): void; emitEvent(name: string, payload?: unknown): void; detachAndJoinLater(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): import('../detach/types.js').DetachHandle; detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void; } export interface ScopeMethods { $getValue(key: string): unknown; $setValue(key: string, value: unknown, shouldRedact?: boolean, description?: string): void; $update(key: string, value: unknown, description?: string): void; $delete(key: string, description?: string): void; /** Proxy-synthesized: calls getValue(rootKey) then lodash.get for nested path. Not a direct delegation. */ $read(dotPath: string): unknown; $getArgs>(): T; $getEnv(): Readonly; $debug(key: string, value: unknown): void; $log(value: unknown): void; $error(key: string, value: unknown): void; $metric(name: string, value: unknown): void; $eval(name: string, value: unknown): void; $attachScopeRecorder(recorder: ScopeRecorder): void; $detachScopeRecorder(recorderId: string): void; $getScopeRecorders(): ScopeRecorder[]; /** * Batch-mutate an array key in a single clone+write cycle. * * Every `scope.items.push(x)` clones the entire array and commits it — O(N) per call. * For N mutations on an M-length array that is O(N×M). Use `$batchArray` to clone once, * apply all mutations inside `fn`, then commit once — O(M) total. * * ```typescript * // Before: 1000 clones × growing array = O(N²) * for (let i = 0; i < 1000; i++) scope.items.push(i); * * // After: 1 clone + 1 commit = O(N) * scope.$batchArray('items', (arr) => { * for (let i = 0; i < 1000; i++) arr.push(i); * }); * ``` * * `fn` receives a plain (non-proxy) mutable **shallow copy** of the current array. * The array itself is a new instance, but object references inside it are shared with * the original state — mutations to nested objects inside `fn` affect those originals. * Only push/pop/sort/splice and other operations that change the array's own slots are * safely isolated. * * Mutations inside `fn` are NOT tracked individually — only the final committed array * appears in the narrative as a single write. If the key does not exist or is not an * array, `fn` receives an empty array and the result is committed as the new value. * * If `fn` throws, `setValue` is never called and state remains unchanged (atomic on * error). The exception propagates to the caller. * * `key` is untyped (`string`) — TypeScript will not catch typos. `arr` is typed as * `unknown[]` because `ScopeMethods` is not parameterized by `T`; cast inside `fn` * when element types are known: `(arr as string[]).push(x)`. */ $batchArray(key: string, fn: (arr: unknown[]) => void): void; /** * Fire a structured event to every attached recorder implementing * `onEmit`. The primary primitive for consumer-authored observability * events (LLM tokens, billing metrics, domain milestones, etc.) that * shouldn't live in scope state. * * Synchronous, pass-through — delivered to recorders immediately, in * call order. Zero-allocation when no recorders are attached. * * ## Naming convention * * Use hierarchical dotted names: `'..'`. * Examples: * - `'agentfootprint.llm.tokens'` * - `'myapp.billing.spend'` * - `'auth.check.denied'` * * The library stays vocabulary-agnostic — no central registry, no * reserved prefixes. Namespace prefixes prevent collisions across * libraries and apps naturally. * * ## Redaction * * If `RedactionPolicy.emitPatterns` matches the name, the payload is * replaced with `'[REDACTED]'` before dispatch. No recorder ever sees * the raw value for redacted event names. * * @param name - Consumer-chosen event identifier (see naming convention). * @param payload - Structured data to attach. Shape is up to the * consumer; library treats it as opaque. * * @example * ```typescript * scope.$emit('agentfootprint.llm.tokens', { input: 100, output: 50 }); * scope.$emit('myapp.decision', { branch: 'approved', confidence: 0.92 }); * ``` */ $emit(name: string, payload?: unknown): void; $detachAndJoinLater(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): import('../detach/types.js').DetachHandle; $detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void; /** * Stop the current execution context. * * - **In a top-level chart:** the traverser exits cleanly after this stage * completes. * - **Inside a subflow:** by default, only the subflow's own execution * stops; control returns to the parent as normal. If the subflow is * mounted with `propagateBreak: true`, the break signal (and its * optional reason) propagates up to the parent scope, terminating the * outer loop too. See `SubflowMountOptions.propagateBreak`. * * @param reason - Optional free-form string describing why the break * happened. Propagates to `FlowBreakEvent.reason` so recorders and * custom narratives can surface it. Defaults to undefined. */ $break(reason?: string): void; $toRaw(): ReactiveTarget; } export type TypedScope> = T & ScopeMethods; export interface ReactiveOptions { /** * Pipeline break function — injected by StageRunner after scope creation. * Accepts an optional reason string that propagates to `FlowBreakEvent` * and (when `propagateBreak` is set on a mount) up to the parent scope. */ breakPipeline?: (reason?: string) => void; } export declare const SCOPE_METHOD_NAMES: Set; export declare const BREAK_SETTER: unique symbol; export declare const IS_TYPED_SCOPE: unique symbol; export declare const EXECUTOR_INTERNAL_METHODS: Set;