@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/dev.js +2494 -271
  2. package/dist/node.cjs +3691 -1595
  3. package/dist/prod/affects.js +13 -12
  4. package/dist/prod/boundaries.js +43 -35
  5. package/dist/prod/core/action.js +3 -3
  6. package/dist/prod/core/async.js +137 -106
  7. package/dist/prod/core/constants.js +55 -1
  8. package/dist/prod/core/core.js +354 -247
  9. package/dist/prod/core/effect.js +47 -50
  10. package/dist/prod/core/error.js +13 -1
  11. package/dist/prod/core/external.js +4 -4
  12. package/dist/prod/core/graph.js +88 -52
  13. package/dist/prod/core/heap.js +38 -38
  14. package/dist/prod/core/lanes.js +34 -34
  15. package/dist/prod/core/optimistic.js +61 -58
  16. package/dist/prod/core/owner.js +38 -38
  17. package/dist/prod/core/scheduler.js +401 -174
  18. package/dist/prod/core/verdict.js +132 -65
  19. package/dist/prod/index.js +7 -3
  20. package/dist/prod/map.js +106 -106
  21. package/dist/prod/signals.js +253 -25
  22. package/dist/prod/store/index.js +2 -0
  23. package/dist/prod/store/next/optimistic.js +314 -121
  24. package/dist/prod/store/next/patch-hooks.js +13 -0
  25. package/dist/prod/store/next/patch.js +614 -0
  26. package/dist/prod/store/next/projection.js +23 -19
  27. package/dist/prod/store/next/reconcile.js +307 -120
  28. package/dist/prod/store/next/store.js +440 -117
  29. package/dist/prod/store/next/target.js +13 -4
  30. package/dist/prod/store/store.js +5 -5
  31. package/dist/types/core/async.d.ts +2 -0
  32. package/dist/types/core/attribution.d.ts +9 -4
  33. package/dist/types/core/constants.d.ts +54 -0
  34. package/dist/types/core/core.d.ts +34 -21
  35. package/dist/types/core/dev.d.ts +8 -0
  36. package/dist/types/core/error.d.ts +9 -0
  37. package/dist/types/core/graph.d.ts +22 -0
  38. package/dist/types/core/index.d.ts +2 -2
  39. package/dist/types/core/scheduler.d.ts +46 -0
  40. package/dist/types/core/types.d.ts +12 -0
  41. package/dist/types/index.d.ts +3 -3
  42. package/dist/types/signals.d.ts +108 -0
  43. package/dist/types/store/index.d.ts +2 -0
  44. package/dist/types/store/next/optimistic.d.ts +13 -10
  45. package/dist/types/store/next/patch-hooks.d.ts +41 -0
  46. package/dist/types/store/next/patch.d.ts +91 -0
  47. package/dist/types/store/next/projection.d.ts +1 -1
  48. package/dist/types/store/next/reconcile.d.ts +14 -0
  49. package/dist/types/store/next/store.d.ts +52 -2
  50. package/dist/types/store/next/target.d.ts +73 -8
  51. package/dist/types-cjs/core/async.d.cts +2 -0
  52. package/dist/types-cjs/core/attribution.d.cts +9 -4
  53. package/dist/types-cjs/core/constants.d.cts +54 -0
  54. package/dist/types-cjs/core/core.d.cts +34 -21
  55. package/dist/types-cjs/core/dev.d.cts +8 -0
  56. package/dist/types-cjs/core/error.d.cts +9 -0
  57. package/dist/types-cjs/core/graph.d.cts +22 -0
  58. package/dist/types-cjs/core/index.d.cts +2 -2
  59. package/dist/types-cjs/core/scheduler.d.cts +46 -0
  60. package/dist/types-cjs/core/types.d.cts +12 -0
  61. package/dist/types-cjs/index.d.cts +3 -3
  62. package/dist/types-cjs/signals.d.cts +108 -0
  63. package/dist/types-cjs/store/index.d.cts +2 -0
  64. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
  66. package/dist/types-cjs/store/next/patch.d.cts +91 -0
  67. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  68. package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
  69. package/dist/types-cjs/store/next/store.d.cts +52 -2
  70. package/dist/types-cjs/store/next/target.d.cts +73 -8
  71. package/package.json +2 -2
@@ -40,6 +40,24 @@ export interface Transition {
40
40
  _done: boolean | Transition;
41
41
  _gatedSubs: Set<Computed<any>>;
42
42
  }
43
+ /**
44
+ * Flip-entanglement (#3164 follow-up): `until()` is a declaration of
45
+ * relatedness — the predicate names the condition that confirms the awaiting
46
+ * transaction. When the predicate settles truthy, every live foreign
47
+ * transition whose staged write it read IS the confirming event by the
48
+ * user's own definition, so it merges into the awaiting transaction and
49
+ * reveals at the joint settle — the cross-primitive twin of the family fold
50
+ * (a landing on an optimism-carrying family joins the retaining
51
+ * transaction). Non-flipping updates never pass through here: falsy
52
+ * evaluations don't entangle, so unrelated traffic on the watched sources
53
+ * reveals freely on its own schedule.
54
+ *
55
+ * Runs inside the predicate's compute (pure phase) — the confirming
56
+ * transition's stamps are still live and its commit decision hasn't run, so
57
+ * the merge lands before any reveal. Only the tree-shaken graphs that call
58
+ * `until()` retain this.
59
+ */
60
+ export declare function entangleConfirmingTransitions(obs: Computed<any>, target: Transition): void;
43
61
  export declare function schedule(): void;
