@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.
- package/dist/dev-shared.js +296 -191
- package/dist/dev.attribution.js +16 -16
- package/dist/dev.js +527 -311
- package/dist/observe/core/async.js +148 -115
- package/dist/observe/core/constants.js +3 -1
- package/dist/observe/core/core.js +27 -27
- package/dist/observe/core/effect.js +29 -32
- package/dist/observe/core/graph.js +5 -5
- package/dist/observe/core/owner.js +7 -7
- package/dist/observe/core/scheduler.js +23 -12
- package/dist/observe/core/verdict.js +2 -2
- package/dist/observe/deferred.js +16 -13
- package/dist/observe/from.js +248 -0
- package/dist/observe/index.js +2 -0
- package/dist/observe/store/reconcile.js +137 -121
- package/dist/observe/store/store.js +595 -618
- package/dist/observe/store/target.js +1 -5
- package/dist/prod/core/async.js +209 -176
- package/dist/prod/core/constants.js +3 -1
- package/dist/prod/core/core.js +33 -33
- package/dist/prod/core/effect.js +24 -27
- package/dist/prod/core/graph.js +2 -2
- package/dist/prod/core/lanes.js +14 -14
- package/dist/prod/core/scheduler.js +35 -24
- package/dist/prod/core/truth.js +4 -4
- package/dist/prod/core/verdict.js +7 -7
- package/dist/prod/deferred.js +17 -14
- package/dist/prod/from.js +248 -0
- package/dist/prod/index.js +2 -0
- package/dist/prod/store/optimistic.js +1 -1
- package/dist/prod/store/reconcile.js +137 -121
- package/dist/prod/store/store.js +94 -117
- package/dist/prod/store/target.js +13 -17
- package/dist/types/core/async.d.ts +12 -2
- package/dist/types/core/constants.d.ts +5 -0
- package/dist/types/core/core.d.ts +19 -3
- package/dist/types/core/scheduler.d.ts +15 -3
- package/dist/types/from.d.ts +47 -0
- package/dist/types/index.d.ts +1 -0
- 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.
|
|
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.
|
|
260
|
-
return e.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
package/dist/prod/deferred.js
CHANGED
|
@@ -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.
|
|
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
|
|
136
|
+
let s;
|
|
137
137
|
try {
|
|
138
|
-
|
|
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
|
|
146
|
-
if (isThenable(
|
|
147
|
-
ask(
|
|
148
|
-
const done = () => land(
|
|
149
|
-
|
|
150
|
-
return serve(
|
|
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 =
|
|
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(
|
|
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)
|
|
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
|
|
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 };
|
package/dist/prod/index.js
CHANGED
|
@@ -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.
|
|
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
|
|