@solidjs/signals 2.0.0-rc.4 → 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 (53) hide show
  1. package/dist/dev.js +1169 -186
  2. package/dist/node.cjs +2041 -1167
  3. package/dist/prod/boundaries.js +4 -1
  4. package/dist/prod/core/async.js +124 -95
  5. package/dist/prod/core/constants.js +55 -1
  6. package/dist/prod/core/core.js +297 -222
  7. package/dist/prod/core/effect.js +28 -28
  8. package/dist/prod/core/error.js +13 -1
  9. package/dist/prod/core/external.js +2 -2
  10. package/dist/prod/core/graph.js +27 -27
  11. package/dist/prod/core/heap.js +30 -30
  12. package/dist/prod/core/lanes.js +32 -32
  13. package/dist/prod/core/optimistic.js +54 -54
  14. package/dist/prod/core/owner.js +34 -34
  15. package/dist/prod/core/scheduler.js +318 -137
  16. package/dist/prod/core/verdict.js +112 -59
  17. package/dist/prod/index.js +3 -3
  18. package/dist/prod/map.js +106 -106
  19. package/dist/prod/signals.js +253 -25
  20. package/dist/prod/store/next/optimistic.js +262 -123
  21. package/dist/prod/store/next/patch.js +6 -6
  22. package/dist/prod/store/next/projection.js +23 -19
  23. package/dist/prod/store/next/store.js +129 -35
  24. package/dist/prod/store/store.js +2 -2
  25. package/dist/types/core/async.d.ts +2 -0
  26. package/dist/types/core/attribution.d.ts +9 -4
  27. package/dist/types/core/constants.d.ts +54 -0
  28. package/dist/types/core/core.d.ts +19 -20
  29. package/dist/types/core/error.d.ts +9 -0
  30. package/dist/types/core/index.d.ts +2 -2
  31. package/dist/types/core/scheduler.d.ts +34 -0
  32. package/dist/types/core/types.d.ts +12 -0
  33. package/dist/types/index.d.ts +3 -3
  34. package/dist/types/signals.d.ts +108 -0
  35. package/dist/types/store/next/optimistic.d.ts +13 -10
  36. package/dist/types/store/next/projection.d.ts +1 -1
  37. package/dist/types/store/next/store.d.ts +22 -0
  38. package/dist/types/store/next/target.d.ts +15 -0
  39. package/dist/types-cjs/core/async.d.cts +2 -0
  40. package/dist/types-cjs/core/attribution.d.cts +9 -4
  41. package/dist/types-cjs/core/constants.d.cts +54 -0
  42. package/dist/types-cjs/core/core.d.cts +19 -20
  43. package/dist/types-cjs/core/error.d.cts +9 -0
  44. package/dist/types-cjs/core/index.d.cts +2 -2
  45. package/dist/types-cjs/core/scheduler.d.cts +34 -0
  46. package/dist/types-cjs/core/types.d.cts +12 -0
  47. package/dist/types-cjs/index.d.cts +3 -3
  48. package/dist/types-cjs/signals.d.cts +108 -0
  49. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  50. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  51. package/dist/types-cjs/store/next/store.d.cts +22 -0
  52. package/dist/types-cjs/store/next/target.d.cts +15 -0
  53. package/package.json +1 -1
@@ -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.
@@ -1,20 +1,23 @@
1
+ import { type Transition } from "../../core/scheduler.js";
1
2
  import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from "../store.js";
2
- import type { StoreNextFamily, StoreNextTarget } from "./target.js";
3
+ import type { StoreNextTarget } from "./target.js";
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>;
@@ -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>;
@@ -30,9 +30,16 @@ export declare function materializePB(target: StoreNextTarget): void;
30
30
  * authoritative base (R21/R32).
31
31
  */
32
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>>;
33
38
  /** Same logical slot: both values resolve to one (re-pointed) child target —
34
39
  * adoption preserved identity, so the slot did not change (R9). */
35
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;
36
43
  /**
37
44
  * The fold diff walks SUBSCRIPTION KEYS ONLY (legacy parity: `for key in
38
45
  * nodes`): nodes exist exactly where something tracked, so unobserved data
@@ -62,6 +69,21 @@ export declare function runAuthoritative<T>(fn: () => T): T;
62
69
  /** Active optimistic override on an armed node (armed slot idles at
63
70
  * NOT_PENDING; undefined = unarmed plain node). */
64
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;
65
87
  export type SetStoreNextFunction<T> = (fn: (draft: T) => T | void) => void;
