/** * utils.ts — Helper functions for nested object manipulation * * Provides consistent path traversal and value manipulation for the memory system. * Zero external dependencies. */ import type { MemoryPatch, TraceEntry } from './types.js'; /** ASCII Unit-Separator — cannot appear in JS identifiers, invisible in logs. */ export declare const DELIM = "\u001F"; type NestedObject = { [key: string]: any; }; /** * Resolves run-namespaced and global paths. * Each flowchart execution (run) stores data under `runs/{id}/` to prevent collisions. */ export declare function getRunAndGlobalPaths(runId?: string, path?: (string | number)[]): { runPath: (string | number)[] | undefined; globalPath: (string | number)[]; }; /** * Sets a value at a nested path, creating intermediate objects as needed. */ export declare function setNestedValue(obj: NestedObject, runId: string, _path: string[], field: string, value: T, defaultValues?: unknown): NestedObject; /** * Deep-merges a value into the object at the specified path. * - Arrays: concatenate * - Objects: shallow merge at each level * - Primitives: replace */ export declare function updateNestedValue(obj: any, runId: string | undefined, _path: (string | number)[], field: string | number, value: T, defaultValues?: unknown): any; /** * In-place value update with merge semantics. * - Arrays (non-empty): concatenate onto existing * - Arrays (empty): direct replace — writing `[]` clears the field * - Objects (non-empty): shallow merge (spread) * - Objects (empty): direct replace — writing `{}` clears the field * - Primitives: direct assignment * * Note on empty arrays: both `value && Array.isArray(value)` and * `Array.isArray(value)` evaluate the same for arrays — `[]` is truthy in * JavaScript, so the `&&` guard was never the issue. The actual bug was the * concat path: `[...cur, ...[]]` silently returned `cur` unchanged when `value` * was `[]`, making `updateValue(obj, 'tags', [])` a no-op instead of a clear. * The fix is the explicit `value.length === 0` early-return branch. */ export declare function updateValue(object: any, key: string | number, value: any): void; /** * Gets a value at a nested path with prototype-pollution protection. */ export declare function getNestedValue(root: any, path: (string | number)[], field?: string | number): any; /** * Redacts sensitive values in a patch for logging/debugging. */ export declare function redactPatch(patch: MemoryPatch, redactedSet: Set): MemoryPatch; /** * Normalises an array path into a stable string key using DELIM. */ export declare function normalisePath(path: (string | number)[]): string; /** * Structural deep equality for committed-state values. * * Used by {@link TransactionBuffer} to decide whether a stage actually CHANGED * a path or merely re-wrote / reverted it to the value it already held (a * "no-op write"). Committed state is JSON-shaped — it must survive * `structuredClone` — so this only needs to handle the shapes that can reach a * commit: primitives, arrays, and plain objects. * * Semantics: * - reference / identical-primitive short-circuits first (cheap fast path) * - `NaN` equals `NaN` (primitive compare falls back to `Object.is`) * - arrays: equal length AND deep-equal element-wise (order-sensitive) * - objects: identical own-key set AND deep-equal per key * - mismatched kinds (array vs object, object vs null) → not equal * * Cost & safety: * - Allocates NOTHING but transient `Object.keys` arrays — no clones. It is * strictly cheaper than the `structuredClone` the commit already performs. * - Primitive comparisons (the bulk of state) are O(1) via the `===` / * `Object.is` fast paths; only nested objects/arrays incur a walk, bounded * by the value's own size. * - Assumes ACYCLIC, JSON-shaped values — the same contract the memory layer * already relies on (committed state is `structuredClone`d and must be * JSON-serialisable for checkpoints). A cyclic value is out of contract * here exactly as it is for checkpointing; dev mode flags cycles at write * time via `ScopeFacade.setValue`. */ export declare function deepEqual(a: any, b: any): boolean; /** * Deep union merge helper. * - Arrays (non-empty): union without duplicates (encounter order preserved) * - Arrays (empty): replace — src `[]` clears the destination array. * Rationale: writing `scope.tags = []` means "clear tags", not "append nothing". * Without this rule, an empty-array write silently becomes a no-op which is * impossible to distinguish from a bug. * - Objects: recursive merge * - Primitives: source wins */ export declare function deepSmartMerge(dst: any, src: any): any; /** * Applies a commit bundle to a base state by replaying operations in order. * Guarantees "last writer wins" semantics. * * The single replay primitive — three consumers inherit every verb from it: * live state (`SharedMemory.applyPatch`), time travel * (`EventLog.materialise`), and the redacted mirror * (`StageContext.commit`'s second `applyPatch`). * * Verb arms: * - `'set'` — overwrite with `overwrite[path]` (the full final value). * - `'merge'` — `deepSmartMerge` the accumulated `updates[path]` delta in. * - `'append'` — (#13c-B delta mode) `overwrite[path]` holds ONLY the tail; * reconstruct by concatenating it onto the current array. When the * current value or the recorded tail is not an array (out-of-order * replay base, or a REDACTED tail — `redactPatch` replaces matched * payloads with the `'REDACTED'` string), degrade to a direct set of the * recorded value — the same terminal value a redacted/corrupt `'set'` * produces. * - `'delete'` — (#13c-B delta mode) remove the key (`nativeDelete`, * prototype-pollution-safe). The path stays enumerated in `overwrite` * (value `undefined`) for key-set consumers; replay ignores that value. */ export declare function applySmartMerge(base: any, updates: MemoryPatch, overwrite: MemoryPatch, trace: TraceEntry[]): any; export {};