@solidjs/signals 2.0.0-rc.16 → 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 (56) hide show
  1. package/dist/dev-shared.js +1972 -1751
  2. package/dist/dev.attribution.js +16 -16
  3. package/dist/dev.js +672 -319
  4. package/dist/observe/boundaries.js +2 -2
  5. package/dist/observe/core/async.js +151 -116
  6. package/dist/observe/core/constants.js +3 -1
  7. package/dist/observe/core/context.js +2 -0
  8. package/dist/observe/core/core.js +51 -35
  9. package/dist/observe/core/effect.js +29 -30
  10. package/dist/observe/core/graph.js +13 -11
  11. package/dist/observe/core/lanes.js +25 -14
  12. package/dist/observe/core/owner.js +77 -51
  13. package/dist/observe/core/scheduler.js +202 -175
  14. package/dist/observe/core/verdict.js +5 -2
  15. package/dist/observe/deferred.js +16 -13
  16. package/dist/observe/from.js +248 -0
  17. package/dist/observe/index.js +2 -0
  18. package/dist/observe/map.js +2 -2
  19. package/dist/observe/signals.js +6 -1
  20. package/dist/observe/store/affects.js +13 -13
  21. package/dist/observe/store/optimistic.js +112 -71
  22. package/dist/observe/store/reconcile.js +137 -121
  23. package/dist/observe/store/store.js +612 -626
  24. package/dist/observe/store/target.js +1 -5
  25. package/dist/prod/boundaries.js +2 -2
  26. package/dist/prod/core/async.js +211 -176
  27. package/dist/prod/core/constants.js +3 -1
  28. package/dist/prod/core/context.js +2 -0
  29. package/dist/prod/core/core.js +63 -47
  30. package/dist/prod/core/effect.js +24 -25
  31. package/dist/prod/core/graph.js +12 -10
  32. package/dist/prod/core/heap.js +1 -1
  33. package/dist/prod/core/lanes.js +39 -28
  34. package/dist/prod/core/owner.js +70 -44
  35. package/dist/prod/core/scheduler.js +78 -51
  36. package/dist/prod/core/truth.js +4 -4
  37. package/dist/prod/core/verdict.js +11 -8
  38. package/dist/prod/deferred.js +17 -14
  39. package/dist/prod/from.js +248 -0
  40. package/dist/prod/index.js +2 -0
  41. package/dist/prod/map.js +2 -2
  42. package/dist/prod/signals.js +18 -13
  43. package/dist/prod/store/optimistic.js +112 -71
  44. package/dist/prod/store/reconcile.js +137 -121
  45. package/dist/prod/store/store.js +109 -121
  46. package/dist/prod/store/target.js +13 -17
  47. package/dist/types/core/async.d.ts +12 -2
  48. package/dist/types/core/constants.d.ts +5 -0
  49. package/dist/types/core/core.d.ts +29 -6
  50. package/dist/types/core/dev.d.ts +9 -1
  51. package/dist/types/core/error.d.ts +9 -1
  52. package/dist/types/core/owner.d.ts +4 -0
  53. package/dist/types/core/scheduler.d.ts +38 -7
  54. package/dist/types/from.d.ts +47 -0
  55. package/dist/types/index.d.ts +1 -0
  56. package/package.json +1 -1