44
62
  /**
45
63
  * Permanently halts the reactive system. Called when a user error escapes
@@ -103,11 +121,19 @@ export declare class GlobalQueue extends Queue {
103
121
  static _transitionBlocked: ((transition: Transition) => boolean) | null;
104
122
  static _cleanupLanes: ((completingTransition: Transition | null) => void) | null;
105
123
  static _runLaneEffects: ((type: number) => void) | null;
124
+ /** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
125
+ * apply at lane-effect timing — visible in flight, unlike the regular
126
+ * effect queues an action stashes. Injected; null when unused. */
127
+ static _drainPatchOptimistic: (() => void) | null;
106
128
  static _gatedRead: ((el: Signal<any>, owner: OptimisticNode, c: Computed<any>) => boolean) | null;
107
129
  static _laneSuspends: ((owner: OptimisticNode) => boolean) | null;
108
130
  static _laneReadsCommitted: ((el: OptimisticNode, owner: OptimisticNode, c: Computed<any>) => boolean) | null;
109
131
  static _recomputeLane: ((el: Computed<any>, own: boolean) => OptimisticLane | null | false) | null;
110
132
  static _laneAsyncPending: ((el: Computed<any>) => void) | null;
133
+ /** Authoritative-view reader wakeup (until()): installed at first until() call.
134
+ * Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which only until()'s
135
+ * carve-out read can set, so `!` invocations are safe once the gate holds. */
136
+ static _notifyAuthoritativeObservers: ((el: Signal<any> | Computed<any>) => void) | null;
111
137
  static _laneAsyncSettled: ((el: Computed<any>) => void) | null;
112
138
  static _trackOptimisticStore: ((store: any) => void) | null;
113
139
  flush(): void;
@@ -126,6 +152,14 @@ export declare function armReaskClear(): void;
126
152
  export declare function insertSubs(node: Signal<any> | Computed<any>, optimistic?: boolean): void;
127
153
  export declare let storeCommitHook: (() => void) | null;
128
154
  export declare function setStoreCommitHook(fn: () => void): void;
155
+ /** Patch-channel release hook (next/patch.ts): transition-stamped patch
156
+ * emissions are released when THEIR batch commits. Transitions never
157
+ * abort: failed actions still commit (only optimistic overrides revert),
158
+ * and merged-away transitions hand their stash to the survivor
159
+ * (mergeTransitionState) — every stash drains exactly once. Injected like
160
+ * storeCommitHook to stay tree-shakeable. */
161
+ export declare let patchCommitHook: ((batch: Transition) => void) | null;
162
+ export declare function setPatchCommitHook(fn: (batch: Transition) => void): void;
129
163
  export declare function finalizePureQueue(completingTransition?: Transition | null, incomplete?: boolean): void;
