import type { DispatchSync } from './runtimeSingleton.js'; /** Wrapping descriptor stored per Submodel boundary. */ export type WrapDescriptor = Readonly<{ toParentMessage: (message: unknown) => unknown; }>; /** Boundary id is a `|`-joined chain of Submodel slot ids. Empty * string represents the root boundary. Two-level example: * `"work-history|entry-abc123"`. User-supplied slot ids must not * contain the separator character; {@link composeBoundary} throws when * they do. */ export type BoundaryId = string; export declare const ROOT_BOUNDARY: BoundaryId; export declare const composeBoundary: (parent: BoundaryId, childId: string) => BoundaryId; /** Per-runtime registry of Submodel wrapping descriptors. The runtime * creates one of these in `start` and reuses it across renders. * `h.submodel` writes into `wraps` each render and attaches a snabbdom * `destroy` hook that calls `deregisterBoundaryWrap` when the * corresponding vnode is removed from the DOM tree. The dispatch path * reads from `wraps` at event-fire time. * * `boundaryDispatches` caches per-(outerDispatch, boundaryId) dispatcher * closures so `requireDispatch` returns a stable reference across * repeated calls with the same outerDispatch (necessary for * `createLazy`'s dispatch-identity check). Keyed by outerDispatch as a * WeakMap so DevTools jump-to renders with a different * outerDispatch (typically a noOpDispatch that drops messages) get * their own per-boundary cache. Without this two-level keying, a * dispatcher created during a live render would still close over the * live outerDispatch after a jump-to and silently mutate the live app. * * `seenThisRender` tracks boundaries marked alive during the current * render for duplicate-slotId detection: two `h.submodel` calls * inside the same parent boundary must use different `slotId`s. * Values are the call site captured at register time, surfaced when a * second register collides so both locations land in the throw * message. The map is cleared at the start of each render via * `beginRender`. Boundaries behind a `createLazy`/`createKeyedLazy` * cache hit are replayed into this map via {@link markSeenForLazyHit} * so the duplicate-slotId guard catches collisions against memoized * siblings, not just against siblings that re-ran this frame. It does * not drive pruning (VNode destroy hooks do), but the Submodel destroy * hook reads it during the patch phase to tell a same-cycle remount (a * keyed root whose key changed) from a true unmount, skipping * deregistration in the remount case. See {@link beginRender}. * * `lazyTrackingStack` is a stack of sets used by `createLazy` and * `createKeyedLazy` to capture which boundary ids were marked alive * during the wrapped function's first execution. On a later cache * hit, the lazy helper replays the captured ids into * `seenThisRender` so the duplicate-slotId guard sees them. Each * active lazy invocation pushes its own set; `registerBoundaryWrap` * and `markSeenForLazyHit` write to every set on the stack so an * outer lazy correctly captures ids contributed by inner lazies it * wraps. */ export type BoundaryRegistry = { readonly wraps: Map; readonly boundaryDispatches: WeakMap>; readonly seenThisRender: Map; readonly lazyTrackingStack: Array>; readonly dedupeSeen: Set; }; export declare const createBoundaryRegistry: () => BoundaryRegistry; export declare const registerBoundaryWrap: (registry: BoundaryRegistry, boundaryId: BoundaryId, descriptor: WrapDescriptor) => void; /** Starts capturing boundary registrations on a fresh set pushed onto * `lazyTrackingStack`. Used by `createLazy`/`createKeyedLazy` around the * wrapped view function. Must be paired with {@link endLazyTracking} on * the same call stack so an exception inside the view does not leak the * tracking frame to a later render. */ export declare const beginLazyTracking: (registry: BoundaryRegistry) => Map; /** Pops the most recent tracking set. Throws when called on an empty * stack to surface unmatched begin/end pairs immediately rather than * silently corrupting later renders. */ export declare const endLazyTracking: (registry: BoundaryRegistry) => void; /** Replays a set of boundary ids captured during a previous lazy run * into `seenThisRender` so the duplicate-slotId guard sees them. Also * forwards them into any active tracking sets so an outer lazy * wrapping this cache hit captures the ids in its own snapshot. * * Skips ids already present in `seenThisRender` to preserve the * original call site of the live entry (the first registration this * render still wins the error message). */ export declare const markSeenForLazyHit: (registry: BoundaryRegistry, trackedIds: ReadonlyMap) => void; /** Removes a boundary's wrap. Called by `h.submodel`'s destroy hook when * the corresponding vnode leaves the DOM. * * Does not touch `boundaryDispatches`: it is a WeakMap keyed by * outerDispatch, so per-outerDispatch inner Maps become unreachable and * are GC'd when their outerDispatch is. Cached dispatcher closures that * outlive a deregister become inert. `dispatchAcrossBoundary` throws * when it cannot find an ancestor wrap, which surfaces a clear error * rather than letting events from a destroyed boundary silently * misroute. */ export declare const deregisterBoundaryWrap: (registry: BoundaryRegistry, boundaryId: BoundaryId) => void; export declare const getOrCreateBoundaryDispatch: (registry: BoundaryRegistry, outerDispatch: DispatchSync, boundaryId: BoundaryId) => DispatchSync; /** Called at the start of each top-level render. Clears the * per-render duplicate-slotId tracking map so siblings inside the * same parent boundary can be re-validated. Does NOT touch `wraps` * or `boundaryDispatches`. Those persist across renders and are * evicted by vnode destroy hooks instead. * * Clear `seenThisRender` only here, at the start of a cycle, never * between the view and patch phases. The Submodel destroy hook reads * it during patch to tell a same-cycle remount (a keyed root whose * key changed) from a true unmount; clearing it mid-cycle would * resurrect the `dispatchAcrossBoundary missing wrap` crash. */ export declare const beginRender: (registry: BoundaryRegistry) => void; //# sourceMappingURL=boundary.d.ts.map