@solidjs/signals 2.0.0-rc.17 → 2.0.0-rc.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.
Files changed (40) hide show
  1. package/dist/dev-shared.js +296 -191
  2. package/dist/dev.attribution.js +16 -16
  3. package/dist/dev.js +527 -311
  4. package/dist/observe/core/async.js +148 -115
  5. package/dist/observe/core/constants.js +3 -1
  6. package/dist/observe/core/core.js +27 -27
  7. package/dist/observe/core/effect.js +29 -32
  8. package/dist/observe/core/graph.js +5 -5
  9. package/dist/observe/core/owner.js +7 -7
  10. package/dist/observe/core/scheduler.js +23 -12
  11. package/dist/observe/core/verdict.js +2 -2
  12. package/dist/observe/deferred.js +16 -13
  13. package/dist/observe/from.js +248 -0
  14. package/dist/observe/index.js +2 -0
  15. package/dist/observe/store/reconcile.js +137 -121
  16. package/dist/observe/store/store.js +595 -618
  17. package/dist/observe/store/target.js +1 -5
  18. package/dist/prod/core/async.js +209 -176
  19. package/dist/prod/core/constants.js +3 -1
  20. package/dist/prod/core/core.js +33 -33
  21. package/dist/prod/core/effect.js +24 -27
  22. package/dist/prod/core/graph.js +2 -2
  23. package/dist/prod/core/lanes.js +14 -14
  24. package/dist/prod/core/scheduler.js +35 -24
  25. package/dist/prod/core/truth.js +4 -4
  26. package/dist/prod/core/verdict.js +7 -7
  27. package/dist/prod/deferred.js +17 -14
  28. package/dist/prod/from.js +248 -0
  29. package/dist/prod/index.js +2 -0
  30. package/dist/prod/store/optimistic.js +1 -1
  31. package/dist/prod/store/reconcile.js +137 -121
  32. package/dist/prod/store/store.js +94 -117
  33. package/dist/prod/store/target.js +13 -17
  34. package/dist/types/core/async.d.ts +12 -2
  35. package/dist/types/core/constants.d.ts +5 -0
  36. package/dist/types/core/core.d.ts +19 -3
  37. package/dist/types/core/scheduler.d.ts +15 -3
  38. package/dist/types/from.d.ts +47 -0
  39. package/dist/types/index.d.ts +1 -0
  40. package/package.json +1 -1
@@ -157,7 +157,7 @@ let probeFound = false;
157
157
  * flight — a staging equal to what is displayed changes nothing when it
158
158
  * lands: final now (maintainer, 2026-10-02). */ function heldNotFinal(e) {
159
159
  if ((e.m & STATUS_PENDING) !== 0 && !quietPending(e)) return true;
160
- return e.se !== NOT_PENDING && !latestActive && !e.T.Ve && !(e.m & STATUS_UNINITIALIZED) && (!!e.T.de || !e.xe || !e.xe(e.Ie, e.se));
160
+ return e.ue !== NOT_PENDING && !latestActive && !e.T.Ve && !(e.m & STATUS_UNINITIALIZED) && (!!e.T.de || !e.xe || !e.xe(e.Ie, e.ue));
161
161
  }
162
162
 
163
163
  /** `isPending`'s answer for an unheld node whose flight is up (cause ii). */ function pendingVerdict(e, t) {
@@ -256,8 +256,8 @@ let probeFound = false;
256
256
  }
257
257
 
258
258
  /** The staging, or the staged error thrown (D-41). */ function staged(e) {
259
- if (e.l & CONFIG_STAGED_ERROR) throw e.se;
260
- return e.se;
259
+ if (e.l & CONFIG_STAGED_ERROR) throw e.ue;
260
+ return e.ue;
261
261
  }
262
262
 