@@ -139,8 +139,8 @@ let probeFound = false;
139
139
  * when every flight it waits on is. */ function quietPending(e) {
140
140
  const t = e.T;
141
141
  if (t === null) return false;
142
- if (t.fe !== undefined) {
143
- for (const e of t.fe) if (!e.T?.ut) return false;
142
+ if (t.se !== undefined) {
143
+ for (const e of t.se) if (!e.T?.ut) return false;
144
144
  return true;
145
145
  }
146
146
  return t.ut;
@@ -323,6 +323,9 @@ let probeFound = false;
323
323
  const u = (n.m & STATUS_PENDING) !== 0 && !quietPending(n);
324
324
  if (e.l & CONFIG_GUESS) {
325
325
  if (probing && (u || e.ue !== NOT_PENDING && (!e.Ye || !e.Ye(e.T.V, e.ue)))) probeFound = true;
326
+ // A lane a correction folded under (D-34): `latest()` shows the hold's
327
+ // world whole — the truth beneath each guess, else what it covered.
328
+ if (latestActive && r.folded) return r.folded(null, e);
326
329
  return e.T.V;
327
330
  }
328
331
  if (probing && u) probeFound = true;
@@ -135,26 +135,26 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
135
135
  // Sticky, and read before this wrapper ever opens the window itself.
136
136
  const n = l ||= !(t.m & STATUS_UNINITIALIZED) && !t.ce;
137
137
  if (n) t.ce = true, t.l |= CONFIG_LAGS;
138
- let c;
138
+ let s;
139
139
  try {
140
- c = e(r);
140
+ s = e(r);
141
141
  } catch (e) {
142
142
  // The wrapping form: an unready source parks the node in the window,
143
143
  // and the wait is its flight until the source's settle re-runs it.
144
- if (n && e instanceof NotReadyError) ask(e);
144
+ if (n && e instanceof NotReadyError) ask(e); else land(o);
145
145
  throw e;
146
146
  }
147
- if (n && typeof c === "object" && c !== null) {
148
- if (isThenable(c)) {
149
- ask(c);
150
- const done = () => land(c, true);
151
- c.then(done, done);
152
- return serve(c);
147
+ if (n && typeof s === "object" && s !== null) {
148
+ if (isThenable(s)) {
149
+ ask(s);
150
+ const done = () => land(s, true);
151
+ s.then(done, done);
152
+ return serve(s);
153
153
  }
154
- const e = c[Symbol.asyncIterator];
154
+ const e = s[Symbol.asyncIterator];
155
155
  if (e) {
156
156
  // A stream's flight ends at its first yield (or its end).
157
- const r = e.call(c);
157
+ const r = e.call(s);
158
158
  let t = true;
159
159
  const done = () => land(o, true);
160
160
  const o = {
@@ -162,7 +162,10 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
162
162
  // promise, as for a plain flight: verdict and value in one flush.
163
163
  next: e => {
164
164
  const o = r.next(e);
165
- if (t) t = false, o.then(done, done);
165
+ if (t) {
166
+ t = false;
167
+ if (isThenable(o)) o.then(done, done); else done();
168
+ }
166
169
  return o;
167
170
  },
168
171
  return: e => r.return?.(e) ?? Promise.resolve({
@@ -177,7 +180,7 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
177
180
  }
178
181
  }
179
182
  land(o);
180
- return c;
183
+ return s;
181
184
  }, r));
182
185
  }
183
186
 
@@ -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 };
@@ -38,6 +38,8 @@ export { affects } from "./affects.js";
38
38
 
39
39
  export { createDeferred } from "./deferred.js";
40
40
 
41
+ export { from } from "./from.js";
42
+
41
43
  export { flatten } from "./flatten.js";
42
44
 
43
45
  export { createErrorBoundary, createLoadingBoundary } from "./boundaries.js";
@@ -305,7 +305,7 @@ function updateKeyedMap() {
305
305
  this.Kt && (this.Kt = []);
306
306
  this.xt && (this.xt = []);
307
307
  }
308
- if (this.L && !this.vt[0]) {
308
+ if (this.L && !this.vt.length) {
309
309
  // an aborted fallback attempt leaves an owner without a mapping;
310
310
  // dispose it before re-creating
311
311
  this.je[0]?.dispose();
@@ -543,7 +543,7 @@ function updateRepeat() {
543
543
  // Reset offset to match the cleared data (#2767, repro 2).
544
544
  this.$t = 0;
545
545
  }
546
- if (this.L && !this.vt[0]) {
546
+ if (this.L && !this.vt.length) {
547
547
  // an aborted fallback attempt leaves an owner without a mapping;
548
548
  // dispose it before re-creating
549
549
  this.je[0]?.dispose();
@@ -296,7 +296,12 @@ function createMemo(e, t) {
296
296
  o = undefined;
297
297
  }
298
298
  runWithOwner(n, () => {
299
- effect(() => (c(), o = getOwner()), t => {
299
+ effect(() => {
300
+ const e = getOwner();
301
+ o = e;
302
+ c();
303
+ return e;
304
+ }, t => {
300
305
  o = undefined;
301
306
  r?.();
302
307
  const n = (e.effect || e)?.();
@@ -38,8 +38,8 @@ const scopes = new Map;
38
38
  function scopeOf(e, o) {
39
39
  let n = scopes.get(e);
40
40
  if (n === undefined) scopes.set(e, n = {
41
- eo: new Set,
42
- oo: [],
41
+ oo: new Set,
42
+ no: [],
43
43
  Vt: o
44
44
  });
45
45
  return n;
@@ -47,15 +47,15 @@ function scopeOf(e, o) {
47
47
 
48
48
  /** The nodes an `affects(store[, key])` declaration marks (affects.ts
49
49
  * registers each). */ function storeMarks(e, o) {
50
- const n = GlobalQueue.no(e, o === undefined ? $AFFECTS : o);
50
+ const n = GlobalQueue.so(e, o === undefined ? $AFFECTS : o);
51
51
  const t = scopeOf(n, o);
52
52
  if (o === undefined) {
53
53
  const o = [ n ];
54
54
  walk(e.px, t, o, e.fam, new Set);
55
55
  return o;
56
56
  }
57
- t.eo.add(e.v);
58
- if (e.pb !== null) t.eo.add(e.pb);
57
+ t.oo.add(e.v);
58
+ if (e.pb !== null) t.oo.add(e.pb);
59
59
  return [ n ];
60
60
  }
61
61
 
@@ -63,14 +63,14 @@ function scopeOf(e, o) {
63
63
  * the committed backing both, when a draft is open; an optimistic family's
64
64
  * view), every live node under each record into `found`. Untracked by
65
65
  * construction: raws, never traps. */ function walk(e, o, n, t, s) {
66
- if (!GlobalQueue.so(e)) return;
66
+ if (!GlobalQueue.fo(e)) return;
67
67
  const f = e[$TARGET] ?? lookupTarget(e, t);
68
68
  let r = f !== undefined ? f.pb ?? f.v : e;
69
69
  if (s.has(r)) return;
70
70
  s.add(r);
71
- o.eo.add(r);
71
+ o.oo.add(r);
72
72
  if (f !== undefined) {
73
- if (f.pb !== null) o.eo.add(f.v);
73
+ if (f.pb !== null) o.oo.add(f.v);
74
74
  // The writer's view: the tick's own unflushed guesses are in motion too.
75
75
  if (f.fam?.opt === true) r = optHooks.view(f, r, true);
76
76
  const e = f.n;
@@ -96,7 +96,7 @@ function scopeOf(e, o) {
96
96
  for (const [t, s] of scopes) {
97
97
  if (t === n || s.Vt !== undefined && s.Vt !== o) continue;
98
98
  for (let o = e.v; ;) {
99
- if (s.eo.has(o)) return t;
99
+ if (s.oo.has(o)) return t;
100
100
  const e = o?.[$TARGET];
101
101
  if (e === undefined) break;
102
102
  const n = e.pb ?? e.v;
@@ -112,17 +112,17 @@ function scopeOf(e, o) {
112
112
  * no work. */ const hooks = {
113
113
  // A node born on a record a live mark covers inherits it (released with
114
114
  // the carrier's last registration).
115
- fo(e, o, n) {
115
+ ro(e, o, n) {
116
116
  if (scopes.size === 0 || n === $AFFECTS) return;
117
117
  const t = covering(e, n, o);
118
118
  if (!t) return;
119
119
  GlobalQueue.N(o);
120
- scopes.get(t).oo.push(o);
120
+ scopes.get(t).no.push(o);
121
121
  },
122
122
  // An untracked probe (`isPending(() => s.x)` with no observer) reading a
123
123
  // record a live mark covers: no node carries the mark for it — the
124
124
  // verdict is told directly.
125
- ro(e, o) {
125
+ io(e, o) {
126
126
  if (e.n?.[$AFFECTS]?.T?.I || covering(e, o)) GlobalQueue.bt();
127
127
  }
128
128
  };
@@ -136,7 +136,7 @@ function scopeOf(e, o) {
136
136
  const o = scopes.get(e);
137
137
  if (o === undefined) return;
138
138
  scopes.delete(e);
139
- releaseMarksHook(o.oo);
139
+ releaseMarksHook(o.no);
140
140
  }
141
141
 
142
142
  export { installStoreAffects, releaseMarkScope, storeMarks };