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

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 (56) hide show
  1. package/dist/dev.js +2743 -694
  2. package/dist/node.cjs +6419 -4234
  3. package/dist/prod/affects.js +222 -0
  4. package/dist/prod/boundaries.js +568 -0
  5. package/dist/prod/core/action.js +83 -0
  6. package/dist/prod/core/async.js +394 -0
  7. package/dist/prod/core/constants.js +78 -0
  8. package/dist/prod/core/context.js +67 -0
  9. package/dist/prod/core/core.js +772 -0
  10. package/dist/prod/core/dev.js +3 -0
  11. package/dist/prod/core/effect.js +145 -0
  12. package/dist/prod/core/error.js +59 -0
  13. package/dist/prod/core/external.js +98 -0
  14. package/dist/prod/core/graph.js +91 -0
  15. package/dist/prod/core/heap.js +132 -0
  16. package/dist/prod/core/invariants.js +42 -0
  17. package/dist/prod/core/lanes.js +130 -0
  18. package/dist/prod/core/optimistic.js +265 -0
  19. package/dist/prod/core/owner.js +293 -0
  20. package/dist/prod/core/scheduler.js +689 -0
  21. package/dist/prod/core/verdict.js +293 -0
  22. package/dist/prod/index.js +45 -0
  23. package/dist/prod/map.js +264 -0
  24. package/dist/prod/signals.js +389 -0
  25. package/dist/prod/store/optimistic.js +149 -0
  26. package/dist/prod/store/projection.js +184 -0
  27. package/dist/prod/store/reconcile.js +392 -0
  28. package/dist/prod/store/store.js +739 -0
  29. package/dist/prod/store/storePath.js +103 -0
  30. package/dist/prod/store/utils.js +297 -0
  31. package/dist/types/affects.d.ts +1 -1
  32. package/dist/types/boundaries.d.ts +6 -6
  33. package/dist/types/core/async.d.ts +4 -38
  34. package/dist/types/core/core.d.ts +12 -78
  35. package/dist/types/core/external.d.ts +0 -30
  36. package/dist/types/core/heap.d.ts +8 -0
  37. package/dist/types/core/index.d.ts +3 -2
  38. package/dist/types/core/optimistic.d.ts +6 -0
  39. package/dist/types/core/owner.d.ts +8 -0
  40. package/dist/types/core/scheduler.d.ts +39 -34
  41. package/dist/types/core/verdict.d.ts +2 -0
  42. package/dist/types/store/projection.d.ts +0 -1
  43. package/dist/types-cjs/affects.d.cts +1 -1
  44. package/dist/types-cjs/boundaries.d.cts +6 -6
  45. package/dist/types-cjs/core/async.d.cts +4 -38
  46. package/dist/types-cjs/core/core.d.cts +12 -78
  47. package/dist/types-cjs/core/external.d.cts +0 -30
  48. package/dist/types-cjs/core/heap.d.cts +8 -0
  49. package/dist/types-cjs/core/index.d.cts +3 -2
  50. package/dist/types-cjs/core/optimistic.d.cts +6 -0
  51. package/dist/types-cjs/core/owner.d.cts +8 -0
  52. package/dist/types-cjs/core/scheduler.d.cts +39 -34
  53. package/dist/types-cjs/core/verdict.d.cts +2 -0
  54. package/dist/types-cjs/store/projection.d.cts +0 -1
  55. package/package.json +8 -6
  56. package/dist/prod.js +0 -4637
