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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/dev.js +1169 -186
  2. package/dist/node.cjs +2041 -1167
  3. package/dist/prod/boundaries.js +4 -1
  4. package/dist/prod/core/async.js +124 -95
  5. package/dist/prod/core/constants.js +55 -1
  6. package/dist/prod/core/core.js +297 -222
  7. package/dist/prod/core/effect.js +28 -28
  8. package/dist/prod/core/error.js +13 -1
  9. package/dist/prod/core/external.js +2 -2
  10. package/dist/prod/core/graph.js +27 -27
  11. package/dist/prod/core/heap.js +30 -30
  12. package/dist/prod/core/lanes.js +32 -32
  13. package/dist/prod/core/optimistic.js +54 -54
  14. package/dist/prod/core/owner.js +34 -34
  15. package/dist/prod/core/scheduler.js +318 -137
  16. package/dist/prod/core/verdict.js +112 -59
  17. package/dist/prod/index.js +3 -3
  18. package/dist/prod/map.js +106 -106
  19. package/dist/prod/signals.js +253 -25
  20. package/dist/prod/store/next/optimistic.js +262 -123
  21. package/dist/prod/store/next/patch.js +6 -6
  22. package/dist/prod/store/next/projection.js +23 -19
  23. package/dist/prod/store/next/store.js +129 -35
  24. package/dist/prod/store/store.js +2 -2
  25. package/dist/types/core/async.d.ts +2 -0
  26. package/dist/types/core/attribution.d.ts +9 -4
  27. package/dist/types/core/constants.d.ts +54 -0
  28. package/dist/types/core/core.d.ts +19 -20
  29. package/dist/types/core/error.d.ts +9 -0
  30. package/dist/types/core/index.d.ts +2 -2
  31. package/dist/types/core/scheduler.d.ts +34 -0
  32. package/dist/types/core/types.d.ts +12 -0
  33. package/dist/types/index.d.ts +3 -3
  34. package/dist/types/signals.d.ts +108 -0
  35. package/dist/types/store/next/optimistic.d.ts +13 -10
  36. package/dist/types/store/next/projection.d.ts +1 -1
  37. package/dist/types/store/next/store.d.ts +22 -0
  38. package/dist/types/store/next/target.d.ts +15 -0
  39. package/dist/types-cjs/core/async.d.cts +2 -0
  40. package/dist/types-cjs/core/attribution.d.cts +9 -4
  41. package/dist/types-cjs/core/constants.d.cts +54 -0
  42. package/dist/types-cjs/core/core.d.cts +19 -20
  43. package/dist/types-cjs/core/error.d.cts +9 -0
  44. package/dist/types-cjs/core/index.d.cts +2 -2
  45. package/dist/types-cjs/core/scheduler.d.cts +34 -0
  46. package/dist/types-cjs/core/types.d.cts +12 -0
  47. package/dist/types-cjs/index.d.cts +3 -3
  48. package/dist/types-cjs/signals.d.cts +108 -0
  49. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  50. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  51. package/dist/types-cjs/store/next/store.d.cts +22 -0
  52. package/dist/types-cjs/store/next/target.d.cts +15 -0
  53. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
- import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, read, untrack } from "./core/core.js";
1
+ import { TimeoutError } from "./core/error.js";
2
2
 
3
- import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
3
+ import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, markRefresh, installAuthoritativeRead, read, untrack } from "./core/core.js";
4
4
 
5
- import { globalQueue, Queue } from "./core/scheduler.js";
5
+ import { cleanup, createRoot, getOwner, dispose, getObserver } from "./core/owner.js";
6
6
 
7
- import { CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH } from "./core/constants.js";
7
+ import { globalQueue, Queue, entangleConfirmingTransitions, activeTransition } from "./core/scheduler.js";
8
+
9
+ import { CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH, CONFIG_DIRECT_COMMIT, CONFIG_AUTHORITATIVE_READ, CONFIG_FRESH_READ } from "./core/constants.js";
8
10
 
9
11
  import "./core/invariants.js";
10
12
 
