@fluixi/reactive 1.0.0-alpha.100
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/LICENSE +21 -0
- package/README.md +280 -0
- package/dist/cdn/reactive.cjs +2803 -0
- package/dist/cdn/reactive.d.ts +11 -0
- package/dist/cdn/reactive.d.ts.map +1 -0
- package/dist/cdn/reactive.global.js +2 -0
- package/dist/cdn/reactive.js +10 -0
- package/dist/cdn/reactive.mjs +2787 -0
- package/dist/cdn/signal.cjs +299 -0
- package/dist/cdn/signal.d.ts +19 -0
- package/dist/cdn/signal.d.ts.map +1 -0
- package/dist/cdn/signal.global.js +2 -0
- package/dist/cdn/signal.js +18 -0
- package/dist/cdn/signal.mjs +276 -0
- package/dist/index.cjs +2803 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +2787 -0
- package/dist/lib/index.cjs +2804 -0
- package/dist/lib/index.d.ts +8 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.mjs +2789 -0
- package/dist/lib/observable/index.cjs +386 -0
- package/dist/lib/observable/index.d.ts +4 -0
- package/dist/lib/observable/index.d.ts.map +1 -0
- package/dist/lib/observable/index.js +3 -0
- package/dist/lib/observable/index.mjs +364 -0
- package/dist/lib/observable/observable.cjs +384 -0
- package/dist/lib/observable/observable.d.ts +246 -0
- package/dist/lib/observable/observable.d.ts.map +1 -0
- package/dist/lib/observable/observable.js +268 -0
- package/dist/lib/observable/observable.mjs +361 -0
- package/dist/lib/observable/subscription.cjs +180 -0
- package/dist/lib/observable/subscription.d.ts +58 -0
- package/dist/lib/observable/subscription.d.ts.map +1 -0
- package/dist/lib/observable/subscription.js +177 -0
- package/dist/lib/observable/subscription.mjs +157 -0
- package/dist/lib/observable/types.cjs +18 -0
- package/dist/lib/observable/types.d.ts +44 -0
- package/dist/lib/observable/types.d.ts.map +1 -0
- package/dist/lib/observable/types.js +1 -0
- package/dist/lib/observable/types.mjs +0 -0
- package/dist/lib/predicates.cjs +44 -0
- package/dist/lib/predicates.d.ts +37 -0
- package/dist/lib/predicates.d.ts.map +1 -0
- package/dist/lib/predicates.js +62 -0
- package/dist/lib/predicates.mjs +23 -0
- package/dist/lib/signal/api.cjs +592 -0
- package/dist/lib/signal/api.d.ts +227 -0
- package/dist/lib/signal/api.d.ts.map +1 -0
- package/dist/lib/signal/api.js +228 -0
- package/dist/lib/signal/api.mjs +569 -0
- package/dist/lib/signal/cached-resource.cjs +726 -0
- package/dist/lib/signal/cached-resource.d.ts +86 -0
- package/dist/lib/signal/cached-resource.d.ts.map +1 -0
- package/dist/lib/signal/cached-resource.js +250 -0
- package/dist/lib/signal/cached-resource.mjs +704 -0
- package/dist/lib/signal/graph/children.cjs +326 -0
- package/dist/lib/signal/graph/children.d.ts +2 -0
- package/dist/lib/signal/graph/children.d.ts.map +1 -0
- package/dist/lib/signal/graph/children.js +30 -0
- package/dist/lib/signal/graph/children.mjs +303 -0
- package/dist/lib/signal/graph/compat.cjs +590 -0
- package/dist/lib/signal/graph/compat.d.ts +66 -0
- package/dist/lib/signal/graph/compat.d.ts.map +1 -0
- package/dist/lib/signal/graph/compat.js +197 -0
- package/dist/lib/signal/graph/compat.mjs +568 -0
- package/dist/lib/signal/graph/core.cjs +295 -0
- package/dist/lib/signal/graph/core.d.ts +99 -0
- package/dist/lib/signal/graph/core.d.ts.map +1 -0
- package/dist/lib/signal/graph/core.js +268 -0
- package/dist/lib/signal/graph/core.mjs +273 -0
- package/dist/lib/signal/graph/debounced.cjs +680 -0
- package/dist/lib/signal/graph/debounced.d.ts +67 -0
- package/dist/lib/signal/graph/debounced.d.ts.map +1 -0
- package/dist/lib/signal/graph/debounced.js +92 -0
- package/dist/lib/signal/graph/debounced.mjs +657 -0
- package/dist/lib/signal/graph/derived.cjs +508 -0
- package/dist/lib/signal/graph/derived.d.ts +14 -0
- package/dist/lib/signal/graph/derived.d.ts.map +1 -0
- package/dist/lib/signal/graph/derived.js +65 -0
- package/dist/lib/signal/graph/derived.mjs +485 -0
- package/dist/lib/signal/graph/detect.cjs +66 -0
- package/dist/lib/signal/graph/detect.d.ts +17 -0
- package/dist/lib/signal/graph/detect.d.ts.map +1 -0
- package/dist/lib/signal/graph/detect.js +39 -0
- package/dist/lib/signal/graph/detect.mjs +45 -0
- package/dist/lib/signal/graph/error-boundary.cjs +313 -0
- package/dist/lib/signal/graph/error-boundary.d.ts +16 -0
- package/dist/lib/signal/graph/error-boundary.d.ts.map +1 -0
- package/dist/lib/signal/graph/error-boundary.js +34 -0
- package/dist/lib/signal/graph/error-boundary.mjs +290 -0
- package/dist/lib/signal/graph/index.cjs +305 -0
- package/dist/lib/signal/graph/index.d.ts +22 -0
- package/dist/lib/signal/graph/index.d.ts.map +1 -0
- package/dist/lib/signal/graph/index.js +21 -0
- package/dist/lib/signal/graph/index.mjs +283 -0
- package/dist/lib/signal/graph/observe.cjs +198 -0
- package/dist/lib/signal/graph/observe.d.ts +185 -0
- package/dist/lib/signal/graph/observe.d.ts.map +1 -0
- package/dist/lib/signal/graph/observe.js +198 -0
- package/dist/lib/signal/graph/observe.mjs +175 -0
- package/dist/lib/signal/graph/resource.cjs +632 -0
- package/dist/lib/signal/graph/resource.d.ts +39 -0
- package/dist/lib/signal/graph/resource.d.ts.map +1 -0
- package/dist/lib/signal/graph/resource.js +165 -0
- package/dist/lib/signal/graph/resource.mjs +609 -0
- package/dist/lib/signal/graph/runtime.cjs +522 -0
- package/dist/lib/signal/graph/runtime.d.ts +80 -0
- package/dist/lib/signal/graph/runtime.d.ts.map +1 -0
- package/dist/lib/signal/graph/runtime.js +358 -0
- package/dist/lib/signal/graph/runtime.mjs +499 -0
- package/dist/lib/signal/graph/signal-next.cjs +1249 -0
- package/dist/lib/signal/graph/signal-next.d.ts +38 -0
- package/dist/lib/signal/graph/signal-next.d.ts.map +1 -0
- package/dist/lib/signal/graph/signal-next.js +44 -0
- package/dist/lib/signal/graph/signal-next.mjs +1227 -0
- package/dist/lib/signal/graph/ssr-hooks.cjs +89 -0
- package/dist/lib/signal/graph/ssr-hooks.d.ts +51 -0
- package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -0
- package/dist/lib/signal/graph/ssr-hooks.js +80 -0
- package/dist/lib/signal/graph/ssr-hooks.mjs +68 -0
- package/dist/lib/signal/graph/state.cjs +72 -0
- package/dist/lib/signal/graph/state.d.ts +76 -0
- package/dist/lib/signal/graph/state.d.ts.map +1 -0
- package/dist/lib/signal/graph/state.js +99 -0
- package/dist/lib/signal/graph/state.mjs +49 -0
- package/dist/lib/signal/graph/suspense.cjs +313 -0
- package/dist/lib/signal/graph/suspense.d.ts +13 -0
- package/dist/lib/signal/graph/suspense.d.ts.map +1 -0
- package/dist/lib/signal/graph/suspense.js +36 -0
- package/dist/lib/signal/graph/suspense.mjs +290 -0
- package/dist/lib/signal/index-legacy.cjs +2582 -0
- package/dist/lib/signal/index-legacy.d.ts +12 -0
- package/dist/lib/signal/index-legacy.d.ts.map +1 -0
- package/dist/lib/signal/index-legacy.js +11 -0
- package/dist/lib/signal/index-legacy.mjs +2560 -0
- package/dist/lib/signal/index.cjs +1350 -0
- package/dist/lib/signal/index.d.ts +19 -0
- package/dist/lib/signal/index.d.ts.map +1 -0
- package/dist/lib/signal/index.js +19 -0
- package/dist/lib/signal/index.mjs +1328 -0
- package/dist/lib/signal/list.cjs +561 -0
- package/dist/lib/signal/list.d.ts +39 -0
- package/dist/lib/signal/list.d.ts.map +1 -0
- package/dist/lib/signal/list.js +172 -0
- package/dist/lib/signal/list.mjs +538 -0
- package/dist/lib/signal/resource-api.cjs +638 -0
- package/dist/lib/signal/resource-api.d.ts +19 -0
- package/dist/lib/signal/resource-api.d.ts.map +1 -0
- package/dist/lib/signal/resource-api.js +27 -0
- package/dist/lib/signal/resource-api.mjs +615 -0
- package/dist/lib/signal/signal.cjs +2035 -0
- package/dist/lib/signal/signal.d.ts +303 -0
- package/dist/lib/signal/signal.d.ts.map +1 -0
- package/dist/lib/signal/signal.js +1233 -0
- package/dist/lib/signal/signal.mjs +2012 -0
- package/dist/lib/signal/types.cjs +38 -0
- package/dist/lib/signal/types.d.ts +133 -0
- package/dist/lib/signal/types.d.ts.map +1 -0
- package/dist/lib/signal/types.js +38 -0
- package/dist/lib/signal/types.mjs +17 -0
- package/dist/lib/signal/utilities.cjs +1024 -0
- package/dist/lib/signal/utilities.d.ts +247 -0
- package/dist/lib/signal/utilities.d.ts.map +1 -0
- package/dist/lib/signal/utilities.js +346 -0
- package/dist/lib/signal/utilities.mjs +1002 -0
- package/dist/lib/store/api.cjs +662 -0
- package/dist/lib/store/api.d.ts +40 -0
- package/dist/lib/store/api.d.ts.map +1 -0
- package/dist/lib/store/api.js +63 -0
- package/dist/lib/store/api.mjs +646 -0
- package/dist/lib/store/index.cjs +741 -0
- package/dist/lib/store/index.d.ts +16 -0
- package/dist/lib/store/index.d.ts.map +1 -0
- package/dist/lib/store/index.js +13 -0
- package/dist/lib/store/index.mjs +726 -0
- package/dist/lib/store/setter.types.cjs +18 -0
- package/dist/lib/store/setter.types.d.ts +131 -0
- package/dist/lib/store/setter.types.d.ts.map +1 -0
- package/dist/lib/store/setter.types.js +65 -0
- package/dist/lib/store/setter.types.mjs +0 -0
- package/dist/lib/store/store.cjs +708 -0
- package/dist/lib/store/store.d.ts +142 -0
- package/dist/lib/store/store.d.ts.map +1 -0
- package/dist/lib/store/store.js +812 -0
- package/dist/lib/store/store.mjs +694 -0
- package/dist/lib/store/types.cjs +18 -0
- package/dist/lib/store/types.d.ts +57 -0
- package/dist/lib/store/types.d.ts.map +1 -0
- package/dist/lib/store/types.js +1 -0
- package/dist/lib/store/types.mjs +0 -0
- package/dist/lib/store/utils.cjs +43 -0
- package/dist/lib/store/utils.d.ts +13 -0
- package/dist/lib/store/utils.d.ts.map +1 -0
- package/dist/lib/store/utils.js +29 -0
- package/dist/lib/store/utils.mjs +22 -0
- package/dist/lib/subject.cjs +450 -0
- package/dist/lib/subject.d.ts +14 -0
- package/dist/lib/subject.d.ts.map +1 -0
- package/dist/lib/subject.js +81 -0
- package/dist/lib/subject.mjs +427 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/version.generated.cjs +27 -0
- package/dist/version.generated.d.ts +3 -0
- package/dist/version.generated.d.ts.map +1 -0
- package/dist/version.generated.js +3 -0
- package/dist/version.generated.mjs +7 -0
- package/package.json +115 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime layer, on top of the graph core in `core.ts`.
|
|
3
|
+
*
|
|
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:
|
|
6
|
+
*
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
import { Computed, Watcher, untrack } from './core.js';
|
|
22
|
+
import { G } from './state.js';
|
|
23
|
+
import { reportReactiveContext, reportReactiveContextCreated, reportReactiveDispose, reportReactiveNode, } from './observe.js';
|
|
24
|
+
/**
|
|
25
|
+
* A child scope of `parent`, attached so the parent's teardown disposes it too.
|
|
26
|
+
*
|
|
27
|
+
* For a derivation that re-runs: context still resolves up the chain from the
|
|
28
|
+
* creation-time owner, while anything registered during a run belongs to this
|
|
29
|
+
* scope and can be torn down before the next one: instead of accumulating on
|
|
30
|
+
* the enclosing scope and only running when that finally dies.
|
|
31
|
+
*/
|
|
32
|
+
export function createChildOwner(parent) {
|
|
33
|
+
const owner = makeOwner(parent);
|
|
34
|
+
if (parent)
|
|
35
|
+
(parent.owned ??= []).push(owner);
|
|
36
|
+
return owner;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Tear down a scope's children and cleanups.
|
|
40
|
+
*
|
|
41
|
+
* `final: false` keeps the scope itself alive and reusable, what a re-running
|
|
42
|
+
* derivation needs between runs.
|
|
43
|
+
*/
|
|
44
|
+
export function disposeScope(owner, final = true) {
|
|
45
|
+
disposeOwner(owner, final);
|
|
46
|
+
}
|
|
47
|
+
/** True once `owner` has been disposed. Deferred work should bail rather than run. */
|
|
48
|
+
export function isOwnerDisposed(owner) {
|
|
49
|
+
return !!owner && owner.disposed === true;
|
|
50
|
+
}
|
|
51
|
+
// Owner / scheduler / boundary-hook state all live on the shared globalThis-backed G
|
|
52
|
+
// (see ./state.ts) so duplicate module copies share one graph.
|
|
53
|
+
function makeOwner(parent) {
|
|
54
|
+
return { cleanups: null, owned: null, parent, contexts: null };
|
|
55
|
+
}
|
|
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);
|
|
60
|
+
const Provider = (props) => {
|
|
61
|
+
// Stamp the value on a DEDICATED child owner, then read `children` under it: never
|
|
62
|
+
// on the current owner directly. Two sibling <Ctx.Provider> elements used inline (no
|
|
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.
|
|
65
|
+
// A child scope isolates each Provider; because createMemo captures the owner at
|
|
66
|
+
// creation, the children memos built while reading `props.children` here capture this
|
|
67
|
+
// scope and resolve the right value even when rendered lazily (Portal/Show/For).
|
|
68
|
+
reportReactiveContext(id, 'provide', props.value);
|
|
69
|
+
const parent = G.owner;
|
|
70
|
+
const owner = makeOwner(parent);
|
|
71
|
+
(owner.contexts = new Map()).set(id, props.value);
|
|
72
|
+
if (parent)
|
|
73
|
+
(parent.owned ??= []).push(owner);
|
|
74
|
+
G.owner = owner;
|
|
75
|
+
try {
|
|
76
|
+
return props.children;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
G.owner = parent;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return { id, defaultValue, Provider };
|
|
83
|
+
}
|
|
84
|
+
/** Read the nearest provided value for `context`, or its default. */
|
|
85
|
+
export function useContext(context) {
|
|
86
|
+
let o = G.owner;
|
|
87
|
+
while (o) {
|
|
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
|
+
}
|
|
93
|
+
o = o.parent;
|
|
94
|
+
}
|
|
95
|
+
reportReactiveContext(context.id, 'read', context.defaultValue);
|
|
96
|
+
return context.defaultValue;
|
|
97
|
+
}
|
|
98
|
+
/** Read `context` starting from a specific owner chain (not the current scope). */
|
|
99
|
+
export function readContextFrom(owner, context) {
|
|
100
|
+
let o = owner;
|
|
101
|
+
while (o) {
|
|
102
|
+
if (o.contexts && o.contexts.has(context.id))
|
|
103
|
+
return o.contexts.get(context.id);
|
|
104
|
+
o = o.parent;
|
|
105
|
+
}
|
|
106
|
+
return context.defaultValue;
|
|
107
|
+
}
|
|
108
|
+
/** Run `fn` in a child scope that provides `value` for `context`. */
|
|
109
|
+
export function provide(context, value, fn) {
|
|
110
|
+
const owner = makeOwner(G.owner);
|
|
111
|
+
(owner.contexts = new Map()).set(context.id, value);
|
|
112
|
+
if (G.owner)
|
|
113
|
+
(G.owner.owned ??= []).push(owner);
|
|
114
|
+
const prev = G.owner;
|
|
115
|
+
G.owner = owner;
|
|
116
|
+
try {
|
|
117
|
+
return fn();
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
G.owner = prev;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ── Suspend / error hooks, injected by the boundary layers so this file stays clear of them ──
|
|
124
|
+
/** Register how a thrown promise (a suspended read) is reported to a boundary. */
|
|
125
|
+
export function setSuspendHandler(fn) {
|
|
126
|
+
G.onSuspend = fn;
|
|
127
|
+
}
|
|
128
|
+
/** Report a suspended read (thrown promise) to the nearest boundary from the current owner. */
|
|
129
|
+
export function reportSuspend(promise) {
|
|
130
|
+
if (G.onSuspend) {
|
|
131
|
+
G.onSuspend(promise, G.owner);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
/** Register how a thrown error is routed to an error boundary. Return true if handled. */
|
|
137
|
+
export function setErrorHandler(fn) {
|
|
138
|
+
G.onError = fn;
|
|
139
|
+
}
|
|
140
|
+
export function getOwner() {
|
|
141
|
+
return G.owner;
|
|
142
|
+
}
|
|
143
|
+
/** Set the current owner directly, returning the previous one. (Framework render glue.) */
|
|
144
|
+
export function setOwner(owner) {
|
|
145
|
+
const prev = G.owner;
|
|
146
|
+
G.owner = owner;
|
|
147
|
+
return prev;
|
|
148
|
+
}
|
|
149
|
+
export function runWithOwner(owner, fn) {
|
|
150
|
+
const prev = G.owner;
|
|
151
|
+
G.owner = owner;
|
|
152
|
+
try {
|
|
153
|
+
return fn();
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
G.owner = prev;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Register a teardown for the current owner scope. */
|
|
160
|
+
export function onCleanup(fn) {
|
|
161
|
+
if (!G.owner)
|
|
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
|
|
165
|
+
// teardown registered from deferred work (a queued onMount) still fires.
|
|
166
|
+
if (G.owner.disposed) {
|
|
167
|
+
fn();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
(G.owner.cleanups ??= []).push(fn);
|
|
171
|
+
}
|
|
172
|
+
// `final` distinguishes a permanent teardown (the scope is gone: mark it disposed so
|
|
173
|
+
// deferred work bails) from an effect's between-runs cleanup (the owner is REUSED for
|
|
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.
|
|
176
|
+
function disposeOwner(owner, final = true) {
|
|
177
|
+
if (owner.disposed)
|
|
178
|
+
return;
|
|
179
|
+
if (owner.owned) {
|
|
180
|
+
for (let i = owner.owned.length - 1; i >= 0; i--)
|
|
181
|
+
disposeOwner(owner.owned[i]);
|
|
182
|
+
owner.owned = null;
|
|
183
|
+
}
|
|
184
|
+
if (owner.cleanups) {
|
|
185
|
+
for (let i = owner.cleanups.length - 1; i >= 0; i--)
|
|
186
|
+
owner.cleanups[i]();
|
|
187
|
+
owner.cleanups = null;
|
|
188
|
+
}
|
|
189
|
+
if (final)
|
|
190
|
+
owner.disposed = true;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Create a root reactive scope with explicit disposal (TC39 has no ownership). Untracked: what
|
|
194
|
+
* runs in it belongs to it, so the computation that created it does not depend on its reads.
|
|
195
|
+
*/
|
|
196
|
+
export function createRoot(fn) {
|
|
197
|
+
const owner = makeOwner(G.owner);
|
|
198
|
+
if (G.owner)
|
|
199
|
+
(G.owner.owned ??= []).push(owner);
|
|
200
|
+
const prev = G.owner;
|
|
201
|
+
G.owner = owner;
|
|
202
|
+
try {
|
|
203
|
+
return untrack(() => fn(() => disposeOwner(owner)));
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
G.owner = prev;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// ── Scheduler (TC39 has none): state on G (see ./state.ts) ───────────────────
|
|
210
|
+
export function setScheduler(fn) {
|
|
211
|
+
G.hostSchedule = fn ?? ((run) => void Promise.resolve().then(run));
|
|
212
|
+
}
|
|
213
|
+
function enqueue(e) {
|
|
214
|
+
if (e.queued)
|
|
215
|
+
return;
|
|
216
|
+
e.queued = true;
|
|
217
|
+
G.queue.push(e);
|
|
218
|
+
if (!G.scheduled && G.batchDepth === 0) {
|
|
219
|
+
G.scheduled = true;
|
|
220
|
+
G.hostSchedule(flushSync);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** Drain all pending effects now (synchronously). */
|
|
224
|
+
export function flushSync() {
|
|
225
|
+
G.scheduled = false;
|
|
226
|
+
// Re-entrancy guard: an effect that writes a signal during its run re-enqueues;
|
|
227
|
+
// the running loop below picks that up, so a nested flushSync must be a no-op
|
|
228
|
+
// (otherwise the same G.queue drains twice).
|
|
229
|
+
if (G.flushing)
|
|
230
|
+
return;
|
|
231
|
+
G.flushing = true;
|
|
232
|
+
let guard = 0;
|
|
233
|
+
// An effect that throws must not stop the rest: those queued after it would be dropped
|
|
234
|
+
// with their `queued` mark still set, so nothing could ever run them again. The first
|
|
235
|
+
// error is rethrown once the queue is drained.
|
|
236
|
+
let failed = false;
|
|
237
|
+
let first;
|
|
238
|
+
try {
|
|
239
|
+
while (G.queue.length) {
|
|
240
|
+
if (++guard > 100000)
|
|
241
|
+
throw new Error('[fluixi] effect flush did not settle (cycle?)');
|
|
242
|
+
const batchOf = G.queue;
|
|
243
|
+
G.queue = [];
|
|
244
|
+
for (const e of batchOf) {
|
|
245
|
+
e.queued = false;
|
|
246
|
+
if (e.disposed)
|
|
247
|
+
continue;
|
|
248
|
+
try {
|
|
249
|
+
e.run();
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
if (!failed) {
|
|
253
|
+
failed = true;
|
|
254
|
+
first = error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
G.flushing = false;
|
|
262
|
+
}
|
|
263
|
+
if (failed)
|
|
264
|
+
throw first;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Run pending effects after a write completes, but only at the top level (not
|
|
268
|
+
* inside a batch). A single write thus marks the whole graph dirty first, then
|
|
269
|
+
* flushes once (legacy-equivalent synchronous effects, glitch-free). The `G.flushing`
|
|
270
|
+
* guard makes a write inside an effect a no-op here (the running loop drains it).
|
|
271
|
+
*/
|
|
272
|
+
export function afterWrite() {
|
|
273
|
+
if (G.batchDepth === 0)
|
|
274
|
+
flushSync();
|
|
275
|
+
}
|
|
276
|
+
/** Group writes so dependent effects flush once, at the end. */
|
|
277
|
+
export function batch(fn) {
|
|
278
|
+
G.batchDepth++;
|
|
279
|
+
try {
|
|
280
|
+
return fn();
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
if (--G.batchDepth === 0)
|
|
284
|
+
flushSync();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// ── effect(): built on Computed + Watcher + Owner ───────────────────────────
|
|
288
|
+
class EffectNode {
|
|
289
|
+
computed;
|
|
290
|
+
watcher;
|
|
291
|
+
owner;
|
|
292
|
+
queued = false;
|
|
293
|
+
disposed = false;
|
|
294
|
+
constructor(fn, report = false, name) {
|
|
295
|
+
this.owner = makeOwner(G.owner);
|
|
296
|
+
// Tie disposal to the creating scope, when the parent owner tears down it must
|
|
297
|
+
// fully dispose this effect (unwatch + body cleanups), not just run the body
|
|
298
|
+
// cleanups. Register dispose() as a parent cleanup rather than as an owned tree.
|
|
299
|
+
if (G.owner)
|
|
300
|
+
(G.owner.cleanups ??= []).push(() => this.dispose());
|
|
301
|
+
// The Computed runs the user fn inside a tracking scope + this effect's owner,
|
|
302
|
+
// so reads subscribe and onCleanup/nested roots attach here.
|
|
303
|
+
this.computed = new Computed(() => {
|
|
304
|
+
// teardown previous run's cleanups before re-running. not final: the owner is
|
|
305
|
+
// reused for this run, so it must stay alive (only its children are disposed).
|
|
306
|
+
disposeOwner(this.owner, false);
|
|
307
|
+
runWithOwner(this.owner, () => {
|
|
308
|
+
const r = fn();
|
|
309
|
+
if (typeof r === 'function')
|
|
310
|
+
(this.owner.cleanups ??= []).push(r);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
// Before the first run, so a watcher counts that run like every later one. A render
|
|
314
|
+
// effect is reported too, marked as plumbing: it is not worth drawing, but a node made
|
|
315
|
+
// inside one has to be able to find its way back to the component that caused it.
|
|
316
|
+
reportReactiveNode({ kind: 'effect', node: this.computed, handle: this, internal: !report, ...(name ? { name } : {}) });
|
|
317
|
+
// The watcher only schedules; the recompute happens on flush.
|
|
318
|
+
this.watcher = new Watcher(() => enqueue(this));
|
|
319
|
+
this.run(); // initial synchronous run (Solid semantics)
|
|
320
|
+
this.watcher.watch(this.computed);
|
|
321
|
+
}
|
|
322
|
+
run() {
|
|
323
|
+
if (this.disposed)
|
|
324
|
+
return;
|
|
325
|
+
this.watcher.getPending(); // clear pending; reading re-arms the watcher
|
|
326
|
+
try {
|
|
327
|
+
this.computed.get(); // recompute if dirty (re-runs the effect body)
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
// A suspended read (createResource while pending) throws its promise. Report
|
|
331
|
+
// it to the nearest Suspense boundary; the effect re-runs via normal
|
|
332
|
+
// reactivity once the resource's state signal changes. Other errors propagate.
|
|
333
|
+
if (e && typeof e.then === 'function' && G.onSuspend) {
|
|
334
|
+
G.onSuspend(e, this.owner);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Route a real error to the nearest error boundary; rethrow if none handled it.
|
|
338
|
+
if (G.onError && G.onError(e, this.owner))
|
|
339
|
+
return;
|
|
340
|
+
throw e;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
dispose() {
|
|
344
|
+
if (this.disposed)
|
|
345
|
+
return;
|
|
346
|
+
this.disposed = true;
|
|
347
|
+
reportReactiveDispose(this.computed);
|
|
348
|
+
this.watcher.unwatch(this.computed);
|
|
349
|
+
disposeOwner(this.owner);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/** A re-running side effect. Runs once now, then again (G.scheduled) when deps change. */
|
|
353
|
+
export function effect(fn, report = false, name) {
|
|
354
|
+
// Reported only when the caller says so. Every DOM binding is a render effect through this
|
|
355
|
+
// same factory, and a graph holding one node per text node is not a graph anyone reads.
|
|
356
|
+
const node = new EffectNode(fn, report, name);
|
|
357
|
+
return () => node.dispose();
|
|
358
|
+
}
|