130
164
  /**
131
165
  * Count of live `affects()` registrations across the system (including
@@ -178,3 +212,15 @@ export declare function flush<T>(fn: () => T): T;
178
212
  export declare function currentTransition(transition: Transition): Transition;
179
213
  export declare function setActiveTransition(transition: Transition | null): void;
180
214
  export declare function runInTransition<T>(transition: Transition, fn: () => T): T;
215
+ /** Run `fn` with `transition` as BOTH the ambient transaction and the
216
+ * registration batch, restoring both after. runInTransition alone is not
217
+ * enough for code that WRITES on behalf of a transaction from inside someone
218
+ * else's window (optimistic replay re-arming a still-open action's edits
219
+ * during a landing commit, #3123): registrations route through the queue's
220
+ * batch pointer, and a bare activeTransition swap leaves them in the ambient
221
+ * batch — a plain batch "completes" at the next flush and reverts optimistic
222
+ * registrations that were supposed to live with the transaction.
223
+ * initTransition is the wrong tool here: it MERGES the currently ambient
224
+ * transaction into the target, entangling whatever the interrupted window
225
+ * belonged to. */
226
+ export declare function runAsTransitionBatch<T>(transition: Transition, fn: () => T): T;
@@ -27,6 +27,11 @@ export interface NodeOptions<T> {
27
27
  ownedWrite?: boolean;
28
28
  /** Exclude this signal from snapshot capture (internal — not part of public API) */
29
29
  _noSnapshot?: boolean;
30
+ /** Extra CONFIG_* bits OR'd into the node's config at creation (internal —
31
+ * not part of public API). Used by resolve()/until() for
32
+ * CONFIG_DIRECT_COMMIT / CONFIG_AUTHORITATIVE_READ, keeping the per-flag
33
+ * option arms out of the core creation path. */
34
+ _extraConfig?: number;
30
35
  unobserved?: () => void;
31
36
  lazy?: boolean;
32
37
  sync?: boolean;
@@ -73,6 +78,13 @@ export interface NodeExtension {
73
78
  */
74
79
  _affectsCount: number;
75
80
  _inFlight: PromiseLike<any> | AsyncIterable<any> | null;
81
+ /** Cancellation for the CURRENT iterator flight (#3122): closes the
82
+ * iterator (`it.return()`), idempotent. Fired at the sites that release
83
+ * `_inFlight` so a superseded stream stops at supersede time — its owner
84
+ * cleanup registration may ride the zombie-disposal channel, which a held
85
+ * transition defers until the SUPERSEDING flight settles. Null for plain
86
+ * promise flights (no cancellation hook exists). */
87
+ _flightTeardown: (() => void) | null;
76
88
  _error: unknown;
77
89
  _blocked: boolean | undefined;
78
90
  _pendingSources: Set<Computed<any>> | undefined;
@@ -1,9 +1,9 @@
1
- export { $REFRESH, ContextNotFoundError, NoOwnerError, NotReadyError, action, createContext, createOwner, createRoot, runWithOwner, flush, getNextChildId, peekNextChildId, getContext, setContext, getOwner, isDisposed, getObserver, isEqual, untrack, isPending, latest, refresh, SUPPORTS_PROXY, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots, enforceLoadingBoundary, enableExternalSource, resetErrorHalt } from "./core/index.cjs";
1
+ export { $REFRESH, ContextNotFoundError, NoOwnerError, NotReadyError, TimeoutError, action, createContext, createOwner, createRoot, runWithOwner, flush, getNextChildId, peekNextChildId, getContext, setContext, getOwner, isDisposed, getObserver, isEqual, untrack, isPending, latest, SUPPORTS_PROXY, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots, enforceLoadingBoundary, enableExternalSource, resetErrorHalt } from "./core/index.cjs";
2
2
  import { type Dev } from "./core/index.cjs";
3
3
  export declare const DEV: Dev | undefined;
4
4
  export type { Owner, Context, ContextRecord, IQueue, ExternalSourceFactory, ExternalSource, ExternalSourceConfig, Refreshable, Dev, DevHooks, DiagnosticCapture, DiagnosticCode, DiagnosticEvent, DiagnosticKind, Diagnostics, DiagnosticSeverity } from "./core/index.cjs";
5
- export { createSignal, createMemo, createEffect, createRenderEffect, createTrackedEffect, createReaction, createOptimistic, resolve, onSettled, onCleanup } from "./signals.cjs";
6
- export type { Accessor, SourceAccessor, Setter, Signal, ComputeFunction, EffectFunction, EffectBundle, EffectOptions, SignalOptions, MemoOptions, NoInfer } from "./signals.cjs";
5
+ export { createSignal, createMemo, createEffect, createRenderEffect, createTrackedEffect, createReaction, createOptimistic, refresh, resolve, until, onSettled, onCleanup } from "./signals.cjs";
6
+ export type { Truthy, UntilOptions, Accessor, SourceAccessor, Setter, Signal, ComputeFunction, EffectFunction, EffectBundle, EffectOptions, SignalOptions, MemoOptions, NoInfer } from "./signals.cjs";
7
7
  export { affects } from "./affects.cjs";
8
8
  export { mapArray, repeat, type Maybe } from "./map.cjs";
9
9
  export * from "./store/index.cjs";
@@ -491,6 +491,114 @@ export declare function createReaction(effectFn: EffectFunction<undefined> | Eff
491
491
  * @param fn a reactive expression to resolve
492
492
  */
493
493
  export declare function resolve<T>(fn: () => T): Promise<T>;
494
+ /**
495
+ * Invalidates one reactive source, forcing it to re-execute even if its inputs
496
+ * haven't changed, and returns a promise for the target's NEXT QUIESCENT
497
+ * STATE — the re-ask (and anything that supersedes it) has settled.
498
+ *
499
+ * Pass either a Solid-created accessor or a projected store created from
500
+ * `createStore(fn, ...)` / `createProjection(...)`. `refresh()` is a
501
+ * write-like invalidation operation: it does not read the target's value, and
502
+ * refreshing a plain signal accessor is a no-op that resolves immediately.
503
+ *
504
+ * The returned promise is safe to ignore (fire-and-forget refresh is
505
+ * unchanged, and a failed refetch will not surface an unhandled rejection).
506
+ * Awaiting it gives imperative flows the settle point without a reactive
507
+ * read:
508
+ * - Accessor targets resolve with the settled value; store targets resolve
509
+ * with the store node passed (reads through it are fresh after the await).
510
+ * - A failed re-ask rejects with the error (inside an action's generator,
511
+ * `yield refresh(x)` throws back at the yield point and the action reverts
512
+ * like any other failure).
513
+ * - Semantics are quiescence, not flight identity: if another refresh (or
514
+ * any invalidation) supersedes this one mid-flight, the promise waits for
515
+ * — and delivers — whatever finally lands.
516
+ * - Inside an action, truth landing into the held transaction is STAGED;
517
+ * the promise still settles then (matching `resolve()`/`until()`, #2930)
518
+ * and delivers the staged value — the caller's own optimistic override is
519
+ * never the delivered value.
520
+ * - The re-ask itself stays verdict-quiet exactly as before: `isPending`
521
+ * does not flip for a bare refresh (pair with `affects()` for a visible
522
+ * pending window).
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * const user = createMemo(async () => fetch(`/users/${id()}`).then(r => r.json()));
527
+ *
528
+ * // Fire-and-forget re-fetch
529
+ * <button onClick={() => refresh(user)}>Reload</button>;
530
+ *
531
+ * // Imperative settle point
532
+ * const fresh = await refresh(user);
533
+ * ```
534
+ */
535
+ export declare function refresh<T>(target: Refreshable<T>): Promise<T extends (...args: any) => infer V ? V : T>;
536
+ /** Falsy values a truthy predicate result is narrowed against. */
537
+ export type Truthy<T> = Exclude<T, false | 0 | 0n | "" | null | undefined>;
538
+ export interface UntilOptions {
539
+ /** Reject with `TimeoutError` if the predicate has not turned truthy within
540
+ * this many milliseconds. Strongly recommended when the confirming truth
541
+ * arrives over a transport that can drop (sockets, subscriptions). */
542
+ timeout?: number;
543
+ /** Reject with `signal.reason` on abort. */
544
+ signal?: AbortSignal;
545
+ }
546
+ /**
547
+ * Awaits a reactive predicate and resolves the first time it settles *truthy*,
548
+ * with that (narrowed) value. Falsy results and pending async reads both mean
549
+ * "not yet": the subscription stays live and re-evaluates as sources change.
550
+ * If the predicate settles with an error — a throw, or an async source that
551
+ * rejects — the promise rejects with it, as do timeout and abort.
552
+ *
553
+ * Where {@link resolve} answers "what is this value" (first settled value,
554
+ * whatever it is), `until` answers "when does the world confirm this
555
+ * condition". The difference matters inside an `action()`: `yield until(...)`
556
+ * holds the action's transaction — and any optimistic state riding it — open
557
+ * until the condition is independently true.
558
+ *
559
+ * To make that sound, `until`'s predicate reads the AUTHORITATIVE view — and
560
+ * this is the one read-semantics difference from `resolve`, which reads the
561
+ * normal (transaction's own) view where overrides are visible:
562
+ *
563
+ * - **Optimistic overrides are invisible** to the predicate. Your own
564
+ * tentative write can never satisfy your own ack, even on the
565
+ * single-primitive shape where the optimistic store IS the live-fed store.
566
+ * (Derived computeds serve their normal cached values — express the
567
+ * condition over sources of truth, not derived views of the overlay.)
568
+ * - **Everything else reads normally, including uncommitted transition-staged
569
+ * data.** Real data is real wherever it currently lives. This is
570
+ * load-bearing, not a loophole: truth that arrives *into* the open
571
+ * transaction (a `refresh()` this action issued, an entangled landing)
572
+ * stages and cannot commit until the hold releases — a predicate that
573
+ * refused staged reads would deadlock on the very data it is waiting for.
574
+ *
575
+ * This is the acknowledgment mechanism for mutations confirmed on a live data
576
+ * channel (sockets, subscriptions, live queries) rather than by the mutation's
577
+ * own response: correlate by a client-generated id or version in the predicate,
578
+ * and let truth arrive however it arrives — push, refetch, or another tab.
579
+ *
580
+ * Failure composes with action semantics: a rejection is thrown back into the
581
+ * generator at the `yield` point — catchable there, or the action fails and
582
+ * its optimistic state reverts.
583
+ *
584
+ * Must be called *outside* a tracking scope.
585
+ *
586
+ * @example
587
+ * ```ts
588
+ * const send = action(async function* (text: string) {
589
+ * const clientId = crypto.randomUUID();
590
+ * setMessages(m => { m.push({ clientId, text, pending: true }); }); // optimistic
591
+ * await socket.send({ clientId, text }); // fire-and-forget transport
592
+ * // Hold until the live source echoes the write (authoritative view —
593
+ * // the optimistic row above cannot satisfy this):
594
+ * yield until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
595
+ * });
596
+ * ```
597
+ *
598
+ * @param fn a reactive predicate over authoritative state
599
+ * @param options optional `timeout` (ms) and abort `signal`
600
+ */
601
+ export declare function until<T>(fn: () => T, options?: UntilOptions): Promise<Truthy<T>>;
494
602
  /**
495
603
  * Creates an optimistic signal that can be used to optimistically update a value
496
604
  * and then revert it back to the previous value at end of transition.
@@ -4,6 +4,8 @@ export { isWrappable, $TRACK, $PROXY, $TARGET } from "./store.cjs";
4
4
  import type { NoFn, ProjectionOptions, Store, StoreOptions, StoreSetter } from "./store.cjs";
5
5
  import type { Refreshable } from "../core/index.cjs";
6
6
  export { createProjectionNext as createProjection } from "./next/projection.cjs";
7
+ export { registerPatch, registerRowOps, registerSlotPatchNext as registerSlotPatch, patchableRaw } from "./next/patch.cjs";
8
+ export { storeIsShallow, storeHasFamily, storeHasOptimisticFamily } from "./next/store.cjs";
7
9
  export { createOptimisticStoreNext as createOptimisticStore } from "./next/optimistic.cjs";
8
10
  /** Public createStore: plain form `(init, options?)` and derived writable
9
11
  * form `(fn, seed, options?)`. */
@@ -1,20 +1,23 @@
1
+ import { type Transition } from "../../core/scheduler.cjs";
1
2
  import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from "../store.cjs";
2
- import type { StoreNextFamily, StoreNextTarget } from "./target.cjs";
3
+ import type { StoreNextTarget } from "./target.cjs";
4
+ /** #3164 fold: a stamped truth is HELD (masked from ordinary readers until
5
+ * the reveal) only while its transition is live AND retaining optimism —
6
+ * overrides are what make partial-coverage composition a tear. A plain
7
+ * async transition carries no overrides, so downstream computes must see
8
+ * staged values to converge (normal speculation). Resolves merges first:
9
+ * merge unions optimistic nodes/stores into the target. */
10
+ export declare function transitionHoldsOptimism(transition: Transition): boolean;
3
11
  export declare function createOptimisticStoreNext<T extends object = {}>(first: T | ((store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>), second?: NoFn<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Store<T>, set: StoreSetter<T>];
4
12
  /** Diff the draft against the current OPTIMISTIC VIEW (committed + active
5
13
  * overrides — the same view the draft was seeded from) and emit engine writes
6
14
  * for exactly the changed keys. Visible-view diffing keeps no-op writes from
7
15
  * entangling lanes (RUL-10 / opt R38). */
8
16
  export declare function notifyOptimisticWrites(t: StoreNextTarget, pb: Record<PropertyKey, any>): void;
9
- /**
10
- * Landing consumption (RUL-2): fresh authoritative data supersedes every
11
- * tentative override in the family. Mirrors legacy clearProjectionOverride —
12
- * drop the override, clear lane/ownership, notify subscribers whose visible
13
- * value changes (reversion effects go to regular queues via the projection
14
- * write posture the caller holds).
15
- */
16
- export declare function consumeOverridesNext(fam: StoreNextFamily): void;
17
17
  /** Optimistic-view composition for snapshot/deep (O1: snapshot is the CURRENT
18
18
  * view, lane values included; a fresh copy per call during pending windows —
19
- * RUL-12). Returns `src` untouched when no override is active on `t`. */
19
+ * RUL-12). Returns `src` untouched when no override is active on `t`.
20
+ * Authoritative-view reads (until()'s predicate) skip composition entirely:
21
+ * the predicate observes authoritative truth, never the caller's tentative
22
+ * overlay. (Write-side emission callers never run under such a compute.) */
20
23
  export declare function optimisticView(t: StoreNextTarget, src: Record<PropertyKey, any>): Record<PropertyKey, any>;
@@ -0,0 +1,41 @@
1
+ import type { StoreNextTarget } from "./target.cjs";
2
+ import type { RowOps } from "./patch.cjs";
3
+ /**
4
+ * Patch-channel emission seams (pay-for-use). The store/reconcile/optimistic
5
+ * write paths emit through these installed hook objects instead of importing
6
+ * `patch.js` statically, so the channel tree-shakes out of apps that never
7
+ * register a patch consumer.
8
+ *
9
+ * TWO TIERS, armed at registration (patch.js installs them; it is retained
10
+ * only through its registration exports, which only compiled patch-mode
11
+ * output — via the web runtime's driver module — imports):
12
+ * - VALUE hooks (`patchHooks`): record patches. Armed by `registerPatch` —
13
+ * present in any bundle with one eligible template under patch mode.
14
+ * - ROW hooks (`rowHooks`): list structure (row ops, slot ticks, the
15
+ * identity/keyed diff builders in reconcile.js they drag in). Armed by
16
+ * `registerRowOps`/`registerSlotPatchNext` — the LIST driver's
17
+ * registrations, so value-only bundles never retain the row machinery.
18
+ *
19
+ * Soundness: every emission site is guarded by the matching `pc` channel
20
+ * (`pc.p` for value, `pc.ro`/`pc.sp` for rows), and a target can only
21
+ * acquire that channel through the corresponding registration — so each
22
+ * hook object is installed by the time any guard passes. Type-only imports
23
+ * from `patch.js` are erased.
24
+ */
25
+ export interface PatchValueHooks {
26
+ emitPatch(t: StoreNextTarget, next: any, prev: any): void;
27
+ emitPatchLocal(t: StoreNextTarget, next: any, prev: any): void;
28
+ emitPatchOptimistic(t: StoreNextTarget, next: any, prev: any): void;
29
+ hasPatches(): boolean;
30
+ demoteToEffects(t: StoreNextTarget): void;
31
+ }
32
+ export interface PatchRowHooks {
33
+ emitRowOps(t: StoreNextTarget, next: any[], ops: RowOps): void;
34
+ emitSlotPatch(t: StoreNextTarget, index: number, next: any, prev: any): void;
35
+ emitSetterRowOps(t: StoreNextTarget, prevRows: any[], nextRows: any[]): void;
36
+ emitRowOpsOptimistic(t: StoreNextTarget, next: any[] | null, ops: RowOps | null): void;
37
+ }
38
+ export declare let patchHooks: PatchValueHooks | null;
39
+ export declare let rowHooks: PatchRowHooks | null;
40
+ export declare function installPatchHooks(hooks: PatchValueHooks): void;
41
+ export declare function installRowHooks(hooks: PatchRowHooks): void;
@@ -0,0 +1,91 @@
1
+ import type { Owner } from "../../core/types.cjs";
2
+ import { type StoreNextTarget } from "./target.cjs";
3
+ export type PatchFn = (next: any, prev: any, force?: boolean) => void;
4
+ interface PatchEntry {
5
+ fn: PatchFn;
6
+ owner: Owner | null;
7
+ }
8
+ /**
9
+ * Emit a record's visibility transition. Callers gate on `hasPatches()` and
10
+ * `t.d` cheaply; this function re-checks and walks ancestors (§4b).
11
+ */
12
+ export declare function emitPatch(t: StoreNextTarget, next: any, prev: any): void;
13
+ /** Emission for sites that already stand at the record with both sides in
14
+ * hand and have already handled ancestors (the adoption walk descends —
15
+ * parents were visited first), so no bubbling walk. */
16
+ export declare function emitPatchLocal(t: StoreNextTarget, next: any, prev: any): void;
17
+ export declare function emitPatchOptimistic(t: StoreNextTarget, next: any, prev: any): void;
18
+ /** Row-ops emission at OPTIMISTIC (lane) timing: user drafts on an
19
+ * optimistic family must show structure IN FLIGHT — bypassing the
20
+ * transition stash exactly like emitPatchOptimistic. Two forms:
21
+ * - `ops` given (write site): `nextRows` is the draft's intended visible
22
+ * list, ops the identity diff against the pre-write optimistic view.
23
+ * - `ops === null` (revert site): RESYNC — the consumer rebuilds retention
24
+ * by row identity against the live post-revert view, resolved from the
25
+ * target at drain time (overrides are gone by then, so `pb ?? v` IS the
26
+ * committed truth). */
27
+ export declare function emitRowOpsOptimistic(t: StoreNextTarget, nextRows: any[] | null, ops: RowOps | null): void;
28
+ /** Test-only accounting probe: the live registration count must return to
29
+ * baseline across register/unbind/demote cycles. @internal */
30
+ export declare function patchCountForTests(): number;
31
+ export declare function hasPatches(): boolean;
32
+ export declare function registerPatch(record: any, fn: PatchFn): () => void;
33
+ /** Dual-driver bind probe (compiler runtime contract): when `record` is a
34
+ * patchable store record, returns its CURRENT raw backing (the driver's
35
+ * initial force-apply reads it directly — no proxy traffic, no tracking);
36
+ * returns undefined otherwise (driver falls back to the effect path).
37
+ * Not patchable: non-records, non-proxies, accessor-bearing records
38
+ * (patches read raw — getters need tracked evaluation), broken chains. */
39
+ export declare function patchableRaw(record: any): Record<PropertyKey, any> | undefined;
40
+ /** Accessor demotion (design §5): a record that acquires an accessor after
41
+ * registration stops being patchable — reads must go through tracked
42
+ * evaluation. Clears patches and repairs the global count; callers re-drive
43
+ * the pulled bodies (demoteToEffects). */
44
+ export declare function demotePatches(t: StoreNextTarget): PatchEntry[] | null;
45
+ /** The demotion re-drive (re-audit blocker 3): each pulled body becomes the
46
+ * SAME dual-driver effect fallback the web runtime would have chosen had the
47
+ * record carried the accessor at bind — a tracked compute pass (next === prev
48
+ * short-circuits every compare into a pure read THROUGH THE PROXY, so getter
49
+ * dependencies track) plus an untracked force-apply at effect timing.
50
+ *
51
+ * Creation is DEFERRED to the effect phase: the trap that discovers the
52
+ * accessor runs mid-draft, and an effect's initial pass must not read
53
+ * through the proxy inside the write window. The record's own transition
54
+ * for that draft is covered by the new effect's initial force-apply.
55
+ *
56
+ * Known edge (documented): a demoted LIST-ROW body re-drives under its
57
+ * registering owner (the list owner), so per-row severing on removal is
58
+ * lost for demoted rows — the effect lives until the LIST disposes. Rows
59
+ * only demote when user code defines an accessor on a row record at
60
+ * runtime. */
61
+ export declare function demoteToEffects(t: StoreNextTarget): void;
62
+ /** Structural ops for one keyed-array transition. `prefix` rows key-matched
63
+ * in place; for each later index i (absolute), `sources[i - prefix]` is the
64
+ * OLD index its row retained from, or -1 for a new row. `removed` holds the
65
+ * dropped old row values (unbind/teardown handles). Aligned value ticks emit
66
+ * NOTHING — ops exist only when structure changed. */
67
+ export interface RowOps {
68
+ prefix: number;
69
+ sources: number[];
70
+ removed: any[];
71
+ }
72
+ /** `ops === null` is the RESYNC form (optimistic revert): the consumer
73
+ * rebuilds retention by row identity against `next` (the live view). */
74
+ export type RowOpsFn = (next: any[], ops: RowOps | null) => void;
75
+ /** Register a structural-ops consumer on a keyed store array (the list
76
+ * container's channel — what `For` consumes through the seam). */
77
+ export declare function registerRowOps(array: any, fn: RowOpsFn): () => void;
78
+ /** Slot patches (shallow arrays) ride the same apply queue: the walk emits
79
+ * per aligned value-replaced slot; application happens at effect phase under
80
+ * the registration owner's lifetime. */
81
+ export declare function emitSlotPatch(t: StoreNextTarget, index: number, next: any, prev: any): void;
82
+ /** Slot patch for shallow arrays: the reconcile walk emits (index, next,
83
+ * prev) for KEY-ALIGNED value-replaced slots (structure rides row ops), and
84
+ * the emission queues through the patch apply queue — effect-phase timing,
85
+ * transition stamping, disposed-owner drop — like every other channel. */
86
+ export declare function registerSlotPatchNext(arr: any, fn: (index: number, next: any, prev: any) => void): () => void;
87
+ /** Row-ops ride the SAME apply queue/timing as record patches: transition-
88
+ * stamped, applied at effect phase, in emission order (structure before the
89
+ * new rows' own patches can exist; retained rows' value patches commute). */
90
+ export declare function emitRowOps(t: StoreNextTarget, next: any[], ops: RowOps): void;
91
+ export {};
@@ -5,4 +5,4 @@ export declare function createProjectionNext<T extends object = {}>(fn: (draft:
5
5
  * masks the recompute for the tick (core R31 — the manual write wins over a
6
6
  * same-flush dependency change). */
7
7
  export declare function createStoreDerivedNext<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): [Refreshable<Store<T>>, (f: (draft: T) => T | void) => void];
8
- export declare function runProjectionComputedNext<T extends object>(wrappedStore: Store<T>, fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, key: string | ((item: NonNullable<any>) => any) | null, wrapCommit?: (write: () => void) => void, onDraftWrite?: () => void): Computed<void | T>;
8
+ export declare function runProjectionComputedNext<T extends object>(wrappedStore: Store<T>, fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, key: string | ((item: NonNullable<any>) => any) | null, wrapCommit?: (write: () => void, value: T) => void, aroundDraftWrite?: (op: () => void) => void): Computed<void | T>;
@@ -1,3 +1,17 @@
1
+ import type { RowOps } from "./patch.cjs";
2
+ import { type StoreNextTarget } from "./target.cjs";
1
3
  type KeyFn = (item: any) => any;
2
4
  export declare function reconcileNextState(value: any, state: any, key: string | KeyFn | null | undefined, replace?: boolean): void;
5
+ /** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
6
+ * SameValueZero, matching the Map-based matchers (buildRowOps, the adoption
7
+ * window) — NaN keys are equal to themselves, so aligned NaN rows stay
8
+ * aligned in the prefix walk instead of forever misaligning. Adoption and
9
+ * row ops MUST agree on key equality or retained DOM rows go stale. */
10
+ export declare function sameKey(a: any, b: any): boolean;
11
+ export declare function emitSetterRowOps(t: StoreNextTarget, prevRows: any[], nextRows: any[]): void;
12
+ /** Identity-keyed structural diff, returned rather than emitted: shared by
13
+ * the setter channel (regular queue) and the OPTIMISTIC write channel (lane
14
+ * queue) — same retention semantics, different dispatch timing. Returns
15
+ * null when the lists are identity-aligned (no structure changed). */
16
+ export declare function buildIdentityRowOps(prevRows: any[], nextRows: any[]): RowOps | null;
3
17
  export {};
@@ -1,5 +1,7 @@
1
1
  import type { Signal } from "../../core/types.cjs";
2
- import { type StoreNextFamily, type StoreNextTarget } from "./target.cjs";
2
+ import { type StoreNextFamily, type StoreNextTarget, type PatchChannel } from "./target.cjs";
3
+ /** Lazily allocate the patch-channel extension (one literal shape). */
4
+ export declare function pcOf(t: StoreNextTarget): PatchChannel;
3
5
  export declare function wrapNext<T extends Record<PropertyKey, any>>(value: T, parent?: StoreNextTarget | null, parentKey?: PropertyKey | null, fam?: StoreNextFamily | null): T;
4
6
  /** Unwrap our own proxies to their current backing; leave everything else. */
5
7
  export declare function unwrapValue(v: any): any;
@@ -9,6 +11,10 @@ export declare function getKeySetNode(target: StoreNextTarget): Signal<number>;
9
11
  /** Deep-witness bump: any value/shape change on a record with a live deep()
10
12
  * subscriber notifies it. One null check when unused. */
11
13
  export declare function bumpDeep(t: StoreNextTarget): void;
14
+ /** Scanned plainness for patch admission (patchableRaw): runs the one-time
15
+ * accessor scan if it hasn't happened yet — the sticky `a` flag alone is not
16
+ * trustworthy before a scan (it starts false and is discovered lazily). */
17
+ export declare function targetIsPlain(target: StoreNextTarget): boolean;
12
18
  /** Downgrade a prototype-overlay pending backing to the clone path: builds
13
19
  * the real container (committed + overlay writes − deletes) that fold will
14
20
  * SWAP in as the committed backing, exactly as if the draft had started on
@@ -24,9 +30,16 @@ export declare function materializePB(target: StoreNextTarget): void;
24
30
  * authoritative base (R21/R32).
25
31
  */
26
32
  export declare function adoptPB(target: StoreNextTarget, incoming: Record<PropertyKey, any>, eager?: boolean): void;
33
+ /** Parked truth-staged pending backings (#3164 fold): a tentative draft that
34
+ * opens while a folded landing's backing is live moves the staged container
35
+ * here (see ensurePB); the tentative discard in notifyOptimisticWrites
36
+ * restores it in place of the usual null. */
37
+ export declare const stagedTruthPB: WeakMap<StoreNextTarget, Record<PropertyKey, any>>;
27
38
  /** Same logical slot: both values resolve to one (re-pointed) child target —
28
39
  * adoption preserved identity, so the slot did not change (R9). */
29
40
  export declare function targetsEqual(ov: any, nv: any): boolean;
41
+ export declare function arrayStructureChanged(old: any[], neu: any[]): boolean;
42
+ export declare function membershipChanged(old: Record<PropertyKey, any>, neu: Record<PropertyKey, any>): boolean;
30
43
  /**
31
44
  * The fold diff walks SUBSCRIPTION KEYS ONLY (legacy parity: `for key in
32
45
  * nodes`): nodes exist exactly where something tracked, so unobserved data
@@ -56,6 +69,21 @@ export declare function runAuthoritative<T>(fn: () => T): T;
56
69
  /** Active optimistic override on an armed node (armed slot idles at
57
70
  * NOT_PENDING; undefined = unarmed plain node). */
58
71
  export declare function hasActiveOverride(node: Signal<any>): boolean;
72
+ /** The reading computation is until()'s authoritative-view predicate — same
73
+ * source of truth as core read()'s A17 carve-out (`context`, which persists
74
+ * under untrack). optimisticView()'s composition gate consults exactly this:
75
+ * write-side machinery (patch emission, tentative re-application) must keep
76
+ * composing even when it runs inside an authoritative-write bracket. */
77
+ export declare function authoritativeRead(): boolean;
78
+ /** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
79
+ * the projection derive's draft (wrapDraft trap brackets, runAuthoritative;
80
+ * the same posture pair ensurePB classifies drafts by). A source computing
81
+ * the next truth must never read its callers' tentative overlays: a derive
82
+ * continuation's `store.push` computing its index from an action's
83
+ * optimistic row landed truth in the wrong slot and corrupted committed
84
+ * state (#3108). Trap-level overlay serves gate on this so values, length,
85
+ * membership, and keys leave the authoritative view together. */
86
+ export declare function authoritativeServe(): boolean;
59
87
  export type SetStoreNextFunction<T> = (fn: (draft: T) => T | void) => void;
60
88
  /** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
61
89
  * emits write-time notifications at outermost exit, applies returned
@@ -63,8 +91,30 @@ export type SetStoreNextFunction<T> = (fn: (draft: T) => T | void) => void;
63
91
  * projection recomputes legitimately write from inside their computed. */
64
92
  export declare function storeSetterNext<T>(proxy: T, fn: (draft: T) => T | void, guard?: boolean): void;
65
93
  export declare function createStoreNext<T extends Record<PropertyKey, any>>(init: T, shallow?: boolean): [T, SetStoreNextFunction<T>];
94
+ /** True when `proxy` is a SHALLOW store (children served verbatim, slots
95
+ * replaced by reference — #2932). The list driver uses this to choose the
96
+ * slot-patch channel (collected row bodies) over per-record registration. */
97
+ export declare function storeIsShallow(proxy: any): boolean;
98
+ /** True when `proxy` belongs to a projection/optimistic FAMILY. The list
99
+ * driver must DECLINE family arrays (external audit finding): family
100
+ * structural changes never emit row/slot ops (the setter channel is
101
+ * fam-gated; optimistic writes ride node overrides), and the proxy identity
102
+ * is stable so the each-watch cannot catch the change either — an engaged
103
+ * list would freeze on optimistic/projection structural updates. Record-
104
+ * level family patches are unaffected (they have their own emission). */
105
+ export declare function storeHasFamily(proxy: any): boolean;
106
+ /** True when `proxy` belongs to an OPTIMISTIC family specifically. The list
107
+ * driver declines these (audit finding, narrowed): optimistic user writes
108
+ * ride node-level overrides — they never enter the reconcile walk, so no
109
+ * row/slot ops are emitted and an engaged list would freeze on optimistic
110
+ * structural changes. PROJECTION (non-optimistic) families are drivable:
111
+ * their recomputes go through the reconcile walk, whose emissions are
112
+ * transition-stamped in the apply queue like any other (equivalence-matrix
113
+ * gated). Re-admitting optimistic families requires a lane-timed structural
114
+ * emission mirroring emitPatchOptimistic, plus revert resync. */
115
+ export declare function storeHasOptimisticFamily(proxy: any): boolean;
66
116
  /** Tracking deep snapshot (`deep()` for next targets): subscribes to the
67
- * key-set and every property node at every reachable level, then returns the
117
+ * key-set and deep-witness node at every reachable level, then returns the
68
118
  * plain view. Shared references and cycles handled via the visited set. */
69
119
  export declare function deepNext<T>(value: T): T;
70
120
  /**