66
88
  /** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
67
89
  * emits write-time notifications at outermost exit, applies returned
@@ -27,6 +27,17 @@ export interface StoreNextFamily {
27
27
  /** Targets currently carrying active node overrides (landing-consumption
28
28
  * walk, RUL-2: visible landed truth replaces optimism). */
29
29
  overlaid?: Set<any>;
30
+ /** Retaining transactions (#3164 fold ruling): every transaction that made
31
+ * an optimistic setter call on this family and may still be open. While
32
+ * any member is live, truth landings FOLD — they stage into the retaining
33
+ * transaction and reveal atomically at its settle, exactly like a signal
34
+ * landing under an active override. Dead members prune lazily at each
35
+ * landing (retainingTransition). */
36
+ rt?: Set<any>;
37
+ /** Normalized row-key fn (same resolution as the projection channels:
38
+ * `options.key`, "id" default, null = unkeyed). The staged-landing walk
39
+ * reads it: key-matched rows keep their proxy identity across a fold. */
40
+ key?: ((item: any) => any) | null;
30
41
  map: WeakMap<object, StoreNextTarget>;
31
42
  /** The projection computed — assigned after creation (accessor pattern). */
32
43
  node: Computed<any> | null;
@@ -159,6 +170,10 @@ export interface OptStoreHooks {
159
170
  notifyOptimisticWrites(t: any, pb: Record<PropertyKey, any>): void;
160
171
  optimisticView(t: any, src: Record<PropertyKey, any>): Record<PropertyKey, any>;
161
172
  applyTentative(t: any, incoming: any, keyFn: ((item: any) => any) | null): void;
173
+ /** #3164 fold: does this live transaction still retain optimism (armed
174
+ * nodes or tracked stores)? Backs the held-truth masks in next/store.ts so
175
+ * plain-store bundles don't carry the transition-optimism probe. */
176
+ retainsOptimism(t: any): boolean;
162
177
  }
163
178
  export declare let optHooks: OptStoreHooks | null;
164
179
  export declare function setOptHooks(h: OptStoreHooks): void;
@@ -16,6 +16,8 @@ export declare function releaseSettledDependents(el: Computed<any>): void;
16
16
  export declare function settleErroredDependents(el: Computed<any>, error: any): void;
17
17
  export declare function settlePendingSource(el: Computed<any>): void;
18
18
  export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