@@ -0,0 +1,389 @@
1
+ import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, read, untrack } from "./core/core.js";
2
+
3
+ import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
4
+
5
+ import { globalQueue } from "./core/scheduler.js";
6
+
7
+ import { CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH, CONFIG_AUTO_DISPOSE } from "./core/constants.js";
8
+
9
+ import "./core/invariants.js";
10
+
11
+ import "./core/verdict.js";
12
+
13
+ import { effect, trackedEffect } from "./core/effect.js";
14
+
15
+ import { installOptimisticEngine } from "./core/optimistic.js";
16
+
17
+ /**
18
+ * Low-level reactive-cleanup primitive. Registers a callback that runs when
19
+ * the surrounding owner is disposed.
20
+ *
21
+ * **In 2.0 user code this is rare.** The two cases where you might reach for
22
+ * it have better-shaped tools:
23
+ *
24
+ * - **Component lifecycle (mount/unmount, listeners, intervals):** use
25
+ * {@link onSettled} and **return** a cleanup function. Setup and teardown
26
+ * stay paired in one block. This replaces the 1.x `onMount` + `onCleanup`
27
+ * pairing.
28
+ * - **Cleanup tied to an effect run:** `onCleanup` does not belong in
29
+ * `createEffect`'s apply phase. If a compute phase genuinely needs per-run
30
+ * teardown, that's usually a sign the work should be a memo/projection
31
+ * instead, or moved to `onSettled` if it's lifecycle-shaped.
32
+ *
33
+ * Where `onCleanup` is the right tool is **library / custom-primitive
34
+ * internals** — coordinating disposal inside a `createRoot` body, or wiring
35
+ * cleanup to a captured owner via `runWithOwner` from a custom factory.
36
+ * Application code rarely needs to write any of those shapes directly.
37
+ *
38
+ * Must be called inside an owner. Calling outside an owner is a no-op (with a
39
+ * dev-mode warning).
40
+ *
41
+ * Cannot be used inside `createTrackedEffect` or `onSettled` — return a
42
+ * cleanup function from the callback body instead.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // Library shape: thread a resource's disposal into a *captured* owner
47
+ * // from a factory that has no settle-phase setup of its own. `onSettled`
48
+ * // would queue a callback we don't need; `onCleanup` is the leaner
49
+ * // primitive when the only job is "register disposal on this owner".
50
+ * function bindToOwner<T extends { dispose(): void }>(owner: Owner, resource: T): T {
51
+ * runWithOwner(owner, () => onCleanup(() => resource.dispose()));
52
+ * return resource;
53
+ * }
54
+ * ```
55
+ */ function onCleanup(e) {
56
+ return cleanup(e);
57
+ }
58
+
59
+ function accessor(e) {
60
+ const t = read.bind(null, e);
61
+ t[$REFRESH] = e;
62
+ return t;
63
+ }
64
+
65
+ function createSignal(e, t) {
66
+ if (typeof e === "function") {
67
+ const n = computed(e, t);
68
+ n.U &= ~CONFIG_AUTO_DISPOSE;
69
+ return [ accessor(n), setMemo.bind(null, n) ];
70
+ }
71
+ const n = signal(e, t);
72
+ return [ accessor(n), setSignal.bind(null, n) ];
73
+ }
74
+
75
+ /**
76
+ * Creates a readonly derived reactive memoized signal.
77
+ *
78
+ * ```typescript
79
+ * const value = createMemo<T>(compute, options?: MemoOptions<T>);
80
+ * ```
81
+ * @param compute a function that receives its previous value and returns a new value used to react on a computation
82
+ * @param options `MemoOptions` -- id, name, equals, unobserved, lazy
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const [first, setFirst] = createSignal("Ada");
87
+ * const [last, setLast] = createSignal("Lovelace");
88
+ *
89
+ * const fullName = createMemo(() => `${first()} ${last()}`);
90
+ *
91
+ * fullName(); // "Ada Lovelace"
92
+ * ```
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * // Async memo — reads surface as pending inside <Loading>
97
+ * const user = createMemo(async () => {
98
+ * const res = await fetch(`/users/${id()}`);
99
+ * return res.json();
100
+ * });
101
+ * ```
102
+ *
103
+ * @description https://docs.solidjs.com/reference/basic-reactivity/create-memo
104
+ */
105
+ // NoInfer keeps the previous-value parameter from influencing T inference, so
106
+ // the memo/effect result type is still driven by the compute return type.
107
+ function createMemo(e, t) {
108
+ return accessor(computed(e, t));
109
+ }
110
+
111
+ function createEffect(e, t, n) {
112
+ effect(e, t.effect || t, t.error, {
113
+ user: true,
114
+ ...n
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Creates a reactive computation that runs during the render phase as DOM elements
120
+ * are created and updated but not necessarily connected.
121
+ *
122
+ * Same compute / effect split as `createEffect`, but scheduled inside the render
123
+ * queue rather than after it. Reach for this only when authoring renderer
124
+ * plumbing (custom DOM bindings, JSX-generated `insert()` / `spread()` calls).
125
+ * App code should use `createEffect`.
126
+ *
127
+ * ```typescript
128
+ * createRenderEffect<T>(compute, effectFn, options?: EffectOptions);
129
+ * ```
130
+ * @param compute a function that receives its previous value and returns a new value used to react on a computation
131
+ * @param effectFn a function that receives the new value and is used to perform side effects
132
+ * @param options `EffectOptions` -- name, defer, schedule
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * // Custom directive: bind an element's textContent to a reactive source.
137
+ * function bindText(el: HTMLElement, source: () => string) {
138
+ * createRenderEffect(
139
+ * () => source(),
140
+ * value => { el.textContent = value; }
141
+ * );
142
+ * }
143
+ * ```
144
+ *
145
+ * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
146
+ */ function createRenderEffect(e, t, n) {
147
+ effect(e, t, undefined, n);
148
+ }
149
+
150
+ /**
151
+ * Creates a tracked reactive effect where dependency tracking and side effects happen
152
+ * in the same scope.
153
+ *
154
+ * WARNING: Because tracking and effects happen in the same scope, this primitive
155
+ * may run multiple times for a single change or show tearing (reading inconsistent
156
+ * state). Use only when dynamic subscription patterns require same-scope tracking.
157
+ *
158
+ * ```typescript
159
+ * createTrackedEffect(compute, options?: { name?: string });
160
+ * ```
161
+ * @param compute a function that contains reactive reads to track and returns an optional cleanup function to run on disposal or before next execution
162
+ * @param options -- name
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * createTrackedEffect(() => {
167
+ * const target = focusedNode();
168
+ * if (!target) return;
169
+ *
170
+ * const handler = () => log(target.value());
171
+ * target.on("change", handler);
172
+ *
173
+ * return () => target.off("change", handler);
174
+ * });
175
+ * ```
176
+ *
177
+ * @description https://docs.solidjs.com/reference/secondary-primitives/create-tracked-effect
178
+ */ function createTrackedEffect(e, t) {
179
+ trackedEffect(e, t);
180
+ }
181
+
182
+ /**
183
+ * Creates a reactive computation that runs after the render phase with flexible tracking.
184
+ *
185
+ * ```typescript
186
+ * const track = createReaction(effectFn, options?: EffectOptions);
187
+ * track(() => { // reactive reads });
188
+ * ```
189
+ * @param effectFn a function (or `EffectBundle`) that is called when tracked function is invalidated
190
+ * @param options `EffectOptions` -- name, defer
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const [count, setCount] = createSignal(0);
195
+ *
196
+ * const track = createReaction(() => {
197
+ * console.log("count changed once, re-arm to listen again");
198
+ * track(() => count()); // re-arm
199
+ * });
200
+ *
201
+ * track(() => count()); // initial arm
202
+ *
203
+ * setCount(1); // logs once, reaction re-armed for next change
204
+ * ```
205
+ *
206
+ * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
207
+ */ function createReaction(e, t) {
208
+ let n = undefined;
209
+ cleanup(() => n?.());
210
+ const c = getOwner();
211
+ // The currently armed effect node. `track()` replaces the previous
212
+ // subscription (1.x semantics): without disposing the superseded arm, its
213
+ // sources stayed live (firing the callback for replaced dependencies), each
214
+ // accumulated arm delivered its own fire, and un-fired arms leaked as live
215
+ // effect nodes until the owner disposed (#2861).
216
+ let r;
217
+ return o => {
218
+ if (r) {
219
+ dispose(r);
220
+ r = undefined;
221
+ }
222
+ runWithOwner(c, () => {
223
+ effect(() => (o(), r = getOwner()), t => {
224
+ r = undefined;
225
+ n?.();
226
+ const c = (e.effect || e)?.();
227
+ if (false && c !== undefined && typeof c !== "function") ;
228
+ n = c;
229
+ dispose(t);
230
+ }, e.error, {
231
+ ...false ? {
232
+ ...t,
233
+ name: t?.name ?? "effect"
234
+ } : t,
235
+ user: true,
236
+ defer: true
237
+ });
238
+ });
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Awaits a reactive expression and returns its first fully-settled value as a
244
+ * `Promise`. Pending async reads (`createMemo` returning a promise, etc.) are
245
+ * waited on; once the expression returns synchronously without `NotReadyError`
246
+ * the promise resolves with that value. If the expression settles with an
247
+ * error instead — including an async source that rejects — the promise
248
+ * rejects with it.
249
+ *
250
+ * Must be called *outside* a tracking scope — it doesn't subscribe, it just
251
+ * resolves the current value once.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const user = createMemo(() => fetch(`/users/${id()}`).then(r => r.json()));
256
+ *
257
+ * // outside any reactive scope
258
+ * const initial = await resolve(() => user());
259
+ * ```
260
+ *
261
+ * @param fn a reactive expression to resolve
262
+ */ function resolve(e) {
263
+ return new Promise((t, n) => {
264
+ createRoot(c => {
265
+ // A user effect rather than a bare computed: computeds are pull-based and
266
+ // are only re-enqueued when a pending source *resolves* — a rejection just
267
+ // marks them errored, so nothing would re-run and the promise would never
268
+ // settle (#2842). The effect's error channel is notified on rejection.
269
+ effect(e, e => {
270
+ t(e);
271
+ c();
272
+ }, e => {
273
+ // The error arm already unwraps StatusError (#2840) — `err` is the
274
+ // user's original error, matching what error boundaries expose.
275
+ n(e);
276
+ c();
277
+ }, {
278
+ user: true
279
+ });
280
+ });
281
+ });
282
+ }
283
+
284
+ function createOptimistic(e, t) {
285
+ // Install before the node exists: only engine-installed programs can carry
286
+ // an _overrideValue slot (same runtime-install pattern as
287
+ // GlobalQueue._clearOptimisticStore in createOptimisticStore).
288
+ installOptimisticEngine();
289
+ if (typeof e === "function") {
290
+ const n = optimisticComputed(e, t);
291
+ n.U &= ~CONFIG_AUTO_DISPOSE;
292
+ return [ accessor(n), setSignal.bind(null, n) ];
293
+ }
294
+ const n = optimisticSignal(e, t);
295
+ return [ accessor(n), setSignal.bind(null, n) ];
296
+ }
297
+
298
+ /**
299
+ * Schedules `callback` to run **once** after the reactive graph has fully
300
+ * settled — i.e. once every pending async read inside the current owner has
301
+ * resolved and the queue has flushed. Each call registers a single fire; it
302
+ * does not create an ongoing subscription.
303
+ *
304
+ * The canonical lifecycle primitive in 2.0. Three main usages:
305
+ *
306
+ * - **Component-level setup-and-teardown** *(the most common shape)*: run
307
+ * setup after the component's first stable render and **return a cleanup
308
+ * function** to dispose it on owner disposal. This is the replacement for
309
+ * the 1.x `onMount` + `onCleanup` pairing — setup and teardown live in one
310
+ * block, and `onCleanup` is no longer the right tool for component
311
+ * bodies. (`onMount` no longer exists in 2.0.)
312
+ * - **Post-settle "ready" hook:** run once after a component's first stable
313
+ * render — analytics ping, focus, scroll-into-view, etc. No cleanup needed.
314
+ * - **Inside an event handler:** schedule work to run after the action /
315
+ * transition triggered by the event has completed.
316
+ *
317
+ * Reactive reads inside the callback are *not* tracked — to react to
318
+ * subsequent settles, register a new `onSettled` each time.
319
+ *
320
+ * `onCleanup` is **not** allowed inside the callback — return a cleanup
321
+ * function instead. The returned cleanup runs on owner disposal.
322
+ *
323
+ * A cleanup return is only honored when `onSettled` is called from an **owned**
324
+ * scope (e.g. a component body). When it fires out of band from an *unowned*
325
+ * scope — an event handler, a tracked effect, or another `onSettled` — there is
326
+ * no owner lifecycle to bind a cleanup to; returning one is a dev-mode error
327
+ * (and is dropped in production). Use the post-settle/event-handler forms below
328
+ * for one-shot work, and keep setup-with-teardown in an owned scope.
329
+ *
330
+ * @example
331
+ * ```tsx
332
+ * // Component-level setup + teardown — replaces onMount + onCleanup.
333
+ * // Subscribe to an external source on mount, unsubscribe on dispose.
334
+ * function useViewportWidth() {
335
+ * const [width, setWidth] = createSignal(window.innerWidth);
336
+ * onSettled(() => {
337
+ * const onResize = () => setWidth(window.innerWidth);
338
+ * window.addEventListener("resize", onResize);
339
+ * return () => window.removeEventListener("resize", onResize);
340
+ * });
341
+ * return width;
342
+ * }
343
+ * ```
344
+ *
345
+ * @example
346
+ * ```tsx
347
+ * // Post-settle "ready" hook — no cleanup needed.
348
+ * function Dashboard() {
349
+ * const data = createMemo(async () => fetchData());
350
+ *
351
+ * onSettled(() => {
352
+ * analytics.track("dashboard.ready");
353
+ * });
354
+ *
355
+ * return <Loading fallback={<Spinner />}><pre>{data()}</pre></Loading>;
356
+ * }
357
+ * ```
358
+ *
359
+ * @example
360
+ * ```tsx
361
+ * // Event-handler — runs after the action settles.
362
+ * function SaveButton() {
363
+ * const save = action(function* () {
364
+ * yield api.save();
365
+ * });
366
+ *
367
+ * const handleClick = () => {
368
+ * save();
369
+ * onSettled(() => toast("Saved!"));
370
+ * };
371
+ *
372
+ * return <button onClick={handleClick}>Save</button>;
373
+ * }
374
+ * ```
375
+ *
376
+ * @param callback Function to run; may return a cleanup function that fires
377
+ * on owner disposal
378
+ */ function onSettled(e) {
379
+ const t = getOwner();
380
+ t && !(t.U & CONFIG_CHILDREN_FORBIDDEN) ? createTrackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, () => {
381
+ // Unowned, out-of-band fire (no owner, or a children-forbidden one this
382
+ // one-shot must not bind to): a returned cleanup has no lifecycle to
383
+ // attach to. Reject it in dev; in production the return is simply
384
+ // dropped — never bound to an unrelated owner or run eagerly.
385
+ e();
386
+ });
387
+ }
388
+
389
+ export { accessor, createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onSettled, resolve };
@@ -0,0 +1,149 @@
1
+ import { computed } from "../core/core.js";
2
+
3
+ import { CONFIG_AUTO_DISPOSE, NOT_PENDING } from "../core/constants.js";
4
+
5
+ import { GlobalQueue, schedule, setProjectionWriteActive, insertSubs, projectionWriteActive } from "../core/scheduler.js";
6
+
7
+ import "../core/invariants.js";
8
+
9
+ import "../core/verdict.js";
10
+
11
+ import "../core/effect.js";
12
+
13
+ import { installOptimisticEngine } from "../core/optimistic.js";
14
+
15
+ import { runProjectionComputed } from "./projection.js";
16
+
17
+ import { storeSetter, $TARGET, STORE_OPTIMISTIC_OVERRIDE, STORE_NODE, getOverlayLayer, STORE_VALUE, $DELETED, isWrappable, wrap, visibleNodeValue, $TRACK, notifySelf, STORE_WRAP, createStoreProxy, storeTraps, STORE_LOOKUP, STORE_OPTIMISTIC, STORE_FIREWALL } from "./store.js";
18
+
19
+ function createOptimisticStore(t, e, r) {
20
+ // Register clear function with scheduler; store nodes marked
21
+ // STORE_OPTIMISTIC take the engine's write path, so install it before any
22
+ // node can be created.
23
+ installOptimisticEngine();
24
+ GlobalQueue.Kt ||= clearOptimisticStores;
25
+ const i = typeof t === "function";
26
+ const o = i ? e : t;
27
+ const c = i ? t : undefined;
28
+ // Create optimistic projection store
29
+ const {store: n} = createOptimisticProjectionInternal(c, o, r);
30
+ return [ n, t => storeSetter(n, t) ];
31
+ }
32
+
33
+ // Clear the optimistic overrides of a settling batch of stores and notify
34
+ // signals. Owns the whole batch (iterate + clear + reschedule) so the
35
+ // scheduler's flush tail carries only a size-guarded hook call.
36
+ function clearOptimisticStores(t) {
37
+ for (const e of t) {
38
+ const t = e[$TARGET];
39
+ if (t?.[STORE_OPTIMISTIC_OVERRIDE]) clearOptimisticOverride(t);
40
+ }
41
+ t.clear();
42
+ schedule();
43
+ }
44
+
45
+ function clearOptimisticOverride(t) {
46
+ const e = t[STORE_OPTIMISTIC_OVERRIDE];
47
+ if (!e) return;
48
+ const r = t[STORE_NODE];
49
+ delete t[STORE_OPTIMISTIC_OVERRIDE];
50
+ // Notify signals for all overridden properties
51
+ // Use projectionWriteActive to bypass optimistic signal behavior (no lane creation)
52
+ // This ensures reversion effects go to regular queues, not lane queues
53
+ const i = projectionWriteActive;
54
+ setProjectionWriteActive(true);
55
+ try {
56
+ if (r) {
57
+ for (const i of Reflect.ownKeys(e)) {
58
+ if (r[i]) {
59
+ const e = r[i];
60
+ // Clear lane association so effects go to regular queue
61
+ e.Je = undefined;
62
+ // Re-read from base — the optimistic layer was deleted above, so the
63
+ // overlay resolves to STORE_OVERRIDE or STORE_VALUE.
64
+ const o = getOverlayLayer(t, i);
65
+ const c = o ? o[i] : t[STORE_VALUE][i];
66
+ const n = c === $DELETED ? undefined : c;
67
+ const s = isWrappable(n) ? wrap(n, t) : n;
68
+ const O = visibleNodeValue(e);
69
+ e.be = NOT_PENDING;
70
+ e.ge = NOT_PENDING;
71
+ e.Ue = s;
72
+ if (!e.Ge || !e.Ge(O, s)) {
73
+ insertSubs(e, true);
74
+ schedule();
75
+ }
76
+ }
77
+ }
78
+ // Notify $TRACK
79
+ if (r[$TRACK]) {
80
+ r[$TRACK].Je = undefined;
81
+ notifySelf(t);
82
+ }
83
+ }
84
+ } finally {
85
+ setProjectionWriteActive(i);
86
+ }
87
+ }
88
+
89
+ function createOptimisticProjectionInternal(t, e, r) {
90
+ let i;
91
+ const o = new WeakMap;
92
+ const wrapper = t => {
93
+ t[STORE_WRAP] = wrapProjection;
94
+ t[STORE_LOOKUP] = o;
95
+ t[STORE_OPTIMISTIC] = true;
96
+ // Mark as optimistic store
97
+ Object.defineProperty(t, STORE_FIREWALL, {
98
+ get() {
99
+ return i;
100
+ },
101
+ configurable: true
102
+ });
103
+ };
104
+ const wrapProjection = t => {
105
+ if (o.has(t)) return o.get(t);
106
+ if (t[$TARGET]?.[STORE_WRAP] === wrapProjection) return t;
107
+ const e = createStoreProxy(t, storeTraps, wrapper);
108
+ o.set(t, e);
109
+ return e;
110
+ };
111
+ const c = wrapProjection(e);
112
+ // If there's a projection function, create a computed to drive it
113
+ if (t) {
114
+ // All writes inside firewall recompute must go to STORE_OVERRIDE (base), not
115
+ // STORE_OPTIMISTIC_OVERRIDE. The outer wrap covers the sync body (including
116
+ // `fn(draft)` and the initial commit); `wrapCommit` re-applies the flag for
117
+ // async yields because they fire outside any enclosing try/finally. It also
118
+ // consumes stale optimistic overlays once fresh projected data lands.
119
+ const clearProjectionOverride = () => {
120
+ const t = c[$TARGET];
121
+ if (t?.[STORE_OPTIMISTIC_OVERRIDE]) clearOptimisticOverride(t);
122
+ };
123
+ const wrapCommit = t => {
124
+ const e = projectionWriteActive;
125
+ setProjectionWriteActive(true);
126
+ try {
127
+ t();
128
+ clearProjectionOverride();
129
+ } finally {
130
+ setProjectionWriteActive(e);
131
+ }
132
+ };
133
+ i = computed(() => {
134
+ setProjectionWriteActive(true);
135
+ try {
136
+ runProjectionComputed(c, t, r?.key || "id", wrapCommit, clearProjectionOverride);
137
+ } finally {
138
+ setProjectionWriteActive(false);
139
+ }
140
+ }, undefined);
141
+ i.U &= ~CONFIG_AUTO_DISPOSE;
142
+ }
143
+ return {
144
+ store: c,
145
+ node: i
146
+ };
147
+ }
148
+
149
+ export { createOptimisticStore };