@fluixi/reactive 1.0.0-alpha.83 → 1.0.0-alpha.84
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/cdn/reactive.cjs +2673 -1
- package/dist/cdn/reactive.global.js +2 -1
- package/dist/cdn/reactive.mjs +2657 -1
- package/dist/cdn/signal.cjs +299 -1
- package/dist/cdn/signal.d.ts +1 -1
- package/dist/cdn/signal.global.js +2 -1
- package/dist/cdn/signal.js +1 -1
- package/dist/cdn/signal.mjs +276 -1
- package/dist/index.cjs +2673 -1
- package/dist/index.mjs +2657 -1
- package/dist/lib/index.cjs +2674 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/index.mjs +2659 -1
- package/dist/lib/observable/index.cjs +386 -1
- package/dist/lib/observable/index.mjs +364 -1
- package/dist/lib/observable/observable.cjs +384 -1
- package/dist/lib/observable/observable.d.ts +0 -137
- package/dist/lib/observable/observable.d.ts.map +1 -1
- package/dist/lib/observable/observable.js +0 -71
- package/dist/lib/observable/observable.mjs +361 -1
- package/dist/lib/observable/subscription.cjs +180 -1
- package/dist/lib/observable/subscription.d.ts +0 -12
- package/dist/lib/observable/subscription.d.ts.map +1 -1
- package/dist/lib/observable/subscription.js +0 -12
- package/dist/lib/observable/subscription.mjs +157 -1
- package/dist/lib/observable/types.cjs +18 -1
- package/dist/lib/observable/types.d.ts.map +1 -1
- package/dist/lib/predicates.cjs +44 -1
- package/dist/lib/predicates.d.ts +2 -2
- package/dist/lib/predicates.js +2 -2
- package/dist/lib/predicates.mjs +23 -1
- package/dist/lib/signal/api.cjs +571 -1
- package/dist/lib/signal/api.d.ts +211 -1
- package/dist/lib/signal/api.d.ts.map +1 -1
- package/dist/lib/signal/api.js +207 -12
- package/dist/lib/signal/api.mjs +548 -1
- package/dist/lib/signal/cached-resource.cjs +672 -1
- package/dist/lib/signal/cached-resource.d.ts +28 -16
- package/dist/lib/signal/cached-resource.d.ts.map +1 -1
- package/dist/lib/signal/cached-resource.js +35 -29
- package/dist/lib/signal/cached-resource.mjs +650 -1
- package/dist/lib/signal/graph/children.cjs +325 -1
- package/dist/lib/signal/graph/children.js +1 -1
- package/dist/lib/signal/graph/children.mjs +302 -1
- package/dist/lib/signal/graph/compat.cjs +578 -1
- package/dist/lib/signal/graph/compat.d.ts +13 -13
- package/dist/lib/signal/graph/compat.d.ts.map +1 -1
- package/dist/lib/signal/graph/compat.js +33 -31
- package/dist/lib/signal/graph/compat.mjs +556 -1
- package/dist/lib/signal/graph/core.cjs +295 -1
- package/dist/lib/signal/graph/core.d.ts +28 -24
- package/dist/lib/signal/graph/core.d.ts.map +1 -1
- package/dist/lib/signal/graph/core.js +38 -31
- package/dist/lib/signal/graph/core.mjs +273 -1
- package/dist/lib/signal/graph/debounced.cjs +668 -1
- package/dist/lib/signal/graph/debounced.d.ts +5 -5
- package/dist/lib/signal/graph/debounced.d.ts.map +1 -1
- package/dist/lib/signal/graph/debounced.js +5 -5
- package/dist/lib/signal/graph/debounced.mjs +645 -1
- package/dist/lib/signal/graph/derived.cjs +496 -1
- package/dist/lib/signal/graph/derived.d.ts +1 -1
- package/dist/lib/signal/graph/derived.js +1 -1
- package/dist/lib/signal/graph/derived.mjs +473 -1
- package/dist/lib/signal/graph/detect.cjs +66 -1
- package/dist/lib/signal/graph/detect.mjs +45 -1
- package/dist/lib/signal/graph/error-boundary.cjs +301 -1
- package/dist/lib/signal/graph/error-boundary.js +3 -3
- package/dist/lib/signal/graph/error-boundary.mjs +278 -1
- package/dist/lib/signal/graph/index.cjs +305 -1
- package/dist/lib/signal/graph/index.d.ts +7 -6
- package/dist/lib/signal/graph/index.d.ts.map +1 -1
- package/dist/lib/signal/graph/index.js +7 -6
- package/dist/lib/signal/graph/index.mjs +283 -1
- package/dist/lib/signal/graph/observe.cjs +163 -1
- package/dist/lib/signal/graph/observe.d.ts +8 -8
- package/dist/lib/signal/graph/observe.js +5 -5
- package/dist/lib/signal/graph/observe.mjs +140 -1
- package/dist/lib/signal/graph/resource.cjs +620 -1
- package/dist/lib/signal/graph/resource.d.ts +1 -1
- package/dist/lib/signal/graph/resource.js +7 -8
- package/dist/lib/signal/graph/resource.mjs +597 -1
- package/dist/lib/signal/graph/runtime.cjs +501 -1
- package/dist/lib/signal/graph/runtime.d.ts +7 -7
- package/dist/lib/signal/graph/runtime.d.ts.map +1 -1
- package/dist/lib/signal/graph/runtime.js +34 -30
- package/dist/lib/signal/graph/runtime.mjs +478 -1
- package/dist/lib/signal/graph/signal-next.cjs +1208 -1
- package/dist/lib/signal/graph/signal-next.d.ts +5 -5
- package/dist/lib/signal/graph/signal-next.d.ts.map +1 -1
- package/dist/lib/signal/graph/signal-next.js +14 -12
- package/dist/lib/signal/graph/signal-next.mjs +1186 -1
- package/dist/lib/signal/graph/ssr-hooks.cjs +60 -1
- package/dist/lib/signal/graph/ssr-hooks.d.ts +6 -7
- package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -1
- package/dist/lib/signal/graph/ssr-hooks.js +6 -7
- package/dist/lib/signal/graph/ssr-hooks.mjs +39 -1
- package/dist/lib/signal/graph/state.cjs +72 -1
- package/dist/lib/signal/graph/state.d.ts +41 -10
- package/dist/lib/signal/graph/state.d.ts.map +1 -1
- package/dist/lib/signal/graph/state.js +83 -22
- package/dist/lib/signal/graph/state.mjs +49 -1
- package/dist/lib/signal/graph/suspense.cjs +301 -1
- package/dist/lib/signal/graph/suspense.js +4 -4
- package/dist/lib/signal/graph/suspense.mjs +278 -1
- package/dist/lib/signal/index-legacy.cjs +2540 -1
- package/dist/lib/signal/index-legacy.d.ts +1 -1
- package/dist/lib/signal/index-legacy.js +1 -1
- package/dist/lib/signal/index-legacy.mjs +2518 -1
- package/dist/lib/signal/index.cjs +1275 -1
- package/dist/lib/signal/index.d.ts +3 -2
- package/dist/lib/signal/index.d.ts.map +1 -1
- package/dist/lib/signal/index.js +7 -4
- package/dist/lib/signal/index.mjs +1253 -1
- package/dist/lib/signal/list.cjs +549 -1
- package/dist/lib/signal/list.d.ts +5 -5
- package/dist/lib/signal/list.js +5 -5
- package/dist/lib/signal/list.mjs +526 -1
- package/dist/lib/signal/resource-api.cjs +626 -1
- package/dist/lib/signal/resource-api.d.ts +5 -5
- package/dist/lib/signal/resource-api.js +5 -5
- package/dist/lib/signal/resource-api.mjs +603 -1
- package/dist/lib/signal/signal.cjs +2023 -1
- package/dist/lib/signal/signal.d.ts +4 -4
- package/dist/lib/signal/signal.d.ts.map +1 -1
- package/dist/lib/signal/signal.js +22 -23
- package/dist/lib/signal/signal.mjs +2000 -1
- package/dist/lib/signal/types.cjs +38 -1
- package/dist/lib/signal/types.d.ts.map +1 -1
- package/dist/lib/signal/types.js +2 -2
- package/dist/lib/signal/types.mjs +17 -1
- package/dist/lib/signal/utilities.cjs +1012 -1
- package/dist/lib/signal/utilities.d.ts +0 -108
- package/dist/lib/signal/utilities.d.ts.map +1 -1
- package/dist/lib/signal/utilities.js +0 -158
- package/dist/lib/signal/utilities.mjs +990 -1
- package/dist/lib/store/api.cjs +612 -1
- package/dist/lib/store/api.d.ts +33 -0
- package/dist/lib/store/api.d.ts.map +1 -1
- package/dist/lib/store/api.js +41 -8
- package/dist/lib/store/api.mjs +596 -1
- package/dist/lib/store/index.cjs +691 -1
- package/dist/lib/store/index.js +1 -1
- package/dist/lib/store/index.mjs +676 -1
- package/dist/lib/store/setter.types.cjs +18 -1
- package/dist/lib/store/setter.types.d.ts +43 -3
- package/dist/lib/store/setter.types.d.ts.map +1 -1
- package/dist/lib/store/store.cjs +658 -1
- package/dist/lib/store/store.d.ts +72 -0
- package/dist/lib/store/store.d.ts.map +1 -1
- package/dist/lib/store/store.js +148 -5
- package/dist/lib/store/store.mjs +644 -1
- package/dist/lib/store/types.cjs +18 -1
- package/dist/lib/store/types.d.ts +1 -1
- package/dist/lib/store/types.d.ts.map +1 -1
- package/dist/lib/store/utils.cjs +43 -1
- package/dist/lib/store/utils.d.ts +1 -1
- package/dist/lib/store/utils.js +1 -1
- package/dist/lib/store/utils.mjs +22 -1
- package/dist/lib/subject.cjs +450 -1
- package/dist/lib/subject.js +3 -91
- package/dist/lib/subject.mjs +427 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/version.generated.cjs +27 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +2 -2
- package/dist/version.generated.mjs +7 -1
- package/package.json +5 -4
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* the L0 graph core (./core.ts). TC39 ships only State/Computed/Watcher; everything
|
|
4
|
-
* a framework needs — ownership/disposal, a scheduler, batching, and effects — is
|
|
5
|
-
* left to userland. This module is that userland, expressed once, cleanly:
|
|
2
|
+
* The runtime layer, on top of the graph core in `core.ts`.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - effect(): re-running side effect built on a Computed + a Watcher + an owner
|
|
4
|
+
* TC39 ships `State`, `Computed` and `Watcher` and stops there. Everything else a
|
|
5
|
+
* framework needs is left to userland, and this module is that userland:
|
|
10
6
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
7
|
+
* - Owner tree: createRoot, onCleanup, getOwner, runWithOwner. No graph edges;
|
|
8
|
+
* ownership is about disposal, not dependency.
|
|
9
|
+
* - Scheduler: an effect queue with flushSync and batch.
|
|
10
|
+
* - effect(): a re-running side effect, built from a Computed, a Watcher and
|
|
11
|
+
* an owner.
|
|
12
|
+
*
|
|
13
|
+
* This is the live implementation. `@fluixi/reactive/signal` re-exports it through
|
|
14
|
+
* `signal-next.ts`, and `compat.ts` puts the `create*` names over the top, so the
|
|
15
|
+
* long-standing public API lands here.
|
|
16
|
+
*
|
|
17
|
+
* Effects are built on `Signal.subtle.Watcher` the way the proposal intends: the
|
|
18
|
+
* watcher's notify only schedules, and the re-run, a tracked recompute, happens on
|
|
19
|
+
* flush.
|
|
16
20
|
*/
|
|
17
21
|
import { Computed, Watcher } from './core.js';
|
|
18
22
|
import { G } from './state.js';
|
|
@@ -22,7 +26,7 @@ import { reportReactiveContext, reportReactiveContextCreated, reportReactiveDisp
|
|
|
22
26
|
*
|
|
23
27
|
* For a derivation that re-runs: context still resolves up the chain from the
|
|
24
28
|
* creation-time owner, while anything registered during a run belongs to this
|
|
25
|
-
* scope and can be torn down before the next one
|
|
29
|
+
* scope and can be torn down before the next one: instead of accumulating on
|
|
26
30
|
* the enclosing scope and only running when that finally dies.
|
|
27
31
|
*/
|
|
28
32
|
export function createChildOwner(parent) {
|
|
@@ -34,7 +38,7 @@ export function createChildOwner(parent) {
|
|
|
34
38
|
/**
|
|
35
39
|
* Tear down a scope's children and cleanups.
|
|
36
40
|
*
|
|
37
|
-
* `final: false` keeps the scope itself alive and reusable
|
|
41
|
+
* `final: false` keeps the scope itself alive and reusable, what a re-running
|
|
38
42
|
* derivation needs between runs.
|
|
39
43
|
*/
|
|
40
44
|
export function disposeScope(owner, final = true) {
|
|
@@ -54,10 +58,10 @@ export function createContext(defaultValue, name) {
|
|
|
54
58
|
const id = Symbol(name ?? 'context');
|
|
55
59
|
reportReactiveContextCreated(id);
|
|
56
60
|
const Provider = (props) => {
|
|
57
|
-
// Stamp the value on a DEDICATED child owner, then read `children` under it
|
|
61
|
+
// Stamp the value on a DEDICATED child owner, then read `children` under it: never
|
|
58
62
|
// on the current owner directly. Two sibling <Ctx.Provider> elements used inline (no
|
|
59
|
-
// wrapping component) run under the
|
|
60
|
-
// second clobber the first's entry
|
|
63
|
+
// wrapping component) run under the same ambient owner, so stamping there lets the
|
|
64
|
+
// second clobber the first's entry, every descendant then resolves the last value.
|
|
61
65
|
// A child scope isolates each Provider; because createMemo captures the owner at
|
|
62
66
|
// creation, the children memos built while reading `props.children` here capture this
|
|
63
67
|
// scope and resolve the right value even when rendered lazily (Portal/Show/For).
|
|
@@ -116,7 +120,7 @@ export function provide(context, value, fn) {
|
|
|
116
120
|
G.owner = prev;
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
|
-
// ── Suspend / error hooks
|
|
123
|
+
// ── Suspend / error hooks, injected by the boundary layers so this file stays clear of them ──
|
|
120
124
|
/** Register how a thrown promise (a suspended read) is reported to a boundary. */
|
|
121
125
|
export function setSuspendHandler(fn) {
|
|
122
126
|
G.onSuspend = fn;
|
|
@@ -155,9 +159,9 @@ export function runWithOwner(owner, fn) {
|
|
|
155
159
|
/** Register a teardown for the current owner scope. */
|
|
156
160
|
export function onCleanup(fn) {
|
|
157
161
|
if (!G.owner)
|
|
158
|
-
return; // no owner
|
|
159
|
-
// A disposed owner is dead
|
|
160
|
-
// array and never run
|
|
162
|
+
return; // no owner -> not retained, runs never (matches Solid)
|
|
163
|
+
// A disposed owner is dead, pushing here would recreate its (nulled) cleanups
|
|
164
|
+
// array and never run, a leak. Run the teardown immediately instead, so a
|
|
161
165
|
// teardown registered from deferred work (a queued onMount) still fires.
|
|
162
166
|
if (G.owner.disposed) {
|
|
163
167
|
fn();
|
|
@@ -165,10 +169,10 @@ export function onCleanup(fn) {
|
|
|
165
169
|
}
|
|
166
170
|
(G.owner.cleanups ??= []).push(fn);
|
|
167
171
|
}
|
|
168
|
-
// `final` distinguishes a permanent teardown (the scope is gone
|
|
172
|
+
// `final` distinguishes a permanent teardown (the scope is gone: mark it disposed so
|
|
169
173
|
// deferred work bails) from an effect's between-runs cleanup (the owner is REUSED for
|
|
170
|
-
// the next run, so it must
|
|
171
|
-
// permanently
|
|
174
|
+
// the next run, so it must not be marked disposed). Children are always torn down
|
|
175
|
+
// permanently, only the effect's own owner survives a re-run.
|
|
172
176
|
function disposeOwner(owner, final = true) {
|
|
173
177
|
if (owner.disposed)
|
|
174
178
|
return;
|
|
@@ -199,7 +203,7 @@ export function createRoot(fn) {
|
|
|
199
203
|
G.owner = prev;
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
|
-
// ── Scheduler (TC39 has none)
|
|
206
|
+
// ── Scheduler (TC39 has none): state on G (see ./state.ts) ───────────────────
|
|
203
207
|
export function setScheduler(fn) {
|
|
204
208
|
G.hostSchedule = fn ?? ((run) => void Promise.resolve().then(run));
|
|
205
209
|
}
|
|
@@ -241,8 +245,8 @@ export function flushSync() {
|
|
|
241
245
|
}
|
|
242
246
|
}
|
|
243
247
|
/**
|
|
244
|
-
* Run pending effects after a write completes
|
|
245
|
-
* inside a batch). A single write thus marks the
|
|
248
|
+
* Run pending effects after a write completes, but only at the top level (not
|
|
249
|
+
* inside a batch). A single write thus marks the whole graph dirty first, then
|
|
246
250
|
* flushes once (legacy-equivalent synchronous effects, glitch-free). The `G.flushing`
|
|
247
251
|
* guard makes a write inside an effect a no-op here (the running loop drains it).
|
|
248
252
|
*/
|
|
@@ -261,7 +265,7 @@ export function batch(fn) {
|
|
|
261
265
|
flushSync();
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
|
-
// ── effect()
|
|
268
|
+
// ── effect(): built on Computed + Watcher + Owner ───────────────────────────
|
|
265
269
|
class EffectNode {
|
|
266
270
|
computed;
|
|
267
271
|
watcher;
|
|
@@ -270,7 +274,7 @@ class EffectNode {
|
|
|
270
274
|
disposed = false;
|
|
271
275
|
constructor(fn, report = false) {
|
|
272
276
|
this.owner = makeOwner(G.owner);
|
|
273
|
-
// Tie disposal to the creating scope
|
|
277
|
+
// Tie disposal to the creating scope, when the parent owner tears down it must
|
|
274
278
|
// fully dispose this effect (unwatch + body cleanups), not just run the body
|
|
275
279
|
// cleanups. Register dispose() as a parent cleanup rather than as an owned tree.
|
|
276
280
|
if (G.owner)
|
|
@@ -278,7 +282,7 @@ class EffectNode {
|
|
|
278
282
|
// The Computed runs the user fn inside a tracking scope + this effect's owner,
|
|
279
283
|
// so reads subscribe and onCleanup/nested roots attach here.
|
|
280
284
|
this.computed = new Computed(() => {
|
|
281
|
-
// teardown previous run's cleanups before re-running
|
|
285
|
+
// teardown previous run's cleanups before re-running. not final: the owner is
|
|
282
286
|
// reused for this run, so it must stay alive (only its children are disposed).
|
|
283
287
|
disposeOwner(this.owner, false);
|
|
284
288
|
runWithOwner(this.owner, () => {
|
|
@@ -1 +1,478 @@
|
|
|
1
|
-
|
|
1
|
+
// src/version.generated.ts
|
|
2
|
+
var VERSION = "1.0.0-alpha.84";
|
|
3
|
+
|
|
4
|
+
// src/lib/signal/graph/state.ts
|
|
5
|
+
var MAJOR = VERSION.split(".")[0];
|
|
6
|
+
var KEY = `__fluixi_signal_state_v${MAJOR}__`;
|
|
7
|
+
var VERSIONS_KEY = `__fluixi_signal_builds_v${MAJOR}__`;
|
|
8
|
+
var defaultSchedule = (run) => {
|
|
9
|
+
if (typeof queueMicrotask === "function") queueMicrotask(run);
|
|
10
|
+
else void Promise.resolve().then(run);
|
|
11
|
+
};
|
|
12
|
+
function defaults() {
|
|
13
|
+
return {
|
|
14
|
+
consumer: null,
|
|
15
|
+
untracked: false,
|
|
16
|
+
owner: null,
|
|
17
|
+
queue: [],
|
|
18
|
+
scheduled: false,
|
|
19
|
+
flushing: false,
|
|
20
|
+
batchDepth: 0,
|
|
21
|
+
hostSchedule: defaultSchedule,
|
|
22
|
+
onSuspend: null,
|
|
23
|
+
onError: null,
|
|
24
|
+
observer: null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
var realm = globalThis;
|
|
28
|
+
function backfillState(existing) {
|
|
29
|
+
const fresh = defaults();
|
|
30
|
+
const target = existing;
|
|
31
|
+
for (const field of Object.keys(fresh)) {
|
|
32
|
+
if (!(field in existing)) target[field] = fresh[field];
|
|
33
|
+
}
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
function claim() {
|
|
37
|
+
const existing = realm[KEY];
|
|
38
|
+
return existing ? backfillState(existing) : realm[KEY] = defaults();
|
|
39
|
+
}
|
|
40
|
+
var G = claim();
|
|
41
|
+
(realm[VERSIONS_KEY] ??= []).push(VERSION);
|
|
42
|
+
|
|
43
|
+
// src/lib/signal/graph/observe.ts
|
|
44
|
+
function observeReactiveNodes(next) {
|
|
45
|
+
G.observer = next;
|
|
46
|
+
return () => {
|
|
47
|
+
if (G.observer === next) G.observer = null;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var running = [];
|
|
51
|
+
var origin;
|
|
52
|
+
function reportReactiveNode(report) {
|
|
53
|
+
const observer = G.observer;
|
|
54
|
+
if (observer) {
|
|
55
|
+
observer.created(
|
|
56
|
+
report.parents === void 0 ? { ...report, parents: running, origin: report.origin ?? origin } : report
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function enterReactiveRun(node) {
|
|
61
|
+
if (!G.observer) return false;
|
|
62
|
+
running.push(node);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function exitReactiveRun() {
|
|
66
|
+
running.pop();
|
|
67
|
+
}
|
|
68
|
+
function reportReactiveRun(node) {
|
|
69
|
+
G.observer?.ran?.(node);
|
|
70
|
+
}
|
|
71
|
+
function reportReactiveContextCreated(id) {
|
|
72
|
+
G.observer?.contextCreated?.(id);
|
|
73
|
+
}
|
|
74
|
+
function reportReactiveContext(id, kind, value) {
|
|
75
|
+
const observer = G.observer;
|
|
76
|
+
if (!observer?.context || !running.length) return;
|
|
77
|
+
observer.context(running[running.length - 1], id, kind, value);
|
|
78
|
+
}
|
|
79
|
+
function reportReactiveDispose(node) {
|
|
80
|
+
G.observer?.disposed?.(node);
|
|
81
|
+
}
|
|
82
|
+
var HOOK = "__FLUIXI_DEVTOOLS_HOOK__";
|
|
83
|
+
var hook = globalThis[HOOK];
|
|
84
|
+
if (typeof hook?.inject === "function") {
|
|
85
|
+
try {
|
|
86
|
+
hook.inject({ observeReactiveNodes, version: VERSION });
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/lib/signal/graph/core.ts
|
|
92
|
+
var CLEAN = 0;
|
|
93
|
+
var CHECK = 1;
|
|
94
|
+
var DIRTY = 2;
|
|
95
|
+
var Source = class {
|
|
96
|
+
constructor() {
|
|
97
|
+
/** Computeds, and watched leaves, that read this node. */
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
this.observers = null;
|
|
100
|
+
/** Watchers eagerly observing this node, which is how `runtime.ts` schedules effects. */
|
|
101
|
+
this.watchers = null;
|
|
102
|
+
/** Bumped whenever this node's value actually changes (equality-gated). */
|
|
103
|
+
this.version = 0;
|
|
104
|
+
}
|
|
105
|
+
/** Settle this node so a reader sees a current value/version. Overridden by Computed. */
|
|
106
|
+
updateIfNecessary() {
|
|
107
|
+
}
|
|
108
|
+
/** Link the current consumer (if any) as an observer of this node. */
|
|
109
|
+
track() {
|
|
110
|
+
if (G.untracked) return;
|
|
111
|
+
const consumer = G.consumer;
|
|
112
|
+
if (!consumer) return;
|
|
113
|
+
(this.observers ??= /* @__PURE__ */ new Set()).add(consumer);
|
|
114
|
+
(consumer.sources ??= /* @__PURE__ */ new Set()).add(this);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var Computed = class extends Source {
|
|
118
|
+
constructor(fn, options) {
|
|
119
|
+
super();
|
|
120
|
+
this._threw = false;
|
|
121
|
+
this._initialized = false;
|
|
122
|
+
this.state = DIRTY;
|
|
123
|
+
/** Nodes this computation read on its last run. Rebuilt on every run. */
|
|
124
|
+
this.sources = null;
|
|
125
|
+
this._fn = fn;
|
|
126
|
+
this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
|
|
127
|
+
}
|
|
128
|
+
get() {
|
|
129
|
+
if (G.consumer === this) {
|
|
130
|
+
throw new Error("Signal.Computed cannot read itself");
|
|
131
|
+
}
|
|
132
|
+
this.track();
|
|
133
|
+
this.updateIfNecessary();
|
|
134
|
+
if (this._threw) throw this._error;
|
|
135
|
+
return this._value;
|
|
136
|
+
}
|
|
137
|
+
/** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
|
|
138
|
+
updateIfNecessary() {
|
|
139
|
+
if (this.state === CHECK) {
|
|
140
|
+
if (this.sources) {
|
|
141
|
+
for (const s of this.sources) {
|
|
142
|
+
s.updateIfNecessary();
|
|
143
|
+
if (this.state === DIRTY) break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (this.state === DIRTY || !this._initialized) this.recompute();
|
|
148
|
+
this.state = CLEAN;
|
|
149
|
+
}
|
|
150
|
+
recompute() {
|
|
151
|
+
if (this.sources) {
|
|
152
|
+
for (const s of this.sources) s.observers?.delete(this);
|
|
153
|
+
this.sources.clear();
|
|
154
|
+
}
|
|
155
|
+
const prevConsumer = G.consumer;
|
|
156
|
+
const prevUntracked = G.untracked;
|
|
157
|
+
const watched = enterReactiveRun(this);
|
|
158
|
+
G.consumer = this;
|
|
159
|
+
G.untracked = false;
|
|
160
|
+
let next;
|
|
161
|
+
let threw = false;
|
|
162
|
+
let err;
|
|
163
|
+
try {
|
|
164
|
+
next = this._fn();
|
|
165
|
+
} catch (e) {
|
|
166
|
+
threw = true;
|
|
167
|
+
err = e;
|
|
168
|
+
next = this._value;
|
|
169
|
+
} finally {
|
|
170
|
+
G.consumer = prevConsumer;
|
|
171
|
+
G.untracked = prevUntracked;
|
|
172
|
+
if (watched) exitReactiveRun();
|
|
173
|
+
}
|
|
174
|
+
const changed = !this._initialized || this._threw !== threw || (threw ? err !== this._error : !this._equals(this._value, next));
|
|
175
|
+
this._initialized = true;
|
|
176
|
+
this._threw = threw;
|
|
177
|
+
this._error = err;
|
|
178
|
+
this._value = next;
|
|
179
|
+
reportReactiveRun(this);
|
|
180
|
+
if (changed) {
|
|
181
|
+
this.version++;
|
|
182
|
+
if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
|
|
183
|
+
notifyWatchers(this);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Push a maybe-dirty mark. A direct observer of a changed node gets DIRTY; its
|
|
188
|
+
* own observers get CHECK (recurse), stopping where already marked, so the
|
|
189
|
+
* push is O(newly-dirtied) and never recomputes.
|
|
190
|
+
*/
|
|
191
|
+
markDirty(level) {
|
|
192
|
+
if (this.state >= level) return;
|
|
193
|
+
const wasClean = this.state === CLEAN;
|
|
194
|
+
this.state = level;
|
|
195
|
+
if (wasClean) {
|
|
196
|
+
if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
|
|
197
|
+
notifyWatchers(this);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
peek() {
|
|
201
|
+
this.updateIfNecessary();
|
|
202
|
+
if (this._threw) throw this._error;
|
|
203
|
+
return this._value;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
function notifyWatchers(node) {
|
|
207
|
+
if (!node.watchers) return;
|
|
208
|
+
for (const w of [...node.watchers]) w._notify(node);
|
|
209
|
+
}
|
|
210
|
+
var Watcher = class {
|
|
211
|
+
constructor(notify) {
|
|
212
|
+
this._watched = /* @__PURE__ */ new Set();
|
|
213
|
+
this._pending = /* @__PURE__ */ new Set();
|
|
214
|
+
this._notifyFn = notify;
|
|
215
|
+
}
|
|
216
|
+
watch(...nodes) {
|
|
217
|
+
for (const n of nodes) {
|
|
218
|
+
this._watched.add(n);
|
|
219
|
+
(n.watchers ??= /* @__PURE__ */ new Set()).add(this);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
unwatch(...nodes) {
|
|
223
|
+
for (const n of nodes) {
|
|
224
|
+
this._watched.delete(n);
|
|
225
|
+
n.watchers?.delete(this);
|
|
226
|
+
this._pending.delete(n);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Watched nodes that may have changed since the last getPending(). */
|
|
230
|
+
getPending() {
|
|
231
|
+
const out = [...this._pending];
|
|
232
|
+
this._pending.clear();
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/** @internal, a watched node became non-clean. Edge-triggered: notify once. */
|
|
236
|
+
_notify(node) {
|
|
237
|
+
if (!this._watched.has(node) || this._pending.has(node)) return;
|
|
238
|
+
const wasEmpty = this._pending.size === 0;
|
|
239
|
+
this._pending.add(node);
|
|
240
|
+
if (wasEmpty) this._notifyFn();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/lib/signal/graph/runtime.ts
|
|
245
|
+
function createChildOwner(parent) {
|
|
246
|
+
const owner = makeOwner(parent);
|
|
247
|
+
if (parent) (parent.owned ??= []).push(owner);
|
|
248
|
+
return owner;
|
|
249
|
+
}
|
|
250
|
+
function disposeScope(owner, final = true) {
|
|
251
|
+
disposeOwner(owner, final);
|
|
252
|
+
}
|
|
253
|
+
function isOwnerDisposed(owner) {
|
|
254
|
+
return !!owner && owner.disposed === true;
|
|
255
|
+
}
|
|
256
|
+
function makeOwner(parent) {
|
|
257
|
+
return { cleanups: null, owned: null, parent, contexts: null };
|
|
258
|
+
}
|
|
259
|
+
function createContext(defaultValue, name) {
|
|
260
|
+
const id = Symbol(name ?? "context");
|
|
261
|
+
reportReactiveContextCreated(id);
|
|
262
|
+
const Provider = (props) => {
|
|
263
|
+
reportReactiveContext(id, "provide", props.value);
|
|
264
|
+
const parent = G.owner;
|
|
265
|
+
const owner = makeOwner(parent);
|
|
266
|
+
(owner.contexts = /* @__PURE__ */ new Map()).set(id, props.value);
|
|
267
|
+
if (parent) (parent.owned ??= []).push(owner);
|
|
268
|
+
G.owner = owner;
|
|
269
|
+
try {
|
|
270
|
+
return props.children;
|
|
271
|
+
} finally {
|
|
272
|
+
G.owner = parent;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
return { id, defaultValue, Provider };
|
|
276
|
+
}
|
|
277
|
+
function useContext(context) {
|
|
278
|
+
let o = G.owner;
|
|
279
|
+
while (o) {
|
|
280
|
+
if (o.contexts && o.contexts.has(context.id)) {
|
|
281
|
+
const found = o.contexts.get(context.id);
|
|
282
|
+
reportReactiveContext(context.id, "read", found);
|
|
283
|
+
return found;
|
|
284
|
+
}
|
|
285
|
+
o = o.parent;
|
|
286
|
+
}
|
|
287
|
+
reportReactiveContext(context.id, "read", context.defaultValue);
|
|
288
|
+
return context.defaultValue;
|
|
289
|
+
}
|
|
290
|
+
function readContextFrom(owner, context) {
|
|
291
|
+
let o = owner;
|
|
292
|
+
while (o) {
|
|
293
|
+
if (o.contexts && o.contexts.has(context.id)) return o.contexts.get(context.id);
|
|
294
|
+
o = o.parent;
|
|
295
|
+
}
|
|
296
|
+
return context.defaultValue;
|
|
297
|
+
}
|
|
298
|
+
function provide(context, value, fn) {
|
|
299
|
+
const owner = makeOwner(G.owner);
|
|
300
|
+
(owner.contexts = /* @__PURE__ */ new Map()).set(context.id, value);
|
|
301
|
+
if (G.owner) (G.owner.owned ??= []).push(owner);
|
|
302
|
+
const prev = G.owner;
|
|
303
|
+
G.owner = owner;
|
|
304
|
+
try {
|
|
305
|
+
return fn();
|
|
306
|
+
} finally {
|
|
307
|
+
G.owner = prev;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function setSuspendHandler(fn) {
|
|
311
|
+
G.onSuspend = fn;
|
|
312
|
+
}
|
|
313
|
+
function reportSuspend(promise) {
|
|
314
|
+
if (G.onSuspend) {
|
|
315
|
+
G.onSuspend(promise, G.owner);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
function setErrorHandler(fn) {
|
|
321
|
+
G.onError = fn;
|
|
322
|
+
}
|
|
323
|
+
function getOwner() {
|
|
324
|
+
return G.owner;
|
|
325
|
+
}
|
|
326
|
+
function setOwner(owner) {
|
|
327
|
+
const prev = G.owner;
|
|
328
|
+
G.owner = owner;
|
|
329
|
+
return prev;
|
|
330
|
+
}
|
|
331
|
+
function runWithOwner(owner, fn) {
|
|
332
|
+
const prev = G.owner;
|
|
333
|
+
G.owner = owner;
|
|
334
|
+
try {
|
|
335
|
+
return fn();
|
|
336
|
+
} finally {
|
|
337
|
+
G.owner = prev;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function onCleanup(fn) {
|
|
341
|
+
if (!G.owner) return;
|
|
342
|
+
if (G.owner.disposed) {
|
|
343
|
+
fn();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
(G.owner.cleanups ??= []).push(fn);
|
|
347
|
+
}
|
|
348
|
+
function disposeOwner(owner, final = true) {
|
|
349
|
+
if (owner.disposed) return;
|
|
350
|
+
if (owner.owned) {
|
|
351
|
+
for (let i = owner.owned.length - 1; i >= 0; i--) disposeOwner(owner.owned[i]);
|
|
352
|
+
owner.owned = null;
|
|
353
|
+
}
|
|
354
|
+
if (owner.cleanups) {
|
|
355
|
+
for (let i = owner.cleanups.length - 1; i >= 0; i--) owner.cleanups[i]();
|
|
356
|
+
owner.cleanups = null;
|
|
357
|
+
}
|
|
358
|
+
if (final) owner.disposed = true;
|
|
359
|
+
}
|
|
360
|
+
function createRoot(fn) {
|
|
361
|
+
const owner = makeOwner(G.owner);
|
|
362
|
+
if (G.owner) (G.owner.owned ??= []).push(owner);
|
|
363
|
+
const prev = G.owner;
|
|
364
|
+
G.owner = owner;
|
|
365
|
+
try {
|
|
366
|
+
return fn(() => disposeOwner(owner));
|
|
367
|
+
} finally {
|
|
368
|
+
G.owner = prev;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function setScheduler(fn) {
|
|
372
|
+
G.hostSchedule = fn ?? ((run) => void Promise.resolve().then(run));
|
|
373
|
+
}
|
|
374
|
+
function enqueue(e) {
|
|
375
|
+
if (e.queued) return;
|
|
376
|
+
e.queued = true;
|
|
377
|
+
G.queue.push(e);
|
|
378
|
+
if (!G.scheduled && G.batchDepth === 0) {
|
|
379
|
+
G.scheduled = true;
|
|
380
|
+
G.hostSchedule(flushSync);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function flushSync() {
|
|
384
|
+
G.scheduled = false;
|
|
385
|
+
if (G.flushing) return;
|
|
386
|
+
G.flushing = true;
|
|
387
|
+
let guard = 0;
|
|
388
|
+
try {
|
|
389
|
+
while (G.queue.length) {
|
|
390
|
+
if (++guard > 1e5) throw new Error("[fluixi] effect flush did not settle (cycle?)");
|
|
391
|
+
const batchOf = G.queue;
|
|
392
|
+
G.queue = [];
|
|
393
|
+
for (const e of batchOf) {
|
|
394
|
+
e.queued = false;
|
|
395
|
+
if (!e.disposed) e.run();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} finally {
|
|
399
|
+
G.flushing = false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function afterWrite() {
|
|
403
|
+
if (G.batchDepth === 0) flushSync();
|
|
404
|
+
}
|
|
405
|
+
function batch(fn) {
|
|
406
|
+
G.batchDepth++;
|
|
407
|
+
try {
|
|
408
|
+
return fn();
|
|
409
|
+
} finally {
|
|
410
|
+
if (--G.batchDepth === 0) flushSync();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
var EffectNode = class {
|
|
414
|
+
constructor(fn, report = false) {
|
|
415
|
+
this.queued = false;
|
|
416
|
+
this.disposed = false;
|
|
417
|
+
this.owner = makeOwner(G.owner);
|
|
418
|
+
if (G.owner) (G.owner.cleanups ??= []).push(() => this.dispose());
|
|
419
|
+
this.computed = new Computed(() => {
|
|
420
|
+
disposeOwner(this.owner, false);
|
|
421
|
+
runWithOwner(this.owner, () => {
|
|
422
|
+
const r = fn();
|
|
423
|
+
if (typeof r === "function") (this.owner.cleanups ??= []).push(r);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
reportReactiveNode({ kind: "effect", node: this.computed, handle: this, internal: !report });
|
|
427
|
+
this.watcher = new Watcher(() => enqueue(this));
|
|
428
|
+
this.run();
|
|
429
|
+
this.watcher.watch(this.computed);
|
|
430
|
+
}
|
|
431
|
+
run() {
|
|
432
|
+
if (this.disposed) return;
|
|
433
|
+
this.watcher.getPending();
|
|
434
|
+
try {
|
|
435
|
+
this.computed.get();
|
|
436
|
+
} catch (e) {
|
|
437
|
+
if (e && typeof e.then === "function" && G.onSuspend) {
|
|
438
|
+
G.onSuspend(e, this.owner);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (G.onError && G.onError(e, this.owner)) return;
|
|
442
|
+
throw e;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
dispose() {
|
|
446
|
+
if (this.disposed) return;
|
|
447
|
+
this.disposed = true;
|
|
448
|
+
reportReactiveDispose(this.computed);
|
|
449
|
+
this.watcher.unwatch(this.computed);
|
|
450
|
+
disposeOwner(this.owner);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
function effect(fn, report = false) {
|
|
454
|
+
const node = new EffectNode(fn, report);
|
|
455
|
+
return () => node.dispose();
|
|
456
|
+
}
|
|
457
|
+
export {
|
|
458
|
+
afterWrite,
|
|
459
|
+
batch,
|
|
460
|
+
createChildOwner,
|
|
461
|
+
createContext,
|
|
462
|
+
createRoot,
|
|
463
|
+
disposeScope,
|
|
464
|
+
effect,
|
|
465
|
+
flushSync,
|
|
466
|
+
getOwner,
|
|
467
|
+
isOwnerDisposed,
|
|
468
|
+
onCleanup,
|
|
469
|
+
provide,
|
|
470
|
+
readContextFrom,
|
|
471
|
+
reportSuspend,
|
|
472
|
+
runWithOwner,
|
|
473
|
+
setErrorHandler,
|
|
474
|
+
setOwner,
|
|
475
|
+
setScheduler,
|
|
476
|
+
setSuspendHandler,
|
|
477
|
+
useContext
|
|
478
|
+
};
|