@fluixi/reactive 1.0.0-alpha.82 → 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/README.md +4 -0
- 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 +51 -32
- 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 +45 -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 -0
- package/dist/lib/signal/graph/observe.d.ts +141 -0
- package/dist/lib/signal/graph/observe.d.ts.map +1 -0
- package/dist/lib/signal/graph/observe.js +149 -0
- package/dist/lib/signal/graph/observe.mjs +140 -0
- 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 +9 -9
- package/dist/lib/signal/graph/runtime.d.ts.map +1 -1
- package/dist/lib/signal/graph/runtime.js +56 -37
- 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 +43 -10
- package/dist/lib/signal/graph/state.d.ts.map +1 -1
- package/dist/lib/signal/graph/state.js +83 -21
- 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 +5 -2
- package/dist/lib/signal/index.d.ts.map +1 -1
- package/dist/lib/signal/index.js +8 -3
- 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 +10 -10
- 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 +294 -11
- 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,27 +1,32 @@
|
|
|
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';
|
|
23
|
+
import { reportReactiveContext, reportReactiveContextCreated, reportReactiveDispose, reportReactiveNode, } from './observe.js';
|
|
19
24
|
/**
|
|
20
25
|
* A child scope of `parent`, attached so the parent's teardown disposes it too.
|
|
21
26
|
*
|
|
22
27
|
* For a derivation that re-runs: context still resolves up the chain from the
|
|
23
28
|
* creation-time owner, while anything registered during a run belongs to this
|
|
24
|
-
* 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
|
|
25
30
|
* the enclosing scope and only running when that finally dies.
|
|
26
31
|
*/
|
|
27
32
|
export function createChildOwner(parent) {
|
|
@@ -33,7 +38,7 @@ export function createChildOwner(parent) {
|
|
|
33
38
|
/**
|
|
34
39
|
* Tear down a scope's children and cleanups.
|
|
35
40
|
*
|
|
36
|
-
* `final: false` keeps the scope itself alive and reusable
|
|
41
|
+
* `final: false` keeps the scope itself alive and reusable, what a re-running
|
|
37
42
|
* derivation needs between runs.
|
|
38
43
|
*/
|
|
39
44
|
export function disposeScope(owner, final = true) {
|
|
@@ -48,16 +53,19 @@ export function isOwnerDisposed(owner) {
|
|
|
48
53
|
function makeOwner(parent) {
|
|
49
54
|
return { cleanups: null, owned: null, parent, contexts: null };
|
|
50
55
|
}
|
|
51
|
-
export function createContext(defaultValue) {
|
|
52
|
-
|
|
56
|
+
export function createContext(defaultValue, name) {
|
|
57
|
+
// Named so a watcher can tell two of them apart. Nothing else reads the description.
|
|
58
|
+
const id = Symbol(name ?? 'context');
|
|
59
|
+
reportReactiveContextCreated(id);
|
|
53
60
|
const Provider = (props) => {
|
|
54
|
-
// 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
|
|
55
62
|
// on the current owner directly. Two sibling <Ctx.Provider> elements used inline (no
|
|
56
|
-
// wrapping component) run under the
|
|
57
|
-
// 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.
|
|
58
65
|
// A child scope isolates each Provider; because createMemo captures the owner at
|
|
59
66
|
// creation, the children memos built while reading `props.children` here capture this
|
|
60
67
|
// scope and resolve the right value even when rendered lazily (Portal/Show/For).
|
|
68
|
+
reportReactiveContext(id, 'provide', props.value);
|
|
61
69
|
const parent = G.owner;
|
|
62
70
|
const owner = makeOwner(parent);
|
|
63
71
|
(owner.contexts = new Map()).set(id, props.value);
|
|
@@ -77,10 +85,14 @@ export function createContext(defaultValue) {
|
|
|
77
85
|
export function useContext(context) {
|
|
78
86
|
let o = G.owner;
|
|
79
87
|
while (o) {
|
|
80
|
-
if (o.contexts && o.contexts.has(context.id))
|
|
81
|
-
|
|
88
|
+
if (o.contexts && o.contexts.has(context.id)) {
|
|
89
|
+
const found = o.contexts.get(context.id);
|
|
90
|
+
reportReactiveContext(context.id, 'read', found);
|
|
91
|
+
return found;
|
|
92
|
+
}
|
|
82
93
|
o = o.parent;
|
|
83
94
|
}
|
|
95
|
+
reportReactiveContext(context.id, 'read', context.defaultValue);
|
|
84
96
|
return context.defaultValue;
|
|
85
97
|
}
|
|
86
98
|
/** Read `context` starting from a specific owner chain (not the current scope). */
|
|
@@ -108,7 +120,7 @@ export function provide(context, value, fn) {
|
|
|
108
120
|
G.owner = prev;
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
|
-
// ── Suspend / error hooks
|
|
123
|
+
// ── Suspend / error hooks, injected by the boundary layers so this file stays clear of them ──
|
|
112
124
|
/** Register how a thrown promise (a suspended read) is reported to a boundary. */
|
|
113
125
|
export function setSuspendHandler(fn) {
|
|
114
126
|
G.onSuspend = fn;
|
|
@@ -147,9 +159,9 @@ export function runWithOwner(owner, fn) {
|
|
|
147
159
|
/** Register a teardown for the current owner scope. */
|
|
148
160
|
export function onCleanup(fn) {
|
|
149
161
|
if (!G.owner)
|
|
150
|
-
return; // no owner
|
|
151
|
-
// A disposed owner is dead
|
|
152
|
-
// 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
|
|
153
165
|
// teardown registered from deferred work (a queued onMount) still fires.
|
|
154
166
|
if (G.owner.disposed) {
|
|
155
167
|
fn();
|
|
@@ -157,10 +169,10 @@ export function onCleanup(fn) {
|
|
|
157
169
|
}
|
|
158
170
|
(G.owner.cleanups ??= []).push(fn);
|
|
159
171
|
}
|
|
160
|
-
// `final` distinguishes a permanent teardown (the scope is gone
|
|
172
|
+
// `final` distinguishes a permanent teardown (the scope is gone: mark it disposed so
|
|
161
173
|
// deferred work bails) from an effect's between-runs cleanup (the owner is REUSED for
|
|
162
|
-
// the next run, so it must
|
|
163
|
-
// 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.
|
|
164
176
|
function disposeOwner(owner, final = true) {
|
|
165
177
|
if (owner.disposed)
|
|
166
178
|
return;
|
|
@@ -191,7 +203,7 @@ export function createRoot(fn) {
|
|
|
191
203
|
G.owner = prev;
|
|
192
204
|
}
|
|
193
205
|
}
|
|
194
|
-
// ── Scheduler (TC39 has none)
|
|
206
|
+
// ── Scheduler (TC39 has none): state on G (see ./state.ts) ───────────────────
|
|
195
207
|
export function setScheduler(fn) {
|
|
196
208
|
G.hostSchedule = fn ?? ((run) => void Promise.resolve().then(run));
|
|
197
209
|
}
|
|
@@ -233,8 +245,8 @@ export function flushSync() {
|
|
|
233
245
|
}
|
|
234
246
|
}
|
|
235
247
|
/**
|
|
236
|
-
* Run pending effects after a write completes
|
|
237
|
-
* 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
|
|
238
250
|
* flushes once (legacy-equivalent synchronous effects, glitch-free). The `G.flushing`
|
|
239
251
|
* guard makes a write inside an effect a no-op here (the running loop drains it).
|
|
240
252
|
*/
|
|
@@ -253,16 +265,16 @@ export function batch(fn) {
|
|
|
253
265
|
flushSync();
|
|
254
266
|
}
|
|
255
267
|
}
|
|
256
|
-
// ── effect()
|
|
268
|
+
// ── effect(): built on Computed + Watcher + Owner ───────────────────────────
|
|
257
269
|
class EffectNode {
|
|
258
270
|
computed;
|
|
259
271
|
watcher;
|
|
260
272
|
owner;
|
|
261
273
|
queued = false;
|
|
262
274
|
disposed = false;
|
|
263
|
-
constructor(fn) {
|
|
275
|
+
constructor(fn, report = false) {
|
|
264
276
|
this.owner = makeOwner(G.owner);
|
|
265
|
-
// Tie disposal to the creating scope
|
|
277
|
+
// Tie disposal to the creating scope, when the parent owner tears down it must
|
|
266
278
|
// fully dispose this effect (unwatch + body cleanups), not just run the body
|
|
267
279
|
// cleanups. Register dispose() as a parent cleanup rather than as an owned tree.
|
|
268
280
|
if (G.owner)
|
|
@@ -270,7 +282,7 @@ class EffectNode {
|
|
|
270
282
|
// The Computed runs the user fn inside a tracking scope + this effect's owner,
|
|
271
283
|
// so reads subscribe and onCleanup/nested roots attach here.
|
|
272
284
|
this.computed = new Computed(() => {
|
|
273
|
-
// teardown previous run's cleanups before re-running
|
|
285
|
+
// teardown previous run's cleanups before re-running. not final: the owner is
|
|
274
286
|
// reused for this run, so it must stay alive (only its children are disposed).
|
|
275
287
|
disposeOwner(this.owner, false);
|
|
276
288
|
runWithOwner(this.owner, () => {
|
|
@@ -279,6 +291,10 @@ class EffectNode {
|
|
|
279
291
|
(this.owner.cleanups ??= []).push(r);
|
|
280
292
|
});
|
|
281
293
|
});
|
|
294
|
+
// Before the first run, so a watcher counts that run like every later one. A render
|
|
295
|
+
// effect is reported too, marked as plumbing: it is not worth drawing, but a node made
|
|
296
|
+
// inside one has to be able to find its way back to the component that caused it.
|
|
297
|
+
reportReactiveNode({ kind: 'effect', node: this.computed, handle: this, internal: !report });
|
|
282
298
|
// The watcher only schedules; the recompute happens on flush.
|
|
283
299
|
this.watcher = new Watcher(() => enqueue(this));
|
|
284
300
|
this.run(); // initial synchronous run (Solid semantics)
|
|
@@ -309,12 +325,15 @@ class EffectNode {
|
|
|
309
325
|
if (this.disposed)
|
|
310
326
|
return;
|
|
311
327
|
this.disposed = true;
|
|
328
|
+
reportReactiveDispose(this.computed);
|
|
312
329
|
this.watcher.unwatch(this.computed);
|
|
313
330
|
disposeOwner(this.owner);
|
|
314
331
|
}
|
|
315
332
|
}
|
|
316
333
|
/** A re-running side effect. Runs once now, then again (G.scheduled) when deps change. */
|
|
317
|
-
export function effect(fn) {
|
|
318
|
-
|
|
334
|
+
export function effect(fn, report = false) {
|
|
335
|
+
// Reported only when the caller says so. Every DOM binding is a render effect through this
|
|
336
|
+
// same factory, and a graph holding one node per text node is not a graph anyone reads.
|
|
337
|
+
const node = new EffectNode(fn, report);
|
|
319
338
|
return () => node.dispose();
|
|
320
339
|
}
|
|
@@ -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
|
+
};
|