19
+ /** Fire and clear a node's iterator-flight cancellation hook (#3122). */
20
+ export declare function releaseFlightTeardown(el: Computed<any>): void;
19
21
  export declare function handleAsync<T>(el: Computed<T>, result: T | PromiseLike<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
20
22
  export declare function clearStatus(el: Computed<any>, clearUninitialized?: boolean): void;
21
23
  export declare function notifyStatus(el: Computed<any>, status: number, error: any, blockStatus?: boolean, lane?: OptimisticLane): void;
@@ -60,11 +60,16 @@ export interface RerunEvent {
60
60
  /** Wall time of this run including nested recomputes (ms). */
61
61
  totalMs: number;
62
62
  /**
63
- * Whether the run committed a changed value. A PLAIN memo run with
63
+ * Whether the run produced a changed value. A PLAIN memo run with
64
64
  * `changed: false` was pure waste — the equality cutoff stopped it from
65
- * notifying anyone; an effect run with `changed: false` computed without
66
- * firing its effect phase. Summed as `wastedMs` in costs() (plain,
67
- * non-held runs only see `phase`).
65
+ * notifying anyone. Effects run with `_equals: false` in core (their
66
+ * effect phase re-fires on every recompute), so the engine derives this
67
+ * fact itself: an effect run whose compute output is identical to the
68
+ * previous run's reports `changed: false` — the phase re-fired with the
69
+ * same input, pure waste. Side-effect-only computes (`undefined` output)
70
+ * are exempt: identity of `undefined` proves nothing about their work.
71
+ * Summed as `wastedMs` in costs() (plain, non-held runs only — see
72
+ * `phase`).
68
73
  */
69
74
  changed: boolean;
70
75
  /**
@@ -51,6 +51,60 @@ export declare const CONFIG_CHILD_COMPANIONS: number;
51
51
  * moved into the cold extension (§12), and an unconditional `_x` deref per
52
52
  * marked node measurably taxed the propagation hot path (diamond -22%). */
53
53
  export declare const CONFIG_FW_CHILDREN: number;
54
+ /** Authoritative-view reader (`until()`): while this node computes, reads
55
+ * dodge active optimistic OVERRIDES only — the predicate must observe
56
+ * arriving truth, never the caller's own tentative writes (which would
57
+ * trivially satisfy it). Everything else reads normally, INCLUDING
58
+ * transition-staged `_pendingValue`: staged data is authoritative (optimism
59
+ * lives only in override slots), and a hold that refused staged reads would
60
+ * deadlock on data the open transaction itself is holding (a refresh the
61
+ * action issued lands staged and cannot commit until the hold releases).
62
+ * read() checks the bit on the reading computation (`context`) directly — no
63
+ * ambient flag — so a shared computed the predicate pulls recomputes as
64
+ * itself (no bit) under the normal view, and its cache never forks. */
65
+ export declare const CONFIG_AUTHORITATIVE_READ: number;
66
+ /** Sticky mark: an authoritative-view reader read this node PAST an active
67
+ * override. The ack shape — an authoritative arrival EQUAL to the override —
68
+ * rides paths that are deliberately silent under A17 (every ordinary reader
69
+ * sees the override, so an equal landing changes nothing for them). A marked
70
+ * node notifies those readers on such paths anyway, so the landed truth is
71
+ * seen without re-firing ordinary subscribers. Never cleared — only nodes an
72
+ * until() predicate observed mid-override pay. */
73
+ export declare const CONFIG_AUTHORITATIVE_OBSERVED: number;
74
+ /** Promise-delivery effect (resolve()/until()): commits its computed value
75
+ * directly even when recomputing under its own held transition. These
76
+ * effects deliver applies on a microtask (#2930) instead of the stashed
77
+ * effect queues, so the value must ride the same immediate schedule — a
78
+ * staged value with an immediate apply delivers stale state (resolve) or
79
+ * deadlocks the hold (until). Safe because the node is a private leaf: no
80
+ * subscriber reads an effect's value, only its own apply does. */
81
+ export declare const CONFIG_DIRECT_COMMIT: number;
82
+ /** Fresh-pull reader (awaitable `refresh()`'s waiter effect): a read of a
83
+ * dirty source recomputes it inline even when the height gate defers to the
84
+ * flush. Closes the same-flush ordering race where a waiter created
85
+ * alongside a refresh() mark read the PRE-re-ask value as settled and
86
+ * delivered stale; with the pull, the waiter either parks on the re-ask's
87
+ * pending window (async — woken by the settle walk, which runs on every
88
+ * landing including equal-value ones) or serves its sync answer. resolve()
89
+ * deliberately keeps that race — its contract is "first settled value"
90
+ * (#2930), not "next quiescent state". */
91
+ export declare const CONFIG_FRESH_READ: number;
92
+ /** HELD truth (#3164): this node's staged `_pendingValue` is confirming
93
+ * truth riding a transaction that retains optimism, revealed only at that
94
+ * transaction's settle. Two arming sites, one meaning: the store fold
95
+ * (a landing staged into the retaining transaction) and until()'s
96
+ * flip-entanglement (a foreign carrier's staged write, stolen when it
97
+ * flipped the awaited predicate truthy). Until the reveal, ordinary
98
+ * readers — lane and speculative recomputes included — keep committed:
99
+ * the staging notified subscribers as a plain write, so without the mask
100
+ * a mid-hold recompute composes live optimism with the confirming truth,
101
+ * a frame no timeline contains (GabbeV's union tear). Authoritative
102
+ * readers (until()'s predicate) and latest() tunnel through — the
103
+ * exemption that keeps holds deadlock-free. Override-covered nodes never
104
+ * arm: the override is their display and its revert their notification
105
+ * (A17). Cleared at commit (the commit IS the reveal); subscribers masked
106
+ * during the hold are woken by finalizePureQueue's post-revert pass. */
107
+ export declare const CONFIG_HELD_TRUTH: number;
54
108
  export declare const STATUS_NONE = 0;
55
109
  export declare const STATUS_PENDING: number;
56
110
  export declare const STATUS_ERROR: number;
@@ -1,4 +1,3 @@
1
- import { type Refreshable } from "./constants.cjs";
2
1
  import { type OptimisticLane } from "./lanes.cjs";
3
2
  import type { Computed, FirewallSignal, NodeExtension, NodeOptions, Owner, Signal } from "./types.cjs";
4
3
  export declare const PRIMITIVE_IN_FORBIDDEN_SCOPE_MESSAGE = "[PRIMITIVE_IN_FORBIDDEN_SCOPE] Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled";
@@ -107,6 +106,19 @@ export declare const READ_SLOW: unique symbol;
107
106
  * snapshot / transition / lane / dev-strictRead state all take the full
108
107
  * resolution. Anything slow returns READ_SLOW; the caller then calls read().
109
108
  */
109
+ /**
110
+ * Wake only authoritative-view readers (until() predicates) subscribed to `el`.
111
+ * The A17-silent ack paths — an authoritative arrival equal to the active
112
+ * override — use this so the predicate re-evaluates without re-firing
113
+ * ordinary subscribers whose visible (override) value did not change.
114
+ * Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
115
+ * installed at first until() call — apps that never use until() shake it.
116
+ */
117
+ export declare function notifyAuthoritativeObservers(el: Signal<any> | Computed<any>): void;
118
+ /** Installs the until() machinery hook. Idempotent; called by until() before
119
+ * any authoritative-view read happens (same late-binding contract as the
120
+ * optimistic engine). */
121
+ export declare function installAuthoritativeRead(): void;
110
122
  export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW;
111
123
  export declare function read<T>(el: Signal<T> | Computed<T>): T;
112
124
  /**
@@ -155,23 +167,10 @@ export declare function setMemo<T>(el: Computed<T>, v: T | ((prev: T) => T)): T;
155
167
  export declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
156
168
  export declare function staleValues<T>(fn: () => T, set?: boolean): T;
157
169
  /**
158
- * Invalidates one reactive source, forcing it to re-execute even if its inputs
159
- * haven't changed.
160
- *
161
- * Pass either a Solid-created accessor or a projected store created from
162
- * `createStore(fn, ...)` / `createProjection(...)`. `refresh()` is a
163
- * write-like invalidation operation: it does not read the target's value, and
164
- * refreshing a plain signal accessor is a no-op.
165
- *
166
- * Use it to invalidate cached async values (e.g. force a re-fetch) without
167
- * tearing the consumer down.
168
- *
169
- * @example
170
- * ```ts
171
- * const user = createMemo(async () => fetch(`/users/${id()}`).then(r => r.json()));
172
- *
173
- * // Re-fetch on demand
174
- * <button onClick={() => refresh(user)}>Reload</button>
175
- * ```
170
+ * Core marking half of `refresh()` (the public wrapper lives in signals.ts
171
+ * it validates the target, marks through here, then builds the quiescence
172
+ * promise on the resolve()/until() effect machinery). Flags the node's next
173
+ * recompute as a quiet re-ask and schedules it; no-ops for non-derived or
174
+ * disposed targets and for same-tick manual writes.
176
175
  */
177
- export declare function refresh<T>(target: Refreshable<T>): void;
176
+ export declare function markRefresh(node: Computed<any>): void;
@@ -39,6 +39,15 @@ export declare class StatusError extends Error {
39
39
  }
40
40
  /** Return the user's error from an internal status wrapper. */
41
41
  export declare function unwrapStatusError(error: unknown): unknown;
42
+ /**
43
+ * Rejection value of `until(fn, { timeout })` when the predicate does not turn
44
+ * truthy within the window. Inside an `action()`, the rejection is thrown back
45
+ * in at the `yield` point — catchable there, or the action fails and its
46
+ * optimistic state reverts.
47
+ */
48
+ export declare class TimeoutError extends Error {
49
+ constructor(message?: string);
50
+ }
42
51
  export declare class NoOwnerError extends Error {
43
52
  constructor();
44
53
  }
@@ -1,5 +1,5 @@
1
- export { ContextNotFoundError, NoOwnerError, NotReadyError } from "./error.cjs";
2
- export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, refresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.cjs";
1
+ export { ContextNotFoundError, NoOwnerError, NotReadyError, TimeoutError } from "./error.cjs";
2
+ export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, installAuthoritativeRead, markRefresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.cjs";
3
3
  export { enableExternalSource, _resetExternalSourceConfig, type ExternalSourceFactory, type ExternalSource, type ExternalSourceConfig } from "./external.cjs";
4
4
  export { createOwner, createRoot, dispose, getNextChildId, getObserver, getOwner, isDisposed, cleanup, peekNextChildId } from "./owner.cjs";
5
5
  export { createContext, getContext, setContext, type Context, type ContextRecord } from "./context.cjs";
@@ -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
@@ -112,6 +130,10 @@ export declare class GlobalQueue extends Queue {
112
130
  static _laneReadsCommitted: ((el: OptimisticNode, owner: OptimisticNode, c: Computed<any>) => boolean) | null;
113
131
  static _recomputeLane: ((el: Computed<any>, own: boolean) => OptimisticLane | null | false) | null;
114
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;
115
137
  static _laneAsyncSettled: ((el: Computed<any>) => void) | null;
116
138
  static _trackOptimisticStore: ((store: any) => void) | null;
117
139
  flush(): void;
@@ -190,3 +212,15 @@ export declare function flush<T>(fn: () => T): T;
190
212
  export declare function currentTransition(transition: Transition): Transition;
191
213
  export declare function setActiveTransition(transition: Transition | null): void;
192
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.