263
263
  /** `latest()` of a held node: the flushed staging — the proposal — and, for
@@ -317,7 +317,7 @@ let probeFound = false;
317
317
  if (i) verdictRead(t, r._parent ?? r);
318
318
  const u = (n.m & STATUS_PENDING) !== 0 && !quietPending(n);
319
319
  if (e.l & CONFIG_GUESS) {
320
- if (probing && (u || e.se !== NOT_PENDING && (!e.xe || !e.xe(e.T.v, e.se)))) probeFound = true;
320
+ if (probing && (u || e.ue !== NOT_PENDING && (!e.xe || !e.xe(e.T.v, e.ue)))) probeFound = true;
321
321
  // A lane a correction folded under (D-34): `latest()` shows the hold's
322
322
  // world whole — the truth beneath each guess, else what it covered.
323
323
  if (latestActive && r.folded) return r.folded(null, e);
@@ -341,7 +341,7 @@ let probeFound = false;
341
341
  }
342
342
  if (i) verdictRead(t, r);
343
343
  if (probing && heldNotFinal(n)) probeFound = true;
344
- if (latestActive && e.se !== NOT_PENDING) {
344
+ if (latestActive && e.ue !== NOT_PENDING) {
345
345
  // An action body reading another transaction's proposal derives
346
346
  // from it: the two settle as one (A15; posture C, 2026-09-15). An
347
347
  // untracked read elsewhere has no transaction to entangle.
@@ -354,11 +354,11 @@ let probeFound = false;
354
354
  // committed value, so no verdict: `isPending` reads it as its read does —
355
355
  // the owner joins the hold (held with it, or caught by a boundary that
356
356
  // has not shown content) and answers for the commit, where it is final.
357
- if (e.se !== NOT_PENDING && !(n.m & STATUS_PENDING)) {
357
+ if (e.ue !== NOT_PENDING && !(n.m & STATUS_PENDING)) {
358
358
  if (probing && r) joinPass(t, e);
359
359
  return staged(e);
360
360
  }
361
- } else if (e.se !== NOT_PENDING) {
361
+ } else if (e.ue !== NOT_PENDING) {
362
362
  if (n.m & STATUS_PENDING && !u) pendingVerdict(i ? t : null, n); else if (i && globalQueue.q) provisionalVerdict(t, !latestActive && !u);
363
363
  // Outside a flush, the staging is an unflushed write (A28): the probe
364
364
  // answers for the flushed world, and watches the seam of the flush that
@@ -121,7 +121,7 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
121
121
  * does not replace (D-40) — restaged as the committed value, the hold's
122
122
  * readers would re-derive against the old input. */ const serve = e => {
123
123
  const r = handleAsync(t, e);
124
- return t.l & CONFIG_HELD && t.se !== NOT_PENDING ? t.se : r;
124
+ return t.l & CONFIG_HELD && t.ue !== NOT_PENDING ? t.ue : r;
125
125
  };
126
126
  const end = () => t.R & REACTIVE_DISPOSED && land(o);