@@ -181,25 +183,25 @@ function createEffect(e, t, n) {
181
183
  */ function createReaction(e, t) {
182
184
  let n = undefined;
183
185
  cleanup(() => n?.());
184
- const c = getOwner();
186
+ const r = getOwner();
185
187
  // The currently armed effect node. `track()` replaces the previous
186
188
  // subscription (1.x semantics): without disposing the superseded arm, its
187
189
  // sources stayed live (firing the callback for replaced dependencies), each
188
190
  // accumulated arm delivered its own fire, and un-fired arms leaked as live
189
191
  // effect nodes until the owner disposed (#2861).
190
- let r;
191
- return o => {
192
- if (r) {
193
- dispose(r);
194
- r = undefined;
192
+ let o;
193
+ return i => {
194
+ if (o) {
195
+ dispose(o);
196
+ o = undefined;
195
197
  }
196
- runWithOwner(c, () => {
197
- effect(() => (o(), r = getOwner()), t => {
198
- r = undefined;
198
+ runWithOwner(r, () => {
199
+ effect(() => (i(), o = getOwner()), t => {
200
+ o = undefined;
199
201
  n?.();
200
- const c = (e.effect || e)?.();
201
- if (false && c !== undefined && typeof c !== "function") ;
202
- n = c;
202
+ const r = (e.effect || e)?.();
203
+ if (false && r !== undefined && typeof r !== "function") ;
204
+ n = r;
203
205
  dispose(t);
204
206
  }, e.error, {
205
207
  ...false ? {
@@ -241,7 +243,7 @@ function createEffect(e, t, n) {
241
243
  * @param fn a reactive expression to resolve
242
244
  */ function resolve(e) {
243
245
  return new Promise((t, n) => {
244
- createRoot(c => {
246
+ createRoot(r => {
245
247
  // Deliver effect applies on a microtask instead of the owner queue: an
246
248
  // incomplete transition stashes its effect queues until it settles, but
247
249
  // an action yielding this promise is itself what keeps the transition
@@ -249,26 +251,252 @@ function createEffect(e, t, n) {
249
251
  // still runs in place (under the transaction's view when created inside
250
252
  // an action step), and status/boundary notifications keep their normal
251
253
  // route through the inherited queue.
252
- const r = getOwner();
253
- const o = new MicrotaskQueue;
254
- o.ke = r.C;
254
+ const o = getOwner();
255
+ const i = new MicrotaskQueue;
256
+ i.ke = o.C;
255
257
  // notify() forwards up the normal chain
256
- r.C = o;
258
+ o.C = i;
257
259
  // A user effect rather than a bare computed: computeds are pull-based and
258
260
  // are only re-enqueued when a pending source *resolves* — a rejection just
259
261
  // marks them errored, so nothing would re-run and the promise would never
260
262
  // settle (#2842). The effect's error channel is notified on rejection.
261
263
  effect(e, e => {
262
264
  t(e);
263
- c();
265
+ r();
264
266
  }, e => {
265
267
  // The error arm already unwraps StatusError (#2840) — `err` is the
266
268
  // user's original error, matching what error boundaries expose.
267
269
  n(e);
268
- c();
270
+ r();
271
+ },
272
+ // DIRECT_COMMIT: a source settling INTO the held transaction (e.g. a
273
+ // refresh this action issued) stages its landing; the effect's own
274
+ // recompute must not stage too, or the microtask apply reads the
275
+ // stale mainline value and resolves with old data.
276
+ {
277
+ user: true,
278
+ dt: CONFIG_DIRECT_COMMIT
279
+ });
280
+ });
281
+ });
282
+ }
283
+
284
+ /**
285
+ * Invalidates one reactive source, forcing it to re-execute even if its inputs
286
+ * haven't changed, and returns a promise for the target's NEXT QUIESCENT
287
+ * STATE — the re-ask (and anything that supersedes it) has settled.
288
+ *
289
+ * Pass either a Solid-created accessor or a projected store created from
290
+ * `createStore(fn, ...)` / `createProjection(...)`. `refresh()` is a
291
+ * write-like invalidation operation: it does not read the target's value, and
292
+ * refreshing a plain signal accessor is a no-op that resolves immediately.
293
+ *
294
+ * The returned promise is safe to ignore (fire-and-forget refresh is
295
+ * unchanged, and a failed refetch will not surface an unhandled rejection).
296
+ * Awaiting it gives imperative flows the settle point without a reactive
297
+ * read:
298
+ * - Accessor targets resolve with the settled value; store targets resolve
299
+ * with the store node passed (reads through it are fresh after the await).
300
+ * - A failed re-ask rejects with the error (inside an action's generator,
301
+ * `yield refresh(x)` throws back at the yield point and the action reverts
302
+ * like any other failure).
303
+ * - Semantics are quiescence, not flight identity: if another refresh (or
304
+ * any invalidation) supersedes this one mid-flight, the promise waits for
305
+ * — and delivers — whatever finally lands.
306
+ * - Inside an action, truth landing into the held transaction is STAGED;
307
+ * the promise still settles then (matching `resolve()`/`until()`, #2930)
308
+ * and delivers the staged value — the caller's own optimistic override is
309
+ * never the delivered value.
310
+ * - The re-ask itself stays verdict-quiet exactly as before: `isPending`
311
+ * does not flip for a bare refresh (pair with `affects()` for a visible
312
+ * pending window).
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * const user = createMemo(async () => fetch(`/users/${id()}`).then(r => r.json()));
317
+ *
318
+ * // Fire-and-forget re-fetch
319
+ * <button onClick={() => refresh(user)}>Reload</button>;
320
+ *
321
+ * // Imperative settle point
322
+ * const fresh = await refresh(user);
323
+ * ```
324
+ */ function refresh(e) {
325
+ const t = e?.[$REFRESH];
326
+ if (!t) {
327
+ return Promise.resolve(undefined);
328
+ }
329
+ // Mark now, watch on a microtask. The waiter is resolve()'s machinery with
330
+ // two extra reader bits, but it must NOT compute at call time (effects
331
+ // recompute eagerly on creation): same-tick refreshes coalesce into ONE
332
+ // re-ask only because every mark lands before anything pulls, and eager
333
+ // per-call pulls turned three refreshes into three fetches. Deferred, the
334
+ // waiter's first read sees the coalesced state: FRESH_READ pulls the node
335
+ // through recompute if it is still dirty (self-deduping — a clean node
336
+ // no-ops, so N waiters cost one pull; this also closes the race where a
337
+ // waiter reads the PRE-re-ask value as settled and delivers stale), after
338
+ // which the read either parks on the re-ask's pending window (async — the
339
+ // settle walk re-runs it on every landing, equal-value and
340
+ // staged-under-hold included, and a rejection arrives through the effect's
341
+ // error channel) or serves the sync answer. AUTHORITATIVE_READ keeps an
342
+ // action's own optimistic override out of the delivered value. resolve()'s
343
+ // own eager compute is untouched: created after a refresh it still settles
344
+ // stale-while-revalidate (#2930) — its contract is "first settled value",
345
+ // not "next quiescent state".
346
+ markRefresh(t);
347
+ const n = new Promise((n, r) => {
348
+ queueMicrotask(() => {
349
+ // No createRoot: the microtask has no ambient owner, so the effect is
350
+ // naturally detached, and settle disposes the node directly — the root
351
+ // added ~560B of otherwise-shakeable machinery for nothing but the
352
+ // dev-mode NO_OWNER_EFFECT warning, so dev keeps a root husk purely to
353
+ // stay quiet. The waiter swaps in its microtask queue during its own
354
+ // first compute (before the initial apply enqueue), replacing the
355
+ // root-owner plumbing.
356
+ // Typed as the effect node, not Owner: the capture runs inside the
357
+ // effect's own compute, where the ambient owner IS the effect —
358
+ // exactly what dispose() takes.
359
+ let o = null;
360
+ const make = () => effect(() => {
361
+ if (o === null) {
362
+ o = getOwner();
363
+ const e = new MicrotaskQueue;
364
+ e.ke = o.C;
365
+ o.C = e;
366
+ }
367
+ return read(t);
368
+ }, t => {
369
+ n(typeof e === "function" ? t : e);
370
+ dispose(o);
371
+ }, e => {
372
+ r(e);
373
+ dispose(o);
269
374
  }, {
270
- user: true
375
+ user: true,
376
+ dt: CONFIG_DIRECT_COMMIT | CONFIG_AUTHORITATIVE_READ | CONFIG_FRESH_READ
377
+ });
378
+ make();
379
+ });
380
+ });
381
+ // Fire-and-forget refresh must not turn a failed refetch into an unhandled
382
+ // rejection; awaiting callers attach their own handlers to `promise`.
383
+ n.catch(() => {});
384
+ return n;
385
+ }
386
+
387
+ /**
388
+ * Awaits a reactive predicate and resolves the first time it settles *truthy*,
389
+ * with that (narrowed) value. Falsy results and pending async reads both mean
390
+ * "not yet": the subscription stays live and re-evaluates as sources change.
391
+ * If the predicate settles with an error — a throw, or an async source that
392
+ * rejects — the promise rejects with it, as do timeout and abort.
393
+ *
394
+ * Where {@link resolve} answers "what is this value" (first settled value,
395
+ * whatever it is), `until` answers "when does the world confirm this
396
+ * condition". The difference matters inside an `action()`: `yield until(...)`
397
+ * holds the action's transaction — and any optimistic state riding it — open
398
+ * until the condition is independently true.
399
+ *
400
+ * To make that sound, `until`'s predicate reads the AUTHORITATIVE view — and
401
+ * this is the one read-semantics difference from `resolve`, which reads the
402
+ * normal (transaction's own) view where overrides are visible:
403
+ *
404
+ * - **Optimistic overrides are invisible** to the predicate. Your own
405
+ * tentative write can never satisfy your own ack, even on the
406
+ * single-primitive shape where the optimistic store IS the live-fed store.
407
+ * (Derived computeds serve their normal cached values — express the
408
+ * condition over sources of truth, not derived views of the overlay.)
409
+ * - **Everything else reads normally, including uncommitted transition-staged
410
+ * data.** Real data is real wherever it currently lives. This is
411
+ * load-bearing, not a loophole: truth that arrives *into* the open
412
+ * transaction (a `refresh()` this action issued, an entangled landing)
413
+ * stages and cannot commit until the hold releases — a predicate that
414
+ * refused staged reads would deadlock on the very data it is waiting for.
415
+ *
416
+ * This is the acknowledgment mechanism for mutations confirmed on a live data
417
+ * channel (sockets, subscriptions, live queries) rather than by the mutation's
418
+ * own response: correlate by a client-generated id or version in the predicate,
419
+ * and let truth arrive however it arrives — push, refetch, or another tab.
420
+ *
421
+ * Failure composes with action semantics: a rejection is thrown back into the
422
+ * generator at the `yield` point — catchable there, or the action fails and
423
+ * its optimistic state reverts.
424
+ *
425
+ * Must be called *outside* a tracking scope.
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * const send = action(async function* (text: string) {
430
+ * const clientId = crypto.randomUUID();
431
+ * setMessages(m => { m.push({ clientId, text, pending: true }); }); // optimistic
432
+ * await socket.send({ clientId, text }); // fire-and-forget transport
433
+ * // Hold until the live source echoes the write (authoritative view —
434
+ * // the optimistic row above cannot satisfy this):
435
+ * yield until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
436
+ * });
437
+ * ```
438
+ *
439
+ * @param fn a reactive predicate over authoritative state
440
+ * @param options optional `timeout` (ms) and abort `signal`
441
+ */ function until(e, t) {
442
+ // Late-bind the wakeup hook for the A17-silent ack paths (pay-for-use:
443
+ // apps that never call until() never retain it).
444
+ installAuthoritativeRead();
445
+ // Flip-entanglement (#3164 follow-up): the transaction this until() holds
446
+ // open (the action's, when yielded from one). The predicate is the user's
447
+ // declaration of what confirms it — when a foreign transition's staged
448
+ // write flips it truthy, that transition merges here and reveals at the
449
+ // joint settle instead of painting the confirmation under live optimism.
450
+ const n = activeTransition;
451
+ return new Promise((r, o) => {
452
+ const i = t?.signal;
453
+ if (i?.aborted) return o(i.reason);
454
+ createRoot(c => {
455
+ // Same delivery contract as resolve() (#2930): effect applies ride a
456
+ // microtask so the promise can settle while the transaction the caller
457
+ // yielded it into is still open — that transaction being open is the
458
+ // entire point of the hold.
459
+ const s = getOwner();
460
+ const u = new MicrotaskQueue;
461
+ u.ke = s.C;
462
+ s.C = u;
463
+ let f;
464
+ let a;
465
+ const settle = e => {
466
+ if (f !== undefined) clearTimeout(f);
467
+ if (a !== undefined) i.removeEventListener("abort", a);
468
+ e();
469
+ c();
470
+ };
471
+ effect(n === null ? e : () => {
472
+ const t = e();
473
+ // Runs inside the compute (pure phase): the confirming
474
+ // transition's stamps are live and its commit hasn't run, so
475
+ // the merge lands before any reveal. Falsy evaluations skip —
476
+ // non-flipping updates were never named as the confirmation.
477
+ if (t) entangleConfirmingTransitions(getObserver(), n);
478
+ return t;
479
+ }, e => {
480
+ // Falsy is "not yet": keep the subscription live and wait for the
481
+ // next evaluation. Only a truthy settled value resolves.
482
+ if (e) settle(() => r(e));
483
+ }, e => settle(() => o(e)),
484
+ // AUTHORITATIVE_READ: overrides invisible to the predicate.
485
+ // DIRECT_COMMIT: truth that stages into the held transaction (a
486
+ // refresh the action issued) must flow through to the microtask
487
+ // apply — a staged effect value would deadlock the hold on data
488
+ // the hold itself is keeping uncommitted.
489
+ {
490
+ user: true,
491
+ dt: CONFIG_AUTHORITATIVE_READ | CONFIG_DIRECT_COMMIT
271
492
  });
493
+ if (t?.timeout !== undefined) f = setTimeout(() => settle(() => o(new TimeoutError)), t.timeout);
494
+ if (i !== undefined) {
495
+ a = () => settle(() => o(i.reason));
496
+ i.addEventListener("abort", a, {
497
+ once: true
498
+ });
499
+ }
272
500
  });
273
501
  });
274
502
  }
@@ -391,4 +619,4 @@ function createOptimistic(e, t) {
391
619
  });
392
620
  }
393
621
 
394
- export { accessor, createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onSettled, resolve };
622
+ export { accessor, createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onSettled, refresh, resolve, until };