@solidjs/signals 2.0.0-beta.17 → 2.0.0-beta.18

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.
@@ -0,0 +1,47 @@
1
+ import { type Store } from "./store/store.js";
2
+ import type { Accessor } from "./signals.js";
3
+ /**
4
+ * Declares that in-flight work will change the targeted data: the named
5
+ * slot(s) — and everything DERIVED from them — read as pending
6
+ * (`isPending` → `true`) from the declaration until the surrounding
7
+ * transaction settles or reverts. Marks ride the same status rails as real
8
+ * in-flight async, so pendingness flows through memos and effects like any
9
+ * other pending source, while the marked values themselves stay readable
10
+ * (a mark is a promise of change, not an absence of value). This is the
11
+ * declaration verb of the pending model — additive only. A mark can turn
12
+ * pending ON for data the graph can't see changing yet; nothing can turn
13
+ * pending OFF while a real change is in flight — a quiet `refresh()`
14
+ * re-ask under a mark still reads pending (declaring the reload is what
15
+ * makes it a real question).
16
+ *
17
+ * Targets:
18
+ * - `affects(store)` — a store proxy (any record, root or nested): every
19
+ * record reachable from it at declaration time reads pending, including
20
+ * through captured child proxies (e.g. `<For>` rows). Siblings are
21
+ * untouched; records added after the declaration are not covered.
22
+ * - `affects(record, key)` — exactly the named slot of the record. One key
23
+ * per call (keys do NOT form a path — target the owning record directly).
24
+ * - `affects(accessor)` — a source accessor (signal or memo): the source
25
+ * reads pending.
26
+ *
27
+ * Typically called at the top of an `action` alongside optimistic writes —
28
+ * both are up-front declarations about the same mutation. Outside any
29
+ * transaction the mark is released at the end of the current flush.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const send = action(function* (text: string) {
34
+ * setState(s => { s.messages.push({ text, status: "sending" }); });
35
+ * affects(state.messages.at(-1)!, "status"); // this slot pends until settle
36
+ * yield api.send(text);
37
+ * });
38
+ *
39
+ * const reload = action(function* () {
40
+ * affects(thing); // the whole store pends…
41
+ * refresh(thing); // …over this otherwise-quiet re-ask
42
+ * yield api.done();
43
+ * });
44
+ * ```
45
+ */
46
+ export declare function affects(target: Accessor<unknown> | Store<object>): void;
47
+ export declare function affects<T extends object>(target: Store<T>, key: keyof T): void;
@@ -24,7 +24,7 @@ export declare class RevealController {
24
24
  /**
25
25
  * "Minimally ready" = this group has something visible to show under its own policy.
26
26
  * Used by an enclosing `together` group to decide when it can release.
27
- * - `together`: fully ready (atomic).
27
+ * - `together`: every direct slot is minimally ready.
28
28
  * - `sequential`: the first owned slot is minimally ready (frontier can advance).
29
29
  * - `natural`: any owned slot is minimally ready.
30
30
  */
@@ -128,7 +128,7 @@ export declare function createErrorBoundary<T, U>(fn: () => T, fallback: (error:
128
128
  * own minimal signal).
129
129
  * - `together` — every direct slot is minimally ready.
130
130
  * - `natural` — any direct slot has visible content (leaves on resolve; nested
131
- * composites when fully ready, since natural treats composites as atomic).
131
+ * composites via their own minimal signal).
132
132
  *
133
133
  * @example
134
134
  * ```ts
@@ -1,7 +1,44 @@
1
1
  import { type OptimisticLane } from "./lanes.js";
2
- import type { Computed } from "./types.js";
3
- export declare function settlePendingSource(el: Computed<any>): void;
2
+ import type { Computed, Signal } from "./types.js";
3
+ export declare function settlePendingSource(el: Computed<any>, source?: Computed<any>, snap?: boolean): void;
4
4
  export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
5
5
  export declare function handleAsync<T>(el: Computed<T>, result: T | PromiseLike<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
6
6
  export declare function clearStatus(el: Computed<any>, clearUninitialized?: boolean): void;
7
7
  export declare function notifyStatus(el: Computed<any>, status: number, error: any, blockStatus?: boolean, lane?: OptimisticLane): void;
8
+ /**
9
+ * The pending-source identity of a live `affects()` mark on `node` (lazy,
10
+ * one per node, shared by overlapping registrations via the refcount).
11
+ *
12
+ * A mark rides the SAME status rails as real in-flight async — downstream
13
+ * subscribers hold the sentinel in `_pendingSources` — but under its own
14
+ * identity so the two channels can't clear each other:
15
+ * - `_reask` is permanently `false`: a mark is by definition a declared
16
+ * value change, so `quietPending` never silences a window it participates
17
+ * in — even when the mark rides over an otherwise-quiet `refresh()`
18
+ * re-ask of the same node (the whole point of declaring one).
19
+ * - A landing on the marked node settles only the node's OWN source entry;
20
+ * the sentinel entry survives until the mark's transaction releases it.
21
+ * - The sentinel itself never carries `STATUS_PENDING`, so
22
+ * `transitionComplete` never counts a mark as a blocker of its own
23
+ * transaction (release happens AT settle — self-blocking would deadlock),
24
+ * and reads of the marked node never throw (marks are value-transparent
25
+ * at the source; pendingness is what propagates).
26
+ */
27
+ export declare function getAffectsSentinel(node: Signal<any> | Computed<any>): Computed<any>;
28
+ /**
29
+ * Push a live mark's pendingness downstream from the marked node through the
30
+ * normal status rails. Runs on every registration (dedup in `notifyStatus`
31
+ * stops re-descent at already-covered subscribers). Subscribers that
32
+ * recompute mid-window shed this via `clearStatus` and re-acquire it through
33
+ * the read path (`applyAffectsReads`) — the same shape as real async, where
34
+ * the re-throw on read re-establishes the source.
35
+ */
36
+ export declare function propagateAffectsMark(node: Signal<any> | Computed<any>): void;
37
+ /**
38
+ * Re-establish mark pendingness on a computed that read marked sources
39
+ * during its recompute (`clearStatus` at the top of the commit path wiped
40
+ * any sentinel entries it held). Called by `recompute` after the commit —
41
+ * not before, because setting `_error` earlier would make the commit path
42
+ * treat the node as errored and skip the value write.
43
+ */
44
+ export declare function applyAffectsReads(el: Computed<any>, sources: (Signal<any> | Computed<any>)[]): void;
@@ -10,6 +10,14 @@ export declare const REACTIVE_OPTIMISTIC_DIRTY: number;
10
10
  export declare const REACTIVE_SNAPSHOT_STALE: number;
11
11
  export declare const REACTIVE_LAZY: number;
12
12
  export declare const REACTIVE_MANUAL_WRITE: number;
13
+ /**
14
+ * The pending recompute is a re-ask of the same question: `refresh()` dirtied
15
+ * the node while no tracked input changed value. Cleared whenever a real
16
+ * value-change notification arrives (`insertSubs`), and consumed by
17
+ * `recompute` into the node's `_reask` classification — a quiet (re-ask)
18
+ * pending window does not read as pending (question-scoped pending model).
19
+ */
20
+ export declare const REACTIVE_REASK: number;
13
21
  export declare const CONFIG_OWNED_WRITE: number;
14
22
  export declare const CONFIG_NO_SNAPSHOT: number;
15
23
  export declare const CONFIG_TRANSPARENT: number;
@@ -97,6 +97,15 @@ export declare function setMemo<T>(el: Computed<T>, v: T | ((prev: T) => T)): T;
97
97
  * ```
98
98
  */
99
99
  export declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
100
+ /**
101
+ * Adds a node to the active isPending() probe without reading it. The store's
102
+ * untracked-probe fallback (`witnessAffectsMark`) calls this with `affects()`
103
+ * carrier nodes: an untracked read through a marked record may touch no real
104
+ * signal node at all, so the probe collects the mark's carrier directly.
105
+ *
106
+ * @internal
107
+ */
108
+ export declare function witnessAffects(node: Signal<any> | Computed<any>): void;
100
109
  /**
101
110
  * Keep the lazily-created isPending()/latest() companion nodes in sync with a
102
111
  * new value. Every path that produces a value for `el` — direct set, async
@@ -6,7 +6,7 @@ export interface DevHooks {
6
6
  onStoreNodeUpdate?: (state: any, property: PropertyKey, value: any, prev: any) => void;
7
7
  }
8
8
  export type DiagnosticSeverity = "warn" | "error";
9
- export type DiagnosticCode = "STRICT_READ_UNTRACKED" | "PENDING_ASYNC_UNTRACKED_READ" | "PENDING_ASYNC_FORBIDDEN_SCOPE" | "REACTIVE_WRITE_IN_OWNED_SCOPE" | "ACTION_CALLED_IN_OWNED_SCOPE" | "RUN_WITH_DISPOSED_OWNER" | "NO_OWNER_CLEANUP" | "CLEANUP_IN_FORBIDDEN_SCOPE" | "SETTLED_CLEANUP_UNOWNED" | "PRIMITIVE_IN_FORBIDDEN_SCOPE" | "NO_OWNER_EFFECT" | "NO_OWNER_BOUNDARY" | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION";
9
+ export type DiagnosticCode = "STRICT_READ_UNTRACKED" | "PENDING_ASYNC_UNTRACKED_READ" | "PENDING_ASYNC_FORBIDDEN_SCOPE" | "REACTIVE_WRITE_IN_OWNED_SCOPE" | "ACTION_CALLED_IN_OWNED_SCOPE" | "RUN_WITH_DISPOSED_OWNER" | "NO_OWNER_CLEANUP" | "CLEANUP_IN_FORBIDDEN_SCOPE" | "SETTLED_CLEANUP_UNOWNED" | "PRIMITIVE_IN_FORBIDDEN_SCOPE" | "NO_OWNER_EFFECT" | "NO_OWNER_BOUNDARY" | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "INVALID_AFFECTS_TARGET" | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION";
10
10
  export type DiagnosticKind = "strict-read" | "async" | "write" | "lifecycle" | "owner" | "error";
11
11
  export interface DiagnosticEvent {
12
12
  sequence: number;
@@ -1,5 +1,5 @@
1
1
  export { ContextNotFoundError, NoOwnerError, NotReadyError } from "./error.js";
2
- export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, isPending, latest, updatePendingSignal, updateChildCompanions, refresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.js";
2
+ export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, isPending, latest, refresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.js";
3
3
  export { enableExternalSource, _resetExternalSourceConfig, type ExternalSourceFactory, type ExternalSource, type ExternalSourceConfig } from "./external.js";
4
4
  export { createOwner, createRoot, dispose, getNextChildId, getObserver, getOwner, isDisposed, cleanup, peekNextChildId } from "./owner.js";
5
5
  export { createContext, getContext, setContext, type Context, type ContextRecord } from "./context.js";
@@ -26,6 +26,7 @@ export declare const InvariantHooks: {
26
26
  export declare function devTrackHeldPending(node: AnyNode): void;
27
27
  export declare function devTrackCompanionOwner(node: AnyNode): void;
28
28
  export declare function devTrackOptimistic(node: AnyNode): void;
29
+ export declare function devTrackAffects(node: AnyNode): void;
29
30
  /**
30
31
  * Open/close the sanctioned registration window. Call sites are `__DEV__`
31
32
  * guarded (no prod cost); the code inside the window must not throw.
@@ -33,6 +34,15 @@ export declare function devTrackOptimistic(node: AnyNode): void;
33
34
  export declare function beginAsyncReporterWrites(): void;
34
35
  export declare function endAsyncReporterWrites(): void;
35
36
  export declare function createAsyncReporters(): Map<Computed<any>, Set<Computed<any>>>;
37
+ /**
38
+ * INV-2: a node with an *active* override must be registered for reversion in
39
+ * the queue's or a transition's `_optimisticNodes`. An unregistered active
40
+ * override would survive transition completion forever. Runs at the end of
41
+ * every flush (not just quiescence — the invariant holds mid-transition).
42
+ * (There is no revert-target requirement: authoritative values commit
43
+ * silently into `_value` under the override mask — A17 — so reverting is
44
+ * just dropping the override.)
45
+ */
36
46
  export declare function devCheckActiveOverrides(isRegisteredForRevert: (node: AnyNode) => boolean): void;
37
47
  /** INV-1: an isPending() probe must never leak past its own call. */
38
48
  export declare function devCheckFlushStart(): void;
@@ -34,6 +34,7 @@ export interface Transition {
34
34
  _asyncReporters: Map<Computed<any>, Set<Computed<any>>>;
35
35
  _pendingNodes: Signal<any>[];
36
36
  _optimisticNodes: OptimisticNode[];
37
+ _affectsNodes: OptimisticNode[];
37
38
  _optimisticStores: Set<any>;
38
39
  _actions: Array<Generator<any, any, any> | AsyncGenerator<any, any, any>>;
39
40
  _queueStash: QueueStub;
@@ -78,19 +79,61 @@ export declare class GlobalQueue extends Queue {
78
79
  _pendingNode: Signal<any> | null;
79
80
  _pendingNodes: Signal<any>[];
80
81
  _optimisticNodes: OptimisticNode[];
82
+ _affectsNodes: OptimisticNode[];
81
83
  _optimisticStores: Set<any>;
82
84
  static _update: (el: Computed<unknown>) => void;
83
85
  static _dispose: (el: Computed<unknown>, self: boolean, zombie: boolean) => void;
84
86
  static _runEffect: (el: Computed<unknown>) => void;
85
87
  static _clearOptimisticStore: ((store: any) => void) | null;
88
+ static _releaseAffectsScope: ((node: OptimisticNode) => void) | null;
89
+ static _propagateAffects: ((node: OptimisticNode) => void) | null;
90
+ static _settleAffects: ((node: OptimisticNode) => void) | null;
86
91
  flush(): void;
87
92
  notify(node: Computed<any>, mask: number, flags: number, error?: any): boolean;
88
93
  initTransition(transition?: Transition | null): void;
89
94
  }
90
95
  export declare function queuePendingNode(node: Signal<any>): void;
96
+ export declare function armReaskClear(): void;
91
97
  export declare function insertSubs(node: Signal<any> | Computed<any>, optimistic?: boolean): void;
92
98
  export declare function finalizePureQueue(completingTransition?: Transition | null, incomplete?: boolean): void;
93
99
  export declare function trackOptimisticStore(store: any): void;
100
+ /**
101
+ * Count of live `affects()` registrations across the system (including
102
+ * store-scope inherited marks). Gates the read-path mark check in `read()` so
103
+ * graphs that never use the feature pay one integer compare.
104
+ */
105
+ export declare let activeAffectsMarks: number;
106
+ /**
107
+ * The counting half of a mark, shared by direct registration and store-scope
108
+ * inheritance (a node created inside a live keyless mark's identity scope):
109
+ * bumps the refcount and pokes the node's verdict companions so an
110
+ * already-materialized `false` flips reactively.
111
+ *
112
+ * @internal
113
+ */
114
+ export declare function markAffects(node: OptimisticNode): void;
115
+ /**
116
+ * Registers one `affects()` mark on a node: counts it, records the
117
+ * registration with the current transaction (after initTransition the queue's
118
+ * array aliases the active transition's, mirroring `_optimisticNodes`), and
119
+ * propagates STATUS_PENDING downstream on the status rails so everything
120
+ * DERIVED from the marked data reads pending too. Propagation runs on every
121
+ * registration (not just the first): subscribers gained since an earlier
122
+ * overlapping registration get covered, and dedup stops re-descent early.
123
+ *
124
+ * @internal
125
+ */
126
+ export declare function registerAffectsMark(node: OptimisticNode): void;
127
+ /**
128
+ * Releases one registration. When the node's last mark drops, settles the
129
+ * mark's sentinel out of every downstream `_pendingSources` (waking blocked
130
+ * nodes and re-deriving verdicts along the walk). Companion writes go through
131
+ * the settlement snap (committed, not transition-scoped) so releasing a mark
132
+ * can't open a fresh override window that would itself need settlement.
133
+ *
134
+ * @internal
135
+ */
136
+ export declare function releaseAffectsMark(node: OptimisticNode): void;
94
137
  export declare const globalQueue: GlobalQueue;
95
138
  /**
96
139
  * Synchronously processes the pending reactive queue, or runs `fn` in a synchronous
@@ -48,6 +48,21 @@ export interface RawSignal<T> {
48
48
  _pendingSignal?: Signal<boolean>;
49
49
  _latestValueComputed?: Computed<T>;
50
50
  _parentSource?: Signal<any> | Computed<any>;
51
+ /**
52
+ * Live `affects()` marks on this node (refcount). Non-zero is declared
53
+ * motion: the node reads pending regardless of graph state, until every
54
+ * declaring transaction settles/reverts and releases its mark.
55
+ */
56
+ _affectsCount?: number;
57
+ /**
58
+ * The mark's identity on the pending-source rails (lazy, see
59
+ * `getAffectsSentinel`). Downstream subscribers hold it in
60
+ * `_pendingSources` exactly like a real in-flight source, but with its own
61
+ * identity so a landing or quiet re-ask on the node itself can't clear it.
62
+ */
63
+ _affectsSentinel?: Computed<any>;
64
+ /** Set only on sentinels: the marked node this sentinel stands for. */
65
+ _affectsFor?: Signal<any> | Computed<any>;
51
66
  }
52
67
  export interface FirewallSignal<T> extends RawSignal<T> {
53
68
  _firewall: Computed<any>;
@@ -90,12 +105,14 @@ export interface Computed<T> extends RawSignal<T>, Owner {
90
105
  _child: FirewallSignal<any> | null;
91
106
  _notifyStatus?: (status?: number, error?: any) => void;
92
107
  /**
93
- * Store-wide optimistic mask (count of this firewall's store targets with
94
- * live optimistic writes). Non-zero decrees the whole store settled for
95
- * `isPending`the store is the primitive the mask covers (A20 re-rule
96
- * 2026-07-07c).
108
+ * Question-scoped pending classification of the node's CURRENT pending
109
+ * window: `true` means the in-flight recompute is a re-ask of the same
110
+ * question (refresh/poll/confirm no tracked input changed value), so the
111
+ * shown answer still answers the question and the node reads NOT pending.
112
+ * Set by `recompute` from `REACTIVE_REASK`, cleared on landing
113
+ * (`clearStatus`). Meaningless while not STATUS_PENDING.
97
114
  */
98
- _optimisticMask?: number;
115
+ _reask: boolean;
99
116
  }
100
117
  export interface Root extends Owner {
101
118
  _root: true;
@@ -4,6 +4,7 @@ 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.js";
5
5
  export { createSignal, createMemo, createEffect, createRenderEffect, createTrackedEffect, createReaction, createOptimistic, resolve, onSettled, onCleanup } from "./signals.js";
6
6
  export type { Accessor, SourceAccessor, Setter, Signal, ComputeFunction, EffectFunction, EffectBundle, EffectOptions, SignalOptions, MemoOptions, NoInfer } from "./signals.js";
7
+ export { affects } from "./affects.js";
7
8
  export { mapArray, repeat, type Maybe } from "./map.js";
8
9
  export * from "./store/index.js";
9
10
  export { createLoadingBoundary, createErrorBoundary, createRevealOrder, flatten, type RevealOrder } from "./boundaries.js";
@@ -40,8 +40,8 @@ type DataNodes = Record<PropertyKey, DataNode>;
40
40
  *
41
41
  * @internal
42
42
  */
43
- export declare const $TRACK: unique symbol, $TARGET: unique symbol, $PROXY: unique symbol, $DELETED: unique symbol;
44
- export declare const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_MASKED = "m";
43
+ export declare const $TRACK: unique symbol, $TARGET: unique symbol, $PROXY: unique symbol, $DELETED: unique symbol, $AFFECTS: unique symbol;
44
+ export declare const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p";
45
45
  export type StoreNode = {
46
46
  [$PROXY]: any;
47
47
  [STORE_VALUE]: Record<PropertyKey, any>;
@@ -54,8 +54,6 @@ export type StoreNode = {
54
54
  [STORE_LOOKUP]?: WeakMap<any, any>;
55
55
  [STORE_FIREWALL]?: Computed<any>;
56
56
  [STORE_OPTIMISTIC]?: boolean;
57
- /** This target currently contributes to its firewall's store-wide mask. */
58
- [STORE_MASKED]?: boolean;
59
57
  [STORE_SNAPSHOT_PROPS]?: Record<PropertyKey, any>;
60
58
  };
61
59
  export declare namespace SolidStore {
@@ -83,20 +81,40 @@ export declare function getOverlayLayer(target: StoreNode, property: PropertyKey
83
81
  * override, else held pending value, else committed value.
84
82
  */
85
83
  export declare function visibleNodeValue(node: DataNode): any;
84
+ /**
85
+ * Witness live mark coverage of a record into the active isPending() probe.
86
+ * Tracked reads don't need this — they go through real signal nodes, which
87
+ * carry marks directly (declaration walk or birth inheritance). This covers
88
+ * UNTRACKED probes reading through records whose nodes never materialized
89
+ * (no observer ever subscribed, so no node exists to carry the mark).
90
+ * Callers guard on `pendingCheckActive`, so plain reads never pay for this.
91
+ *
92
+ * @internal
93
+ */
94
+ export declare function witnessAffectsMark(target: StoreNode): void;
95
+ /**
96
+ * Resolves the store nodes an `affects()` declaration marks: with a `key`,
97
+ * the named slot's leaf node (upserted so the mark has an addressable
98
+ * carrier); without, the record's $AFFECTS carrier plus every LIVE node in
99
+ * its subtree (the edges existing readers subscribed through), with the
100
+ * subtree's identities snapshotted into the mark's scope so nodes created
101
+ * during the window — and untracked probes over captured proxies — resolve
102
+ * against it (#2882).
103
+ *
104
+ * @internal
105
+ */
106
+ export declare function getStoreAffectsNodes(target: StoreNode, key?: PropertyKey): DataNode[];
86
107
  export declare function trackSelf(target: StoreNode, symbol?: symbol): void;
87
108
  export declare function notifySelf(target: StoreNode): void;
88
- export declare function getKeys(source: Record<PropertyKey, any>, override: Record<PropertyKey, any> | undefined, enumerable?: boolean): PropertyKey[];
89
- export declare function getPropertyDescriptor(source: Record<PropertyKey, any>, override: Record<PropertyKey, any> | undefined, property: PropertyKey): PropertyDescriptor | undefined;
90
109
  /**
91
- * Store-wide mask bookkeeping (A20 re-rule 2026-07-07c): the store is the
92
- * primitive, so an optimistic write decrees the WHOLE store settled for
93
- * `isPending` firewall, written leaves, untouched siblings, structural
94
- * reads for the lifetime of the override/transition. The firewall carries a
95
- * count of masked targets (nested objects mask independently); companions of
96
- * the firewall and its probed leaves are poked on 0↔1 transitions so an
97
- * already-materialized verdict flips without waiting for another write.
110
+ * The write overlay a walk must read through: optimistic writes shadow
111
+ * regular pending writes, the same resolution order as every proxy trap and
112
+ * `reconcile` (#2850). Merging allocates only in the rare both-present case
113
+ * (a derived optimistic store with an in-flight projection commit).
98
114
  */
99
- export declare function maskStoreTarget(target: StoreNode, on: boolean): void;
115
+ export declare function mergedOverlay(target: StoreNode): Record<PropertyKey, any> | undefined;
116
+ export declare function getKeys(source: Record<PropertyKey, any>, override: Record<PropertyKey, any> | undefined, enumerable?: boolean): PropertyKey[];
117
+ export declare function getPropertyDescriptor(source: Record<PropertyKey, any>, override: Record<PropertyKey, any> | undefined, property: PropertyKey): PropertyDescriptor | undefined;
100
118
  export declare const storeTraps: ProxyHandler<StoreNode>;
101
119
  export declare function storeSetter<T extends object>(store: Store<T>, fn: (draft: T) => T | void): void;
102
120
  /**
@@ -0,0 +1,47 @@
1
+ import { type Store } from "./store/store.cjs";
2
+ import type { Accessor } from "./signals.cjs";
3
+ /**
4
+ * Declares that in-flight work will change the targeted data: the named
5
+ * slot(s) — and everything DERIVED from them — read as pending
6
+ * (`isPending` → `true`) from the declaration until the surrounding
7
+ * transaction settles or reverts. Marks ride the same status rails as real
8
+ * in-flight async, so pendingness flows through memos and effects like any
9
+ * other pending source, while the marked values themselves stay readable
10
+ * (a mark is a promise of change, not an absence of value). This is the
11
+ * declaration verb of the pending model — additive only. A mark can turn
12
+ * pending ON for data the graph can't see changing yet; nothing can turn
13
+ * pending OFF while a real change is in flight — a quiet `refresh()`
14
+ * re-ask under a mark still reads pending (declaring the reload is what
15
+ * makes it a real question).
16
+ *
17
+ * Targets:
18
+ * - `affects(store)` — a store proxy (any record, root or nested): every
19
+ * record reachable from it at declaration time reads pending, including
20
+ * through captured child proxies (e.g. `<For>` rows). Siblings are
21
+ * untouched; records added after the declaration are not covered.
22
+ * - `affects(record, key)` — exactly the named slot of the record. One key
23
+ * per call (keys do NOT form a path — target the owning record directly).
24
+ * - `affects(accessor)` — a source accessor (signal or memo): the source
25
+ * reads pending.
26
+ *
27
+ * Typically called at the top of an `action` alongside optimistic writes —
28
+ * both are up-front declarations about the same mutation. Outside any
29
+ * transaction the mark is released at the end of the current flush.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const send = action(function* (text: string) {
34
+ * setState(s => { s.messages.push({ text, status: "sending" }); });
35
+ * affects(state.messages.at(-1)!, "status"); // this slot pends until settle
36
+ * yield api.send(text);
37
+ * });
38
+ *
39
+ * const reload = action(function* () {
40
+ * affects(thing); // the whole store pends…
41
+ * refresh(thing); // …over this otherwise-quiet re-ask
42
+ * yield api.done();
43
+ * });
44
+ * ```
45
+ */
46
+ export declare function affects(target: Accessor<unknown> | Store<object>): void;
47
+ export declare function affects<T extends object>(target: Store<T>, key: keyof T): void;
@@ -24,7 +24,7 @@ export declare class RevealController {
24
24
  /**
25
25
  * "Minimally ready" = this group has something visible to show under its own policy.
26
26
  * Used by an enclosing `together` group to decide when it can release.
27
- * - `together`: fully ready (atomic).
27
+ * - `together`: every direct slot is minimally ready.
28
28
  * - `sequential`: the first owned slot is minimally ready (frontier can advance).
29
29
  * - `natural`: any owned slot is minimally ready.
30
30
  */
@@ -128,7 +128,7 @@ export declare function createErrorBoundary<T, U>(fn: () => T, fallback: (error:
128
128
  * own minimal signal).
129
129
  * - `together` — every direct slot is minimally ready.
130
130
  * - `natural` — any direct slot has visible content (leaves on resolve; nested
131
- * composites when fully ready, since natural treats composites as atomic).
131
+ * composites via their own minimal signal).
132
132
  *
133
133
  * @example
134
134
  * ```ts
@@ -1,7 +1,44 @@
1
1
  import { type OptimisticLane } from "./lanes.cjs";
2
- import type { Computed } from "./types.cjs";
3
- export declare function settlePendingSource(el: Computed<any>): void;
2
+ import type { Computed, Signal } from "./types.cjs";
3
+ export declare function settlePendingSource(el: Computed<any>, source?: Computed<any>, snap?: boolean): void;
4
4
  export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
5
5
  export declare function handleAsync<T>(el: Computed<T>, result: T | PromiseLike<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
6
6
  export declare function clearStatus(el: Computed<any>, clearUninitialized?: boolean): void;
7
7
  export declare function notifyStatus(el: Computed<any>, status: number, error: any, blockStatus?: boolean, lane?: OptimisticLane): void;
8
+ /**
9
+ * The pending-source identity of a live `affects()` mark on `node` (lazy,
10
+ * one per node, shared by overlapping registrations via the refcount).
11
+ *
12
+ * A mark rides the SAME status rails as real in-flight async — downstream
13
+ * subscribers hold the sentinel in `_pendingSources` — but under its own
14
+ * identity so the two channels can't clear each other:
15
+ * - `_reask` is permanently `false`: a mark is by definition a declared
16
+ * value change, so `quietPending` never silences a window it participates
17
+ * in — even when the mark rides over an otherwise-quiet `refresh()`
18
+ * re-ask of the same node (the whole point of declaring one).
19
+ * - A landing on the marked node settles only the node's OWN source entry;
20
+ * the sentinel entry survives until the mark's transaction releases it.
21
+ * - The sentinel itself never carries `STATUS_PENDING`, so
22
+ * `transitionComplete` never counts a mark as a blocker of its own
23
+ * transaction (release happens AT settle — self-blocking would deadlock),
24
+ * and reads of the marked node never throw (marks are value-transparent
25
+ * at the source; pendingness is what propagates).
26
+ */
27
+ export declare function getAffectsSentinel(node: Signal<any> | Computed<any>): Computed<any>;
28
+ /**
29
+ * Push a live mark's pendingness downstream from the marked node through the
30
+ * normal status rails. Runs on every registration (dedup in `notifyStatus`
31
+ * stops re-descent at already-covered subscribers). Subscribers that
32
+ * recompute mid-window shed this via `clearStatus` and re-acquire it through
33
+ * the read path (`applyAffectsReads`) — the same shape as real async, where
34
+ * the re-throw on read re-establishes the source.
35
+ */
36
+ export declare function propagateAffectsMark(node: Signal<any> | Computed<any>): void;
37
+ /**
38
+ * Re-establish mark pendingness on a computed that read marked sources
39
+ * during its recompute (`clearStatus` at the top of the commit path wiped
40
+ * any sentinel entries it held). Called by `recompute` after the commit —
41
+ * not before, because setting `_error` earlier would make the commit path
42
+ * treat the node as errored and skip the value write.
43
+ */
44
+ export declare function applyAffectsReads(el: Computed<any>, sources: (Signal<any> | Computed<any>)[]): void;
@@ -10,6 +10,14 @@ export declare const REACTIVE_OPTIMISTIC_DIRTY: number;
10
10
  export declare const REACTIVE_SNAPSHOT_STALE: number;
11
11
  export declare const REACTIVE_LAZY: number;
12
12
  export declare const REACTIVE_MANUAL_WRITE: number;
13
+ /**
14
+ * The pending recompute is a re-ask of the same question: `refresh()` dirtied
15
+ * the node while no tracked input changed value. Cleared whenever a real
16
+ * value-change notification arrives (`insertSubs`), and consumed by
17
+ * `recompute` into the node's `_reask` classification — a quiet (re-ask)
18
+ * pending window does not read as pending (question-scoped pending model).
19
+ */
20
+ export declare const REACTIVE_REASK: number;
13
21
  export declare const CONFIG_OWNED_WRITE: number;
14
22
  export declare const CONFIG_NO_SNAPSHOT: number;
15
23
  export declare const CONFIG_TRANSPARENT: number;
@@ -97,6 +97,15 @@ export declare function setMemo<T>(el: Computed<T>, v: T | ((prev: T) => T)): T;
97
97
  * ```
98
98
  */
99
99
  export declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
100
+ /**
101
+ * Adds a node to the active isPending() probe without reading it. The store's
102
+ * untracked-probe fallback (`witnessAffectsMark`) calls this with `affects()`
103
+ * carrier nodes: an untracked read through a marked record may touch no real
104
+ * signal node at all, so the probe collects the mark's carrier directly.
105
+ *
106
+ * @internal
107
+ */
108
+ export declare function witnessAffects(node: Signal<any> | Computed<any>): void;
100
109
  /**
101
110
  * Keep the lazily-created isPending()/latest() companion nodes in sync with a
102
111
  * new value. Every path that produces a value for `el` — direct set, async
@@ -6,7 +6,7 @@ export interface DevHooks {
6
6
  onStoreNodeUpdate?: (state: any, property: PropertyKey, value: any, prev: any) => void;
7
7
  }
8
8
  export type DiagnosticSeverity = "warn" | "error";
9
- export type DiagnosticCode = "STRICT_READ_UNTRACKED" | "PENDING_ASYNC_UNTRACKED_READ" | "PENDING_ASYNC_FORBIDDEN_SCOPE" | "REACTIVE_WRITE_IN_OWNED_SCOPE" | "ACTION_CALLED_IN_OWNED_SCOPE" | "RUN_WITH_DISPOSED_OWNER" | "NO_OWNER_CLEANUP" | "CLEANUP_IN_FORBIDDEN_SCOPE" | "SETTLED_CLEANUP_UNOWNED" | "PRIMITIVE_IN_FORBIDDEN_SCOPE" | "NO_OWNER_EFFECT" | "NO_OWNER_BOUNDARY" | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION";
9
+ export type DiagnosticCode = "STRICT_READ_UNTRACKED" | "PENDING_ASYNC_UNTRACKED_READ" | "PENDING_ASYNC_FORBIDDEN_SCOPE" | "REACTIVE_WRITE_IN_OWNED_SCOPE" | "ACTION_CALLED_IN_OWNED_SCOPE" | "RUN_WITH_DISPOSED_OWNER" | "NO_OWNER_CLEANUP" | "CLEANUP_IN_FORBIDDEN_SCOPE" | "SETTLED_CLEANUP_UNOWNED" | "PRIMITIVE_IN_FORBIDDEN_SCOPE" | "NO_OWNER_EFFECT" | "NO_OWNER_BOUNDARY" | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "INVALID_AFFECTS_TARGET" | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION";
10
10
  export type DiagnosticKind = "strict-read" | "async" | "write" | "lifecycle" | "owner" | "error";
11
11
  export interface DiagnosticEvent {
12
12
  sequence: number;
@@ -1,5 +1,5 @@
1
1
  export { ContextNotFoundError, NoOwnerError, NotReadyError } from "./error.cjs";
2
- export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, isPending, latest, updatePendingSignal, updateChildCompanions, refresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.cjs";
2
+ export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, isPending, latest, refresh, 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";
@@ -26,6 +26,7 @@ export declare const InvariantHooks: {
26
26
  export declare function devTrackHeldPending(node: AnyNode): void;
27
27
  export declare function devTrackCompanionOwner(node: AnyNode): void;
28
28
  export declare function devTrackOptimistic(node: AnyNode): void;
29
+ export declare function devTrackAffects(node: AnyNode): void;
29
30
  /**
30
31
  * Open/close the sanctioned registration window. Call sites are `__DEV__`
31
32
  * guarded (no prod cost); the code inside the window must not throw.
@@ -33,6 +34,15 @@ export declare function devTrackOptimistic(node: AnyNode): void;
33
34
  export declare function beginAsyncReporterWrites(): void;
34
35
  export declare function endAsyncReporterWrites(): void;
35
36
  export declare function createAsyncReporters(): Map<Computed<any>, Set<Computed<any>>>;
37
+ /**
38
+ * INV-2: a node with an *active* override must be registered for reversion in
39
+ * the queue's or a transition's `_optimisticNodes`. An unregistered active
40
+ * override would survive transition completion forever. Runs at the end of
41
+ * every flush (not just quiescence — the invariant holds mid-transition).
42
+ * (There is no revert-target requirement: authoritative values commit
43
+ * silently into `_value` under the override mask — A17 — so reverting is
44
+ * just dropping the override.)
45
+ */
36
46
  export declare function devCheckActiveOverrides(isRegisteredForRevert: (node: AnyNode) => boolean): void;
37
47
  /** INV-1: an isPending() probe must never leak past its own call. */
38
48
  export declare function devCheckFlushStart(): void;