@solidjs/signals 2.0.0-rc.13 → 2.0.0-rc.14
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.
- package/dist/dev-shared.js +2000 -3768
- package/dist/dev.attribution.js +132 -79
- package/dist/dev.js +7226 -8488
- package/dist/observe/affects.js +125 -104
- package/dist/observe/boundaries.js +458 -665
- package/dist/observe/core/action.js +63 -66
- package/dist/observe/core/async.js +240 -383
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +240 -183
- package/dist/observe/core/constants.js +115 -187
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +840 -1438
- package/dist/observe/core/dev.js +3 -3
- package/dist/observe/core/effect.js +75 -86
- package/dist/observe/core/external.js +5 -5
- package/dist/observe/core/graph.js +61 -70
- package/dist/observe/core/heap.js +75 -98
- package/dist/observe/core/invariants.js +1 -34
- package/dist/observe/core/lanes.js +704 -181
- package/dist/observe/core/owner.js +80 -101
- package/dist/observe/core/scheduler.js +708 -1351
- package/dist/observe/core/verdict.js +302 -621
- package/dist/observe/flatten.js +79 -0
- package/dist/observe/index.js +15 -13
- package/dist/observe/map.js +232 -235
- package/dist/observe/reveal.js +212 -0
- package/dist/observe/signals.js +133 -215
- package/dist/observe/store/affects.js +135 -0
- package/dist/observe/store/index.js +29 -24
- package/dist/observe/store/optimistic.js +538 -0
- package/dist/observe/store/projection.js +302 -0
- package/dist/observe/store/reconcile.js +257 -0
- package/dist/observe/store/store.js +1945 -277
- package/dist/observe/store/storePath.js +1 -1
- package/dist/{prod/store/next → observe/store}/target.js +17 -18
- package/dist/observe/store/types.js +73 -0
- package/dist/observe/store/utils.js +1 -13
- package/dist/prod/affects.js +127 -106
- package/dist/prod/boundaries.js +442 -654
- package/dist/prod/core/action.js +71 -74
- package/dist/prod/core/async.js +307 -442
- package/dist/prod/core/constants.js +112 -186
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +852 -1458
- package/dist/prod/core/effect.js +88 -99
- package/dist/prod/core/external.js +5 -5
- package/dist/prod/core/graph.js +61 -70
- package/dist/prod/core/heap.js +75 -98
- package/dist/prod/core/invariants.js +1 -32
- package/dist/prod/core/lanes.js +666 -181
- package/dist/prod/core/owner.js +75 -96
- package/dist/prod/core/scheduler.js +677 -1332
- package/dist/prod/core/verdict.js +297 -621
- package/dist/prod/flatten.js +77 -0
- package/dist/prod/index.js +15 -13
- package/dist/prod/map.js +143 -146
- package/dist/prod/reveal.js +212 -0
- package/dist/prod/signals.js +144 -226
- package/dist/prod/store/affects.js +135 -0
- package/dist/prod/store/index.js +28 -19
- package/dist/prod/store/optimistic.js +533 -0
- package/dist/prod/store/projection.js +295 -0
- package/dist/prod/store/reconcile.js +257 -0
- package/dist/prod/store/store.js +1840 -272
- package/dist/prod/store/storePath.js +1 -1
- package/dist/{observe/store/next → prod/store}/target.js +13 -17
- package/dist/prod/store/types.js +73 -0
- package/dist/prod/store/utils.js +1 -11
- package/dist/types/affects.d.ts +11 -30
- package/dist/types/boundaries.d.ts +62 -176
- package/dist/types/carved.d.ts +1 -0
- package/dist/types/core/action.d.ts +5 -6
- package/dist/types/core/async.d.ts +10 -5
- package/dist/types/core/attribution-hooks.d.ts +20 -1
- package/dist/types/core/attribution.d.ts +20 -9
- package/dist/types/core/constants.d.ts +125 -180
- package/dist/types/core/core.d.ts +104 -264
- package/dist/types/core/dev.d.ts +2 -2
- package/dist/types/core/effect.d.ts +0 -7
- package/dist/types/core/graph.d.ts +7 -1
- package/dist/types/core/heap.d.ts +5 -7
- package/dist/types/core/index.d.ts +5 -5
- package/dist/types/core/invariants.d.ts +0 -29
- package/dist/types/core/lanes.d.ts +65 -87
- package/dist/types/core/owner.d.ts +3 -3
- package/dist/types/core/scheduler.d.ts +249 -331
- package/dist/types/core/types.d.ts +51 -85
- package/dist/types/core/verdict.d.ts +3 -0
- package/dist/types/flatten.d.ts +8 -0
- package/dist/types/index.d.ts +6 -4
- package/dist/types/reveal.d.ts +19 -0
- package/dist/types/signals.d.ts +35 -91
- package/dist/types/store/affects.d.ts +1 -0
- package/dist/types/store/index.d.ts +35 -15
- package/dist/types/store/optimistic.d.ts +23 -0
- package/dist/types/store/projection.d.ts +18 -0
- package/dist/types/store/reconcile.d.ts +3 -0
- package/dist/types/store/store.d.ts +165 -143
- package/dist/types/store/storePath.d.ts +1 -1
- package/dist/types/store/target.d.ts +151 -0
- package/dist/types/store/types.d.ts +84 -0
- package/package.json +1 -1
- package/dist/observe/core/optimistic.js +0 -669
- package/dist/observe/store/next/optimistic.js +0 -717
- package/dist/observe/store/next/projection.js +0 -266
- package/dist/observe/store/next/reconcile.js +0 -375
- package/dist/observe/store/next/store.js +0 -2608
- package/dist/prod/core/optimistic.js +0 -658
- package/dist/prod/store/next/optimistic.js +0 -710
- package/dist/prod/store/next/projection.js +0 -257
- package/dist/prod/store/next/reconcile.js +0 -373
- package/dist/prod/store/next/store.js +0 -2484
- package/dist/types/core/optimistic.d.ts +0 -6
- package/dist/types/store/next/optimistic.d.ts +0 -25
- package/dist/types/store/next/projection.d.ts +0 -27
- package/dist/types/store/next/reconcile.d.ts +0 -10
- package/dist/types/store/next/store.d.ts +0 -145
- package/dist/types/store/next/target.d.ts +0 -196
package/dist/prod/signals.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import { TimeoutError } from "./core/error.js";
|
|
1
|
+
import { NotReadyError, unwrapStatusError, TimeoutError } from "./core/error.js";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { cleanup, createRoot, getOwner, dispose, getObserver } from "./core/owner.js";
|
|
3
|
+
import "./core/verdict.js";
|
|
6
4
|
|
|
7
|
-
import {
|
|
5
|
+
import { CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH, CONFIG_REDERIVE, REACTIVE_JOINED, CONFIG_AUTO_DISPOSE, CONFIG_FRESH_READ, CONFIG_AUTHORITATIVE } from "./core/constants.js";
|
|
8
6
|
|
|
9
|
-
import {
|
|
7
|
+
import { computed, signal, runWithOwner, setMemo, setSignal, markRefresh, read, untrack } from "./core/core.js";
|
|
10
8
|
|
|
11
|
-
import "./core/
|
|
9
|
+
import { globalQueue, flushTransaction, resolveTx, passTx, staleReader, dirtyQueue, joinFuture } from "./core/scheduler.js";
|
|
12
10
|
|
|
13
|
-
import "./core/
|
|
11
|
+
import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
|
|
14
12
|
|
|
15
13
|
import { effect, trackedEffect } from "./core/effect.js";
|
|
16
14
|
|
|
17
|
-
import {
|
|
15
|
+
import { optimisticWrite } from "./core/lanes.js";
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Low-level reactive-cleanup primitive. Registers a callback that runs when
|
|
@@ -72,12 +70,22 @@ function accessor(e) {
|
|
|
72
70
|
|
|
73
71
|
function createSignal(e, t) {
|
|
74
72
|
if (typeof e === "function") {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
return [ accessor(
|
|
73
|
+
const r = computed(e, t);
|
|
74
|
+
r.u &= ~CONFIG_AUTO_DISPOSE;
|
|
75
|
+
return [ accessor(r), setMemo.bind(null, r) ];
|
|
76
|
+
}
|
|
77
|
+
const r = signal(e, t);
|
|
78
|
+
return [ accessor(r), setSignal.bind(null, r) ];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createOptimistic(e, t) {
|
|
82
|
+
if (typeof e === "function") {
|
|
83
|
+
const r = computed(e, t);
|
|
84
|
+
r.u &= ~CONFIG_AUTO_DISPOSE;
|
|
85
|
+
return [ accessor(r), optimisticWrite.bind(null, r) ];
|
|
78
86
|
}
|
|
79
|
-
const
|
|
80
|
-
return [ accessor(
|
|
87
|
+
const r = signal(e, t);
|
|
88
|
+
return [ accessor(r), optimisticWrite.bind(null, r) ];
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
function createMemo(e, t) {
|
|
@@ -151,10 +159,10 @@ function createMemo(e, t) {
|
|
|
151
159
|
* ```
|
|
152
160
|
*
|
|
153
161
|
* @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
|
|
154
|
-
*/ function createEffect(e, t,
|
|
162
|
+
*/ function createEffect(e, t, r) {
|
|
155
163
|
effect(e, t.effect || t, t.error, {
|
|
156
164
|
user: true,
|
|
157
|
-
...
|
|
165
|
+
...r
|
|
158
166
|
});
|
|
159
167
|
}
|
|
160
168
|
|
|
@@ -186,8 +194,8 @@ function createMemo(e, t) {
|
|
|
186
194
|
* ```
|
|
187
195
|
*
|
|
188
196
|
* @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
|
|
189
|
-
*/ function createRenderEffect(e, t,
|
|
190
|
-
effect(e, t, undefined,
|
|
197
|
+
*/ function createRenderEffect(e, t, r) {
|
|
198
|
+
effect(e, t, undefined, r);
|
|
191
199
|
}
|
|
192
200
|
|
|
193
201
|
/**
|
|
@@ -264,27 +272,27 @@ function createMemo(e, t) {
|
|
|
264
272
|
*
|
|
265
273
|
* @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
|
|
266
274
|
*/ function createReaction(e, t) {
|
|
267
|
-
let
|
|
268
|
-
cleanup(() =>
|
|
269
|
-
const
|
|
275
|
+
let r = undefined;
|
|
276
|
+
cleanup(() => r?.());
|
|
277
|
+
const n = getOwner();
|
|
270
278
|
// The currently armed effect node. `track()` replaces the previous
|
|
271
279
|
// subscription (1.x semantics): without disposing the superseded arm, its
|
|
272
280
|
// sources stayed live (firing the callback for replaced dependencies), each
|
|
273
281
|
// accumulated arm delivered its own fire, and un-fired arms leaked as live
|
|
274
282
|
// effect nodes until the owner disposed (#2861).
|
|
275
|
-
let
|
|
276
|
-
return
|
|
277
|
-
if (
|
|
278
|
-
dispose(
|
|
279
|
-
|
|
283
|
+
let o;
|
|
284
|
+
return c => {
|
|
285
|
+
if (o) {
|
|
286
|
+
dispose(o);
|
|
287
|
+
o = undefined;
|
|
280
288
|
}
|
|
281
|
-
runWithOwner(
|
|
282
|
-
effect(() => (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
if (false &&
|
|
287
|
-
|
|
289
|
+
runWithOwner(n, () => {
|
|
290
|
+
effect(() => (c(), o = getOwner()), t => {
|
|
291
|
+
o = undefined;
|
|
292
|
+
r?.();
|
|
293
|
+
const n = (e.effect || e)?.();
|
|
294
|
+
if (false && n !== undefined && typeof n !== "function") ;
|
|
295
|
+
r = n;
|
|
288
296
|
dispose(t);
|
|
289
297
|
}, e.error, {
|
|
290
298
|
...t,
|
|
@@ -295,10 +303,48 @@ function createMemo(e, t) {
|
|
|
295
303
|
};
|
|
296
304
|
}
|
|
297
305
|
|
|
298
|
-
/**
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
/** A promise over a reactive expression (`resolve`, `until`): a pass that
|
|
307
|
+
* runs the expression and settles on a microtask. Not an effect — an
|
|
308
|
+
* effect's run is the flush's effect phase, and a flush parked by the
|
|
309
|
+
* action that is `yield`ing on this promise stashes its runs with the
|
|
310
|
+
* transaction (#2930); the heap runs a pass parked or not. A pending read
|
|
311
|
+
* re-runs it when the source lands (the pass is pending itself); the
|
|
312
|
+
* expression throwing settles the rejection. */
|
|
313
|
+
/** The promise-delivery readers' pass (`resolve`, `until`, `refresh`'s
|
|
314
|
+
* waiter): delivers what it read on a microtask. By posture (#3482, #3490):
|
|
315
|
+
* - the reader of the transaction it was created in (`own`: an action's
|
|
316
|
+
* body, a `refresh()` called there — the waiter reads its slice's frame on
|
|
317
|
+
* a later microtask) delivers that frame — the staged landing an action
|
|
318
|
+
* holds (the action awaits the promise that would otherwise wait on its
|
|
319
|
+
* own settle);
|
|
320
|
+
* - a mainline reader whose pass read a frame another transaction holds
|
|
321
|
+
* joined it (L2, born held — A29) and delivers at that commit: the
|
|
322
|
+
* committed view, never a foreign hold's unrevealed frame. */ function watch(e, t, r, n = 0, o, c = flushTransaction) {
|
|
323
|
+
// `config` is on the node for its first pass too (the pass runs inside
|
|
324
|
+
// `computed()`): `until`'s predicate is authoritative from its first read,
|
|
325
|
+
// or an already-displayed guess would satisfy it in a later slice. (REDERIVE
|
|
326
|
+
// after: the first pass's own error is the promise's rejection.)
|
|
327
|
+
computed(() => {
|
|
328
|
+
let n;
|
|
329
|
+
try {
|
|
330
|
+
n = e();
|
|
331
|
+
} catch (e) {
|
|
332
|
+
if (e instanceof NotReadyError) throw e;
|
|
333
|
+
queueMicrotask(() => r(unwrapStatusError(e)));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const u = getOwner();
|
|
337
|
+
// (Outside a flush a mainline pass joins through `passTx`.)
|
|
338
|
+
const i = flushTransaction ?? passTx;
|
|
339
|
+
if (u.R & REACTIVE_JOINED && i !== null && (c === null || resolveTx(c) !== resolveTx(i))) {
|
|
340
|
+
staleReader(u, resolveTx(i));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
o?.(n);
|
|
344
|
+
queueMicrotask(() => t(n));
|
|
345
|
+
}, {
|
|
346
|
+
we: n
|
|
347
|
+
}).u |= CONFIG_REDERIVE;
|
|
302
348
|
}
|
|
303
349
|
|
|
304
350
|
/**
|
|
@@ -322,42 +368,8 @@ function createMemo(e, t) {
|
|
|
322
368
|
*
|
|
323
369
|
* @param fn a reactive expression to resolve
|
|
324
370
|
*/ function resolve(e) {
|
|
325
|
-
return new Promise((t,
|
|
326
|
-
createRoot(
|
|
327
|
-
// Deliver effect applies on a microtask instead of the owner queue: an
|
|
328
|
-
// incomplete transition stashes its effect queues until it settles, but
|
|
329
|
-
// an action yielding this promise is itself what keeps the transition
|
|
330
|
-
// open — the stashed res() deadlocked the action (#2930). The compute
|
|
331
|
-
// still runs in place (under the transaction's view when created inside
|
|
332
|
-
// an action step), and status/boundary notifications keep their normal
|
|
333
|
-
// route through the inherited queue.
|
|
334
|
-
const i = getOwner();
|
|
335
|
-
const o = new MicrotaskQueue;
|
|
336
|
-
o._parent = i.T;
|
|
337
|
-
// notify() forwards up the normal chain
|
|
338
|
-
i.T = o;
|
|
339
|
-
// A user effect rather than a bare computed: computeds are pull-based and
|
|
340
|
-
// are only re-enqueued when a pending source *resolves* — a rejection just
|
|
341
|
-
// marks them errored, so nothing would re-run and the promise would never
|
|
342
|
-
// settle (#2842). The effect's error channel is notified on rejection.
|
|
343
|
-
effect(e, e => {
|
|
344
|
-
t(e);
|
|
345
|
-
r();
|
|
346
|
-
}, e => {
|
|
347
|
-
// The error arm already unwraps StatusError (#2840) — `err` is the
|
|
348
|
-
// user's original error, matching what error boundaries expose.
|
|
349
|
-
n(e);
|
|
350
|
-
r();
|
|
351
|
-
},
|
|
352
|
-
// DIRECT_COMMIT: a source settling INTO the held transaction (e.g. a
|
|
353
|
-
// refresh this action issued) stages its landing; the effect's own
|
|
354
|
-
// recompute must not stage too, or the microtask apply reads the
|
|
355
|
-
// stale mainline value and resolves with old data.
|
|
356
|
-
{
|
|
357
|
-
user: true,
|
|
358
|
-
yn: CONFIG_DIRECT_COMMIT
|
|
359
|
-
});
|
|
360
|
-
});
|
|
371
|
+
return new Promise((t, r) => {
|
|
372
|
+
createRoot(n => watch(e, e => (t(e), n()), e => (r(e), n())));
|
|
361
373
|
});
|
|
362
374
|
}
|
|
363
375
|
|
|
@@ -377,19 +389,10 @@ function createMemo(e, t) {
|
|
|
377
389
|
* read:
|
|
378
390
|
* - Accessor targets resolve with the settled value; store targets resolve
|
|
379
391
|
* with the store node passed (reads through it are fresh after the await).
|
|
380
|
-
* - A failed re-ask rejects with the error
|
|
381
|
-
* `yield refresh(x)` throws back at the yield point and the action reverts
|
|
382
|
-
* like any other failure).
|
|
392
|
+
* - A failed re-ask rejects with the error.
|
|
383
393
|
* - Semantics are quiescence, not flight identity: if another refresh (or
|
|
384
394
|
* any invalidation) supersedes this one mid-flight, the promise waits for
|
|
385
395
|
* — and delivers — whatever finally lands.
|
|
386
|
-
* - Inside an action, truth landing into the held transaction is STAGED;
|
|
387
|
-
* the promise still settles then (matching `resolve()`/`until()`, #2930)
|
|
388
|
-
* and delivers the staged value — the caller's own optimistic override is
|
|
389
|
-
* never the delivered value.
|
|
390
|
-
* - The re-ask itself stays verdict-quiet exactly as before: `isPending`
|
|
391
|
-
* does not flip for a bare refresh (pair with `affects()` for a visible
|
|
392
|
-
* pending window).
|
|
393
396
|
*
|
|
394
397
|
* @example
|
|
395
398
|
* ```ts
|
|
@@ -416,52 +419,33 @@ function createMemo(e, t) {
|
|
|
416
419
|
// no-ops, so N waiters cost one pull; this also closes the race where a
|
|
417
420
|
// waiter reads the PRE-re-ask value as settled and delivers stale), after
|
|
418
421
|
// which the read either parks on the re-ask's pending window (async — the
|
|
419
|
-
// settle walk re-runs it on every landing, equal-value and
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
// that reader bit must install it — until() does, and this waiter is the
|
|
430
|
-
// other one (#3303: refresh of an optimistic in an app that never called
|
|
431
|
-
// until() dereferenced the null hook).
|
|
432
|
-
installAuthoritativeRead();
|
|
433
|
-
markRefresh(t);
|
|
434
|
-
const n = new Promise((n, r) => {
|
|
422
|
+
// settle walk re-runs it on every landing, equal-value included, and a
|
|
423
|
+
// rejection arrives through the effect's error channel) or serves the sync
|
|
424
|
+
// answer. resolve()'s own eager compute is untouched: created after a
|
|
425
|
+
// refresh it still settles stale-while-revalidate (#2930) — its contract is
|
|
426
|
+
// "first settled value", not "next quiescent state".
|
|
427
|
+
markRefresh(t);
|
|
428
|
+
// The transaction `refresh()` is called in (an action's slice): the
|
|
429
|
+
// waiter, created on the microtask below, is its reader (`watch`).
|
|
430
|
+
const r = flushTransaction;
|
|
431
|
+
const n = new Promise((n, o) => {
|
|
435
432
|
queueMicrotask(() => {
|
|
436
|
-
// No createRoot: the microtask has no ambient owner, so the
|
|
437
|
-
// naturally detached, and settle disposes the node directly
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// effect's own compute, where the ambient owner IS the effect —
|
|
445
|
-
// exactly what dispose() takes.
|
|
446
|
-
let i = null;
|
|
447
|
-
const make = () => effect(() => {
|
|
448
|
-
if (i === null) {
|
|
449
|
-
i = getOwner();
|
|
450
|
-
const e = new MicrotaskQueue;
|
|
451
|
-
e._parent = i.T;
|
|
452
|
-
i.T = e;
|
|
453
|
-
}
|
|
433
|
+
// No createRoot: the microtask has no ambient owner, so the watcher is
|
|
434
|
+
// naturally detached, and settle disposes the node directly (dev keeps
|
|
435
|
+
// a root husk purely to stay quiet about the missing owner). The
|
|
436
|
+
// capture runs inside the watcher's own pass, where the ambient owner
|
|
437
|
+
// IS the watcher — exactly what dispose() takes.
|
|
438
|
+
let c = null;
|
|
439
|
+
const make = () => watch(() => {
|
|
440
|
+
if (c === null) c = getOwner();
|
|
454
441
|
return read(t);
|
|
455
442
|
}, t => {
|
|
456
443
|
n(typeof e === "function" ? t : e);
|
|
457
|
-
dispose(
|
|
444
|
+
dispose(c);
|
|
458
445
|
}, e => {
|
|
459
|
-
|
|
460
|
-
dispose(
|
|
461
|
-
},
|
|
462
|
-
user: true,
|
|
463
|
-
yn: CONFIG_DIRECT_COMMIT | CONFIG_AUTHORITATIVE_READ | CONFIG_FRESH_READ
|
|
464
|
-
});
|
|
446
|
+
o(e);
|
|
447
|
+
dispose(c);
|
|
448
|
+
}, CONFIG_FRESH_READ, undefined, r);
|
|
465
449
|
make();
|
|
466
450
|
});
|
|
467
451
|
});
|
|
@@ -480,115 +464,63 @@ function createMemo(e, t) {
|
|
|
480
464
|
*
|
|
481
465
|
* Where {@link resolve} answers "what is this value" (first settled value,
|
|
482
466
|
* whatever it is), `until` answers "when does the world confirm this
|
|
483
|
-
* condition".
|
|
484
|
-
* holds the action's transaction — and any optimistic state riding it — open
|
|
485
|
-
* until the condition is independently true.
|
|
486
|
-
*
|
|
487
|
-
* To make that sound, `until`'s predicate reads the AUTHORITATIVE view — and
|
|
488
|
-
* this is the one read-semantics difference from `resolve`, which reads the
|
|
489
|
-
* normal (transaction's own) view where overrides are visible:
|
|
490
|
-
*
|
|
491
|
-
* - **Optimistic overrides are invisible** to the predicate. Your own
|
|
492
|
-
* tentative write can never satisfy your own ack, even on the
|
|
493
|
-
* single-primitive shape where the optimistic store IS the live-fed store.
|
|
494
|
-
* (Derived computeds serve their normal cached values — express the
|
|
495
|
-
* condition over sources of truth, not derived views of the overlay.)
|
|
496
|
-
* - **Everything else reads normally, including uncommitted transition-staged
|
|
497
|
-
* data.** Real data is real wherever it currently lives. This is
|
|
498
|
-
* load-bearing, not a loophole: truth that arrives *into* the open
|
|
499
|
-
* transaction (a `refresh()` this action issued, an entangled landing)
|
|
500
|
-
* stages and cannot commit until the hold releases — a predicate that
|
|
501
|
-
* refused staged reads would deadlock on the very data it is waiting for.
|
|
467
|
+
* condition".
|
|
502
468
|
*
|
|
503
469
|
* This is the acknowledgment mechanism for mutations confirmed on a live data
|
|
504
470
|
* channel (sockets, subscriptions, live queries) rather than by the mutation's
|
|
505
471
|
* own response: correlate by a client-generated id or version in the predicate,
|
|
506
472
|
* and let truth arrive however it arrives — push, refetch, or another tab.
|
|
507
473
|
*
|
|
508
|
-
* Failure composes with action semantics: a rejection is thrown back into the
|
|
509
|
-
* generator at the `yield` point — catchable there, or the action fails and
|
|
510
|
-
* its optimistic state reverts.
|
|
511
|
-
*
|
|
512
474
|
* Must be called *outside* a tracking scope.
|
|
513
475
|
*
|
|
514
|
-
* Inside an action, call it from a step: after an `await`, put a bare `yield`
|
|
515
|
-
* before `yield until(...)`. The runtime cannot hook an async generator's
|
|
516
|
-
* `await` continuation, so the `until(...)` expression — which CREATES the
|
|
517
|
-
* predicate's reader — would otherwise run outside the transaction; created
|
|
518
|
-
* there it is born held (A29) and replays only at the commit its own promise
|
|
519
|
-
* holds open (#3482). See {@link action}.
|
|
520
|
-
*
|
|
521
476
|
* @example
|
|
522
477
|
* ```ts
|
|
523
|
-
* const
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
* yield; // re-enter the transaction after the await
|
|
528
|
-
* // Hold until the live source echoes the write (authoritative view —
|
|
529
|
-
* // the optimistic row above cannot satisfy this):
|
|
530
|
-
* yield until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
|
|
531
|
-
* });
|
|
478
|
+
* const clientId = crypto.randomUUID();
|
|
479
|
+
* await socket.send({ clientId, text }); // fire-and-forget transport
|
|
480
|
+
* // Wait until the live source echoes the write:
|
|
481
|
+
* await until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
|
|
532
482
|
* ```
|
|
533
483
|
*
|
|
534
|
-
* @param fn a reactive predicate
|
|
484
|
+
* @param fn a reactive predicate
|
|
535
485
|
* @param options optional `timeout` (ms) and abort `signal`
|
|
536
486
|
*/ function until(e, t) {
|
|
537
|
-
|
|
538
|
-
// apps that never call until() never retain it).
|
|
539
|
-
installAuthoritativeRead();
|
|
540
|
-
// Flip-entanglement (#3164 follow-up): the transaction this until() holds
|
|
541
|
-
// open (the action's, when yielded from one). The predicate is the user's
|
|
542
|
-
// declaration of what confirms it — when a foreign transition's staged
|
|
543
|
-
// write flips it truthy, that transition merges here and reveals at the
|
|
544
|
-
// joint settle instead of painting the confirmation under live optimism.
|
|
545
|
-
const n = activeTransition;
|
|
546
|
-
return new Promise((r, i) => {
|
|
487
|
+
return new Promise((r, n) => {
|
|
547
488
|
const o = t?.signal;
|
|
548
|
-
if (o?.aborted) return
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
let
|
|
559
|
-
let
|
|
489
|
+
if (o?.aborted) return n(o.reason);
|
|
490
|
+
// The awaiting action's transaction, if any (its body runs joined to
|
|
491
|
+
// it). `until()` is a declaration of relatedness (#3164 follow-up): the
|
|
492
|
+
// predicate names the condition that confirms the action, so the frame
|
|
493
|
+
// whose landing flips it truthy is the confirming event by the user's
|
|
494
|
+
// own definition — it reveals WITH the action's settle, not before
|
|
495
|
+
// (`saving=true` beside the confirmed `version=1` is a tear no timeline
|
|
496
|
+
// contains). Updates that do not flip the predicate reveal freely.
|
|
497
|
+
const c = flushTransaction;
|
|
498
|
+
createRoot(u => {
|
|
499
|
+
let i;
|
|
500
|
+
let s;
|
|
560
501
|
const settle = e => {
|
|
561
|
-
if (
|
|
562
|
-
if (
|
|
502
|
+
if (i !== undefined) clearTimeout(i);
|
|
503
|
+
if (s !== undefined) o.removeEventListener("abort", s);
|
|
563
504
|
e();
|
|
564
|
-
|
|
505
|
+
u();
|
|
565
506
|
};
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
// the merge lands before any reveal. Falsy evaluations skip —
|
|
571
|
-
// non-flipping updates were never named as the confirmation.
|
|
572
|
-
if (t) entangleConfirmingTransitions(getObserver(), n);
|
|
573
|
-
return t;
|
|
574
|
-
}, e => {
|
|
507
|
+
// Authoritative: the predicate sees the truth, never the action's own
|
|
508
|
+
// optimistic guess — a guess cannot satisfy the wait for its own
|
|
509
|
+
// confirmation.
|
|
510
|
+
watch(e, e => {
|
|
575
511
|
// Falsy is "not yet": keep the subscription live and wait for the
|
|
576
512
|
// next evaluation. Only a truthy settled value resolves.
|
|
577
513
|
if (e) settle(() => r(e));
|
|
578
|
-
}, e => settle(() =>
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
// the hold itself is keeping uncommitted.
|
|
584
|
-
{
|
|
585
|
-
user: true,
|
|
586
|
-
yn: CONFIG_AUTHORITATIVE_READ | CONFIG_DIRECT_COMMIT
|
|
514
|
+
}, e => settle(() => n(e)), CONFIG_AUTHORITATIVE, e => {
|
|
515
|
+
// The flip, inside the pass of the flush carrying it: the flush
|
|
516
|
+
// joins the action's transaction, and what it staged is held and
|
|
517
|
+
// revealed with the action.
|
|
518
|
+
if (e && c !== null && globalQueue.ee && resolveTx(c).tt !== 0) joinFuture(c);
|
|
587
519
|
});
|
|
588
|
-
if (t?.timeout !== undefined)
|
|
520
|
+
if (t?.timeout !== undefined) i = setTimeout(() => settle(() => n(new TimeoutError)), t.timeout);
|
|
589
521
|
if (o !== undefined) {
|
|
590
|
-
|
|
591
|
-
o.addEventListener("abort",
|
|
522
|
+
s = () => settle(() => n(o.reason));
|
|
523
|
+
o.addEventListener("abort", s, {
|
|
592
524
|
once: true
|
|
593
525
|
});
|
|
594
526
|
}
|
|
@@ -596,20 +528,6 @@ function createMemo(e, t) {
|
|
|
596
528
|
});
|
|
597
529
|
}
|
|
598
530
|
|
|
599
|
-
function createOptimistic(e, t) {
|
|
600
|
-
// Install before the node exists: only engine-installed programs can carry
|
|
601
|
-
// an _overrideValue slot (same runtime-install pattern as
|
|
602
|
-
// GlobalQueue._clearOptimisticStore in createOptimisticStore).
|
|
603
|
-
installOptimisticEngine();
|
|
604
|
-
if (typeof e === "function") {
|
|
605
|
-
const n = optimisticComputed(e, t);
|
|
606
|
-
n.C &= ~CONFIG_AUTO_DISPOSE;
|
|
607
|
-
return [ accessor(n), setSignal.bind(null, n) ];
|
|
608
|
-
}
|
|
609
|
-
const n = optimisticSignal(e, t);
|
|
610
|
-
return [ accessor(n), setSignal.bind(null, n) ];
|
|
611
|
-
}
|
|
612
|
-
|
|
613
531
|
/**
|
|
614
532
|
* Schedules `callback` to run **once** after the reactive graph has fully
|
|
615
533
|
* settled — i.e. once every pending async read inside the current owner has
|
|
@@ -705,7 +623,7 @@ function createOptimistic(e, t) {
|
|
|
705
623
|
* on owner disposal
|
|
706
624
|
*/ function onSettled(e) {
|
|
707
625
|
const t = getOwner();
|
|
708
|
-
t && !(t.
|
|
626
|
+
t && !(t.u & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, function fire() {
|
|
709
627
|
// Settled means derived. A settle that reverts optimism (or replays
|
|
710
628
|
// gated reads) only enqueues the affected subscribers; the pass after
|
|
711
629
|
// the commit re-derives them. Fired in the commit pass, the callback
|
|
@@ -713,7 +631,7 @@ function createOptimistic(e, t) {
|
|
|
713
631
|
// still holding the optimistic value — reads do not pull (#3411). Fall
|
|
714
632
|
// to the next pass while the heap has work; `run` swapped the queue,
|
|
715
633
|
// so this lands there, and `enqueue` keeps the drain alive.
|
|
716
|
-
if (dirtyQueue.
|
|
634
|
+
if (dirtyQueue.At >= dirtyQueue.He) return globalQueue.enqueue(EFFECT_USER, fire);
|
|
717
635
|
// Unowned, out-of-band fire (no owner, or a children-forbidden one this
|
|
718
636
|
// one-shot must not bind to): a returned cleanup has no lifecycle to
|
|
719
637
|
// attach to. Reject it in dev; in production the return is simply
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { GlobalQueue } from "../core/scheduler.js";
|
|
2
|
+
|
|
3
|
+
import { installAffectsHooks, getNode, optHooks } from "./store.js";
|
|
4
|
+
|
|
5
|
+
import { lookupTarget, $OWNER } from "./target.js";
|
|
6
|
+
|
|
7
|
+
import { isWrappable, $TARGET } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
|
|
11
|
+
* #2893, #2904).
|
|
12
|
+
*
|
|
13
|
+
* A mark on a store is a mark on nodes (affects.ts: a count on the node, a
|
|
14
|
+
* dependency walk at probe time): `affects(record, key)` marks the slot's
|
|
15
|
+
* leaf; `affects(record)` marks a carrier (the record's `$AFFECTS` leaf)
|
|
16
|
+
* and every live node in the record's subtree — the edges existing readers
|
|
17
|
+
* subscribed through. Coverage is by RAW IDENTITY, not read path (#2882: a
|
|
18
|
+
* captured row proxy, a derived store sharing the source's raw, never read
|
|
19
|
+
* through the declaration's proxy): the identities reachable at the
|
|
20
|
+
* declaration are the mark's `scope`, so a node born inside the window on
|
|
21
|
+
* a covered record inherits the mark (released with the carrier's last),
|
|
22
|
+
* and an untracked probe through a record with no nodes is witnessed into
|
|
23
|
+
* the verdict. A mark declared on a draft (inside a setter) or over an
|
|
24
|
+
* optimistic family walks the view the writer sees.
|
|
25
|
+
*
|
|
26
|
+
* Installed by `store/index.ts`: a program with stores carries it; one
|
|
27
|
+
* without pays nothing (affects.ts asks `GlobalQueue._storeMarks`).
|
|
28
|
+
*/
|
|
29
|
+
/** The keyless mark's carrier key: a leaf of the record no read serves. */ const $AFFECTS = Symbol(0);
|
|
30
|
+
|
|
31
|
+
const scopes = new Map;
|
|
32
|
+
|
|
33
|
+
/** The nodes an `affects(store[, key])` declaration marks (affects.ts
|
|
34
|
+
* registers each). */ function storeMarks(e, n) {
|
|
35
|
+
if (n === undefined) {
|
|
36
|
+
const n = getNode(e, $AFFECTS);
|
|
37
|
+
let o = scopes.get(n);
|
|
38
|
+
if (o === undefined) scopes.set(n, o = {
|
|
39
|
+
scope: new Set,
|
|
40
|
+
inherited: []
|
|
41
|
+
});
|
|
42
|
+
const t = [ n ];
|
|
43
|
+
walk(e.px, o, t, e.fam, new Set);
|
|
44
|
+
return t;
|
|
45
|
+
}
|
|
46
|
+
const o = e.n?.[n] ?? getNode(e, n);
|
|
47
|
+
let t = scopes.get(o);
|
|
48
|
+
if (t === undefined) scopes.set(o, t = {
|
|
49
|
+
scope: new Set,
|
|
50
|
+
inherited: [],
|
|
51
|
+
key: n
|
|
52
|
+
});
|
|
53
|
+
t.scope.add(e.v);
|
|
54
|
+
if (e.pb !== null) t.scope.add(e.pb);
|
|
55
|
+
return [ o ];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The identities reachable from `value` into the scope (the draft's and
|
|
59
|
+
* the committed backing both, when a draft is open; an optimistic family's
|
|
60
|
+
* view), every live node under each record into `found`. Untracked by
|
|
61
|
+
* construction: raws, never traps. */ function walk(e, n, o, t, s) {
|
|
62
|
+
if (!isWrappable(e)) return;
|
|
63
|
+
const f = e[$TARGET] ?? lookupTarget(e, t);
|
|
64
|
+
let i = f !== undefined ? f.pb ?? f.v : e;
|
|
65
|
+
if (s.has(i)) return;
|
|
66
|
+
s.add(i);
|
|
67
|
+
n.scope.add(i);
|
|
68
|
+
if (f !== undefined) {
|
|
69
|
+
if (f.pb !== null) n.scope.add(f.v);
|
|
70
|
+
// The writer's view: the tick's own unflushed guesses are in motion too.
|
|
71
|
+
if (f.fam?.opt === true) i = optHooks.view(f, i, true);
|
|
72
|
+
const e = f.n;
|
|
73
|
+
if (e !== null) for (const n of Reflect.ownKeys(e))
|
|
74
|
+
// Another mark's carrier is its own channel: counting it here would
|
|
75
|
+
// extend that scope's lifetime to this declaration's.
|
|
76
|
+
if (n !== $AFFECTS) o.push(e[n]);
|
|
77
|
+
const s = f.h;
|
|
78
|
+
if (s !== null) for (const e of Reflect.ownKeys(s)) o.push(s[e]);
|
|
79
|
+
if (f.k !== null) o.push(f.k);
|
|
80
|
+
if (f.dk !== null) o.push(f.dk);
|
|
81
|
+
t = f.fam ?? t;
|
|
82
|
+
}
|
|
83
|
+
for (const e of Reflect.ownKeys(i)) {
|
|
84
|
+
if (e === $OWNER) continue;
|
|
85
|
+
const f = Object.getOwnPropertyDescriptor(i, e);
|
|
86
|
+
if (f === undefined || f.get !== undefined) continue;
|
|
87
|
+
walk(f.value, n, o, t, s);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A live scope covering `t`'s identity (keyed: for `key`). Chained
|
|
92
|
+
* backings (§7b): a view's backing is another store's proxy — marks cover
|
|
93
|
+
* the BASE raw, so every identity along the chain is checked. */ function covering(e, n, o) {
|
|
94
|
+
for (const [t, s] of scopes) {
|
|
95
|
+
if (t === o || s.key !== undefined && s.key !== n) continue;
|
|
96
|
+
for (let n = e.v; ;) {
|
|
97
|
+
if (s.scope.has(n)) return t;
|
|
98
|
+
const e = n?.[$TARGET];
|
|
99
|
+
if (e === undefined) break;
|
|
100
|
+
const o = e.pb ?? e.v;
|
|
101
|
+
if (o === n) break;
|
|
102
|
+
n = o;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
installAffectsHooks({
|
|
109
|
+
// A node born on a record a live mark covers inherits it (released with
|
|
110
|
+
// the carrier's last registration).
|
|
111
|
+
born(e, n, o) {
|
|
112
|
+
if (o === $AFFECTS) return;
|
|
113
|
+
const t = covering(e, o, n);
|
|
114
|
+
if (t === null) return;
|
|
115
|
+
GlobalQueue.h(n);
|
|
116
|
+
scopes.get(t).inherited.push(n);
|
|
117
|
+
},
|
|
118
|
+
// An untracked probe (`isPending(() => s.x)` with no observer) reading a
|
|
119
|
+
// record a live mark covers: no node carries the mark for it — the
|
|
120
|
+
// verdict is told directly.
|
|
121
|
+
witness(e, n) {
|
|
122
|
+
const o = e.n?.[$AFFECTS];
|
|
123
|
+
if (o !== undefined && o.m !== null && o.m.T !== 0 || covering(e, n) !== null) GlobalQueue.dt();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
GlobalQueue.O = storeMarks;
|
|
128
|
+
|
|
129
|
+
/** The carrier's last mark released (affects.ts): its scope dies, and the
|
|
130
|
+
* nodes that inherited the mark release theirs. */ GlobalQueue.N = e => {
|
|
131
|
+
const n = scopes.get(e);
|
|
132
|
+
if (n === undefined) return;
|
|
133
|
+
scopes.delete(e);
|
|
134
|
+
GlobalQueue.A(n.inherited);
|
|
135
|
+
};
|