127
127
  return accessor(computed(r => {
@@ -133,26 +133,26 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
133
133
  // Sticky, and read before this wrapper ever opens the window itself.
134
134
  const n = l ||= !(t.m & STATUS_UNINITIALIZED) && !t.ce;
135
135
  if (n) t.ce = true, t.l |= CONFIG_LAGS;
136
- let c;
136
+ let s;
137
137
  try {
138
- c = e(r);
138
+ s = e(r);
139
139
  } catch (e) {
140
140
  // The wrapping form: an unready source parks the node in the window,
141
141
  // and the wait is its flight until the source's settle re-runs it.
142
- if (n && e instanceof NotReadyError) ask(e);
142
+ if (n && e instanceof NotReadyError) ask(e); else land(o);
143
143
  throw e;
144
144
  }
145
- if (n && typeof c === "object" && c !== null) {
146
- if (isThenable(c)) {
147
- ask(c);
148
- const done = () => land(c, true);
149
- c.then(done, done);
150
- return serve(c);
145
+ if (n && typeof s === "object" && s !== null) {
146
+ if (isThenable(s)) {
147
+ ask(s);
148
+ const done = () => land(s, true);
149
+ s.then(done, done);
150
+ return serve(s);
151
151
  }
152
- const e = c[Symbol.asyncIterator];
152
+ const e = s[Symbol.asyncIterator];
153
153
  if (e) {
154
154
  // A stream's flight ends at its first yield (or its end).
155
- const r = e.call(c);
155
+ const r = e.call(s);
156
156
  let t = true;
157
157
  const done = () => land(o, true);
158
158
  const o = {
@@ -160,7 +160,10 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
160
160
  // promise, as for a plain flight: verdict and value in one flush.
161
161
  next: e => {
162
162
  const o = r.next(e);
163
- if (t) t = false, o.then(done, done);
163
+ if (t) {
164
+ t = false;
165
+ if (isThenable(o)) o.then(done, done); else done();
166
+ }
164
167
  return o;
165
168
  },
166
169
  return: e => r.return?.(e) ?? Promise.resolve({
@@ -175,7 +178,7 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
175
178
  }
176
179
  }
177
180
  land(o);
178
- return c;
181
+ return s;
179
182
  }, r));
180
183
  }
181
184
 
@@ -0,0 +1,248 @@
1
+ import { isThenable, onSupersede } from "./core/async.js";
2
+
3
+ import { CONFIG_CHILDREN_FORBIDDEN } from "./core/constants.js";
4
+
5
+ import { cleanup, getOwner } from "./core/owner.js";
6
+
7
+ // `from` — a push source (callback subscription, Observable, AsyncIterable)
8
+ // as an AsyncIterable a computation can return or `for await` over:
9
+ // const value = createMemo(() => from(source));
10
+ // A subscription keeps the latest value: values that arrive between pulls
11
+ // coalesce. An AsyncIterable streams through unchanged. Either way the source
12
+ // opens when the result is iterated and closes on return(), on supersede (the
13
+ // owning computation re-runs) and on owner disposal, whichever comes first.
14
+ function toSubscribe(e) {
15
+ if (typeof e === "function") return e;
16
+ // TC39 / RxJS interop: `Symbol.observable` (or "@@observable") returns the observable.
17
+ // Read per call, not at module scope, so the module stays side-effect free.
18
+ const n = Symbol.observable;
19
+ const r = (n && e?.[n]) ?? e?.["@@observable"];
20
+ const t = typeof r === "function" ? r.call(e) : e;
21
+ if (t && typeof t.subscribe === "function") return (e, n, r) => t.subscribe({
22
+ next: e,
23
+ error: n,
24
+ complete: r
25
+ });
26
+ return null;
27
+ }
28
+
29
+ const DONE = {
30
+ value: undefined,
31
+ done: true
32
+ };
33
+
34
+ /**
35
+ * Ties an iterator's `finish` to the computation iterating it. Solid reaches
36
+ * an iterator's return() only when the compute returns the iterable directly.
37
+ * Inside an async generator the generator's own return() is queued behind its
38
+ * pending `await`, and owner cleanups wait for the replacing pass to commit,
39
+ * so close on supersede, with owner disposal as the backstop. No owner (an
40
+ * async compute iterating it after its first `await`, or plain iteration):
41
+ * no registration and no diagnostic, so only return() closes it. Scopes that
42
+ * forbid cleanups rely on supersede and return().
43
+ */ function bindToOwner(e) {
44
+ const n = getOwner();
45
+ if (!n) return;
46
+ onSupersede(e);
47
+ if (!(n.l & CONFIG_CHILDREN_FORBIDDEN)) cleanup(e);
48
+ }
49
+
50
+ /**
51
+ * An AsyncIterable as a stream bound to the iterating computation: every
52
+ * value passes through (no coalescing). `finish` calls the inner return()
53
+ * once and settles pending pulls as done, so a `for await` over it exits as
54
+ * soon as the computation re-runs — even when the inner iterator is an async
55
+ * generator whose own return() the language queues behind its pending
56
+ * `await`.
57
+ */ function wrapIterable(e) {
58
+ return {
59
+ [Symbol.asyncIterator]() {
60
+ const n = e[Symbol.asyncIterator]();
61
+ let r = false;
62
+ const t = new Set;
63
+ const finish = () => {
64
+ if (r) return DONE;
65
+ r = true;
66
+ for (const e of t) e(DONE);
67
+ t.clear();
68
+ try {
69
+ const e = n.return?.();
70
+ if (isThenable(e)) e.then(undefined, () => {});
71
+ } catch {}
72
+ return DONE;
73
+ };
74
+ bindToOwner(finish);
75
+ return {
76
+ next(...e) {
77
+ if (r) return Promise.resolve(DONE);
78
+ const o = n.next(...e);
79
+ // A bare result (the producer's buffered fast path) passes through.
80
+ if (!isThenable(o)) return o;
81
+ return new Promise((e, n) => {
82
+ t.add(e);
83
+ o.then(n => t.delete(e) && e(n), r => t.delete(e) && n(r));
84
+ });
85
+ },
86
+ return() {
87
+ return Promise.resolve(finish());
88
+ }
89
+ };
90
+ }
91
+ };
92
+ }
93
+
94
+ const toCleanup = e => typeof e === "function" ? e : e && typeof e.unsubscribe === "function" ? () => e.unsubscribe() : () => {};
95
+
96
+ /**
97
+ * Converts a callback subscription, an Observable or an AsyncIterable into an
98
+ * AsyncIterable for a Solid computation. A subscription keeps only the latest
99
+ * value, and a value the source emits during `subscribe` is served without a
100
+ * loading frame; an AsyncIterable streams every value through.
101
+ *
102
+ * The source closes on supersede — the owning computation re-running — and
103
+ * on owner disposal. Inside an async generator memo that holds for a
104
+ * `for await` that starts before the first `await`; one that starts after it
105
+ * has no owner to bind to, so its source closes only when the loop ends.
106
+ * Prefer returning `from(...)` from its own memo, with awaited inputs read
107
+ * from upstream memos and transforms done in a downstream memo.
108
+ *
109
+ * Iterate the result with `for await` or return it from a computation: its
110
+ * `next()` returns an already-buffered value as a bare result, not a promise.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const position = createMemo(() =>
115
+ * from<Position>((next, error) => {
116
+ * const id = navigator.geolocation.watchPosition(next, error);
117
+ * return () => navigator.geolocation.clearWatch(id);
118
+ * })
119
+ * );
120
+ * const ticks = createMemo(() => from(interval$(rate())));
121
+ * ```
122
+ */ function from(e) {
123
+ const n = toSubscribe(e);
124
+ if (!n) {
125
+ if (e && typeof e[Symbol.asyncIterator] === "function") return wrapIterable(e);
126
+ throw new TypeError("from(): expected a subscribe function, an Observable or an AsyncIterable");
127
+ }
128
+ return {
129
+ [Symbol.asyncIterator]() {
130
+ let e;
131
+ let r = false;
132
+ let t = false;
133
+ let o = false;
134
+ let u;
135
+ let f = null;
136
+ let l = null;
137
+ let s = false;
138
+ const close = () => {
139
+ if (s) return;
140
+ s = true;
141
+ t = true;
142
+ const e = l;
143
+ l = null;
144
+ // A throwing unsubscribe must not escape: at supersede this runs
145
+ // inside flush(), ahead of the replacing pass (the returned-iterator
146
+ // path swallows it the same way).
147
+ try {
148
+ e?.();
149
+ } catch {}
150
+ };
151
+ const settle = () => {
152
+ if (!f) return;
153
+ const n = f;
154
+ if (r) {
155
+ f = null;
156
+ r = false;
157
+ n.resolve({
158
+ value: e,
159
+ done: false
160
+ });
161
+ } else if (o) {
162
+ f = null;
163
+ o = false;
164
+ n.reject(u);
165
+ } else if (t) {
166
+ f = null;
167
+ n.resolve({
168
+ value: undefined,
169
+ done: true
170
+ });
171
+ }
172
+ };
173
+ const i = n(n => {
174
+ if (t) return;
175
+ e = n;
176
+ r = true;
177
+ settle();
178
+ }, e => {
179
+ if (t) return;
180
+ o = true;
181
+ u = e;
182
+ t = true;
183
+ settle();
184
+ close();
185
+ }, () => {
186
+ if (t) return;
187
+ t = true;
188
+ settle();
189
+ close();
190
+ });
191
+ l = toCleanup(i);
192
+ // Completed or errored during subscribe: release what it just returned.
193
+ if (s) {
194
+ const e = l;
195
+ l = null;
196
+ try {
197
+ e();
198
+ } catch {}
199
+ }
200
+ // Idempotent: return(), supersede and disposal may all reach it.
201
+ const finish = () => {
202
+ close();
203
+ r = false;
204
+ if (f) {
205
+ const e = f;
206
+ f = null;
207
+ e.resolve(DONE);
208
+ }
209
+ return DONE;
210
+ };
211
+ bindToOwner(finish);
212
+ return {
213
+ next() {
214
+ // A buffered value is returned as a bare result, not a promise: the
215
+ // iterator consumer lands it in the same pass, so a source that
216
+ // emits during subscribe (BehaviorSubject-style) shows its value
217
+ // with no loading frame. `for await` accepts either form.
218
+ if (r) {
219
+ r = false;
220
+ return {
221
+ value: e,
222
+ done: false
223
+ };
224
+ }
225
+ if (o) {
226
+ o = false;
227
+ return Promise.reject(u);
228
+ }
229
+ if (t) return Promise.resolve({
230
+ value: undefined,
231
+ done: true
232
+ });
233
+ return new Promise((e, n) => {
234
+ f = {
235
+ resolve: e,
236
+ reject: n
237
+ };
238
+ });
239
+ },
240
+ return() {
241
+ return Promise.resolve(finish());
242
+ }
243
+ };
244
+ }
245
+ };
246
+ }
247
+
248
+ export { from };
@@ -36,6 +36,8 @@ export { affects } from "./affects.js";
36
36
 
37
37
  export { createDeferred } from "./deferred.js";
38
38
 
39
+ export { from } from "./from.js";
40
+
39
41
  export { flatten } from "./flatten.js";
40
42
 
41
43
  export { createErrorBoundary, createLoadingBoundary } from "./boundaries.js";
@@ -365,7 +365,7 @@ function arrangementEqual(e, n, t) {
365
365
  const t = txOf(n);
366
366
  joinPassAt(e, resolveTx(t._parent ?? t));
367
367
  }
368
- if (n.se !== NOT_PENDING) return n.se;
368
+ if (n.ue !== NOT_PENDING) return n.ue;
369
369
  return n.l & CONFIG_SLOT_NODE ? GlobalQueue.fn(n) : n.Ie;
370
370
  }
371
371