@fluixi/reactive 1.0.0-alpha.53
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 +343 -0
- package/dist/cdn/reactive.cjs +1 -0
- package/dist/cdn/reactive.d.ts +11 -0
- package/dist/cdn/reactive.d.ts.map +1 -0
- package/dist/cdn/reactive.global.js +1 -0
- package/dist/cdn/reactive.js +10 -0
- package/dist/cdn/reactive.mjs +1 -0
- package/dist/cdn/signal.cjs +1 -0
- package/dist/cdn/signal.d.ts +19 -0
- package/dist/cdn/signal.d.ts.map +1 -0
- package/dist/cdn/signal.global.js +1 -0
- package/dist/cdn/signal.js +18 -0
- package/dist/cdn/signal.mjs +1 -0
- package/dist/index.cjs +1 -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 +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +4 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/observable/index.cjs +1 -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 +1 -0
- package/dist/lib/observable/observable.cjs +1 -0
- package/dist/lib/observable/observable.d.ts +383 -0
- package/dist/lib/observable/observable.d.ts.map +1 -0
- package/dist/lib/observable/observable.js +339 -0
- package/dist/lib/observable/observable.mjs +1 -0
- package/dist/lib/observable/subscription.cjs +1 -0
- package/dist/lib/observable/subscription.d.ts +70 -0
- package/dist/lib/observable/subscription.d.ts.map +1 -0
- package/dist/lib/observable/subscription.js +190 -0
- package/dist/lib/observable/subscription.mjs +1 -0
- package/dist/lib/observable/types.cjs +1 -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/signal/cached-resource.cjs +1 -0
- package/dist/lib/signal/cached-resource.d.ts +94 -0
- package/dist/lib/signal/cached-resource.d.ts.map +1 -0
- package/dist/lib/signal/cached-resource.js +257 -0
- package/dist/lib/signal/cached-resource.mjs +1 -0
- package/dist/lib/signal/graph/children.cjs +1 -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 +1 -0
- package/dist/lib/signal/graph/compat.cjs +1 -0
- package/dist/lib/signal/graph/compat.d.ts +64 -0
- package/dist/lib/signal/graph/compat.d.ts.map +1 -0
- package/dist/lib/signal/graph/compat.js +178 -0
- package/dist/lib/signal/graph/compat.mjs +1 -0
- package/dist/lib/signal/graph/core.cjs +1 -0
- package/dist/lib/signal/graph/core.d.ts +95 -0
- package/dist/lib/signal/graph/core.d.ts.map +1 -0
- package/dist/lib/signal/graph/core.js +254 -0
- package/dist/lib/signal/graph/core.mjs +1 -0
- package/dist/lib/signal/graph/derived.cjs +1 -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 +81 -0
- package/dist/lib/signal/graph/derived.mjs +1 -0
- package/dist/lib/signal/graph/detect.cjs +1 -0
- package/dist/lib/signal/graph/detect.d.ts +16 -0
- package/dist/lib/signal/graph/detect.d.ts.map +1 -0
- package/dist/lib/signal/graph/detect.js +36 -0
- package/dist/lib/signal/graph/detect.mjs +1 -0
- package/dist/lib/signal/graph/error-boundary.cjs +1 -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 +1 -0
- package/dist/lib/signal/graph/index.cjs +1 -0
- package/dist/lib/signal/graph/index.d.ts +21 -0
- package/dist/lib/signal/graph/index.d.ts.map +1 -0
- package/dist/lib/signal/graph/index.js +20 -0
- package/dist/lib/signal/graph/index.mjs +1 -0
- package/dist/lib/signal/graph/resource.cjs +1 -0
- package/dist/lib/signal/graph/resource.d.ts +36 -0
- package/dist/lib/signal/graph/resource.d.ts.map +1 -0
- package/dist/lib/signal/graph/resource.js +156 -0
- package/dist/lib/signal/graph/resource.mjs +1 -0
- package/dist/lib/signal/graph/runtime.cjs +1 -0
- package/dist/lib/signal/graph/runtime.d.ts +56 -0
- package/dist/lib/signal/graph/runtime.d.ts.map +1 -0
- package/dist/lib/signal/graph/runtime.js +277 -0
- package/dist/lib/signal/graph/runtime.mjs +1 -0
- package/dist/lib/signal/graph/signal-next.cjs +1 -0
- package/dist/lib/signal/graph/signal-next.d.ts +37 -0
- package/dist/lib/signal/graph/signal-next.d.ts.map +1 -0
- package/dist/lib/signal/graph/signal-next.js +41 -0
- package/dist/lib/signal/graph/signal-next.mjs +1 -0
- package/dist/lib/signal/graph/ssr-hooks.cjs +1 -0
- package/dist/lib/signal/graph/ssr-hooks.d.ts +28 -0
- package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -0
- package/dist/lib/signal/graph/ssr-hooks.js +45 -0
- package/dist/lib/signal/graph/ssr-hooks.mjs +1 -0
- package/dist/lib/signal/graph/state.cjs +1 -0
- package/dist/lib/signal/graph/state.d.ts +43 -0
- package/dist/lib/signal/graph/state.d.ts.map +1 -0
- package/dist/lib/signal/graph/state.js +37 -0
- package/dist/lib/signal/graph/state.mjs +1 -0
- package/dist/lib/signal/graph/suspense.cjs +1 -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 +1 -0
- package/dist/lib/signal/index-legacy.cjs +1 -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 +1 -0
- package/dist/lib/signal/index.cjs +1 -0
- package/dist/lib/signal/index.d.ts +12 -0
- package/dist/lib/signal/index.d.ts.map +1 -0
- package/dist/lib/signal/index.js +11 -0
- package/dist/lib/signal/index.mjs +1 -0
- package/dist/lib/signal/list.cjs +1 -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 +1 -0
- package/dist/lib/signal/signal.cjs +1 -0
- package/dist/lib/signal/signal.d.ts +295 -0
- package/dist/lib/signal/signal.d.ts.map +1 -0
- package/dist/lib/signal/signal.js +1219 -0
- package/dist/lib/signal/signal.mjs +1 -0
- package/dist/lib/signal/types.cjs +1 -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 +1 -0
- package/dist/lib/signal/utilities.cjs +1 -0
- package/dist/lib/signal/utilities.d.ts +355 -0
- package/dist/lib/signal/utilities.d.ts.map +1 -0
- package/dist/lib/signal/utilities.js +504 -0
- package/dist/lib/signal/utilities.mjs +1 -0
- package/dist/lib/store/index.cjs +1 -0
- package/dist/lib/store/index.d.ts +14 -0
- package/dist/lib/store/index.d.ts.map +1 -0
- package/dist/lib/store/index.js +11 -0
- package/dist/lib/store/index.mjs +1 -0
- package/dist/lib/store/setter.types.cjs +1 -0
- package/dist/lib/store/setter.types.d.ts +79 -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 +1 -0
- package/dist/lib/store/store.d.ts +86 -0
- package/dist/lib/store/store.d.ts.map +1 -0
- package/dist/lib/store/store.js +442 -0
- package/dist/lib/store/store.mjs +1 -0
- package/dist/lib/store/types.cjs +1 -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 +1 -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 +1 -0
- package/dist/lib/subject.cjs +1 -0
- package/dist/lib/subject.d.ts +14 -0
- package/dist/lib/subject.d.ts.map +1 -0
- package/dist/lib/subject.js +169 -0
- package/dist/lib/subject.mjs +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +114 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 (core) — the legacy public reactive API re-expressed on top of L0 (graph) +
|
|
3
|
+
* L1 (runtime), with byte-identical signatures so it is a drop-in for the existing
|
|
4
|
+
* `createSignal`/`createMemo`/`createEffect`/… surface. This is the "re-point": the
|
|
5
|
+
* old API delegates to the TC39-shaped core instead of the bespoke Computation graph.
|
|
6
|
+
*
|
|
7
|
+
* Scope: the CORE primitives (signals, memos, effects, roots, batch, untrack, on).
|
|
8
|
+
* The wider surface (resources, stores, context, suspense, transitions, the SSR
|
|
9
|
+
* eager-stale/drain hooks) is ported separately before the production export flips
|
|
10
|
+
* — until then this lives behind its own module + spec, validated for behavioural
|
|
11
|
+
* equivalence against the legacy oracle.
|
|
12
|
+
*
|
|
13
|
+
* Timing note: legacy effects flush SYNCHRONOUSLY on a write (outside batch), so we
|
|
14
|
+
* install a synchronous L1 scheduler here; `batch` still coalesces to one flush.
|
|
15
|
+
*/
|
|
16
|
+
import { State, Computed, untrack as l0untrack } from './core.js';
|
|
17
|
+
import { effect as l1effect, batch as l1batch, setScheduler, afterWrite, flushSync, reportSuspend, getOwner, runWithOwner, } from './runtime.js';
|
|
18
|
+
function isThenable(v) {
|
|
19
|
+
return !!v && typeof v.then === 'function';
|
|
20
|
+
}
|
|
21
|
+
export { createRoot, onCleanup, getOwner, setOwner, runWithOwner } from './runtime.js';
|
|
22
|
+
// Compat owns all flush points (afterWrite / batch), so the host scheduler is a
|
|
23
|
+
// no-op: enqueue only QUEUES; a write marks the whole graph then flushes once via
|
|
24
|
+
// afterWrite. This separates marking from running (glitch-free, legacy-equivalent
|
|
25
|
+
// synchronous effects) — a synchronous scheduler that flushed mid-propagation would
|
|
26
|
+
// run effects before all observers were marked.
|
|
27
|
+
setScheduler(() => { });
|
|
28
|
+
const $SIGNAL = Symbol.for('signal');
|
|
29
|
+
const $MEMO = Symbol.for('memo');
|
|
30
|
+
// The legacy createSignal attaches a ReactiveNode (with a live `.value`/`.version`)
|
|
31
|
+
// under this symbol; consumers like the store read `signal[$NODE].value` to gate
|
|
32
|
+
// updates. compat exposes a matching node-like view so those consumers work unchanged.
|
|
33
|
+
const $NODE = Symbol.for('signal-node');
|
|
34
|
+
/** createSignal — a writable cell, returned as the legacy `[get, set]` tuple. */
|
|
35
|
+
export function createSignal(value, options) {
|
|
36
|
+
const state = new State(value, { equals: options?.equals });
|
|
37
|
+
const read = (() => state.get());
|
|
38
|
+
const write = ((next) => {
|
|
39
|
+
const v = typeof next === 'function' ? next(state.peek()) : next;
|
|
40
|
+
state.set(v); // marks the graph dirty
|
|
41
|
+
afterWrite(); // then flush effects once (no-op inside a batch)
|
|
42
|
+
return v;
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
|
|
45
|
+
// Node-like view for consumers (e.g. the store) that read `signal[$NODE].value`.
|
|
46
|
+
Object.defineProperty(read, $NODE, {
|
|
47
|
+
value: {
|
|
48
|
+
get value() {
|
|
49
|
+
return state.peek();
|
|
50
|
+
},
|
|
51
|
+
get version() {
|
|
52
|
+
return state.version;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
enumerable: false,
|
|
56
|
+
});
|
|
57
|
+
return [read, write];
|
|
58
|
+
}
|
|
59
|
+
/** createMemo — a lazy, cached derived value (legacy accessor). */
|
|
60
|
+
export function createMemo(fn, value, options) {
|
|
61
|
+
let prev = value;
|
|
62
|
+
// Capture the owner at creation so the memo's fn (and any component it renders, e.g.
|
|
63
|
+
// a lazy route) runs under THIS scope's owner — preserving context up the owner chain
|
|
64
|
+
// even when the memo recomputes later (async SSR drain). Matches legacy memo semantics.
|
|
65
|
+
const owner = getOwner();
|
|
66
|
+
const c = new Computed(() => runWithOwner(owner, () => {
|
|
67
|
+
try {
|
|
68
|
+
return (prev = fn(prev));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// A suspended read (resource/lazy while pending) throws its promise. Legacy
|
|
72
|
+
// memos keep their previous value and let a Suspense boundary handle it,
|
|
73
|
+
// rather than rethrowing the promise as an error.
|
|
74
|
+
if (isThenable(e)) {
|
|
75
|
+
reportSuspend(e);
|
|
76
|
+
return prev;
|
|
77
|
+
}
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}), { equals: options?.equals });
|
|
81
|
+
const read = (() => c.get());
|
|
82
|
+
// A memo IS a readable reactive accessor — stamp $SIGNAL (matching the legacy
|
|
83
|
+
// createMemo) so `isSignal(memo)` is true. Without this, the DOM runtime's
|
|
84
|
+
// insert() fails to recognise a control-flow memo (For/Switch/Index/Dynamic)
|
|
85
|
+
// as reactive, caches it as a component, and freezes it → updates never apply.
|
|
86
|
+
Object.defineProperty(read, $SIGNAL, { value: true, enumerable: true });
|
|
87
|
+
Object.defineProperty(read, $MEMO, { value: true, enumerable: true });
|
|
88
|
+
return read;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* createEffect — a re-running side effect. The fn receives the previous return
|
|
92
|
+
* value and may return a new one (legacy signature) or a cleanup function.
|
|
93
|
+
*/
|
|
94
|
+
export function createEffect(fn, value) {
|
|
95
|
+
let prev = value;
|
|
96
|
+
return l1effect(() => {
|
|
97
|
+
const r = fn(prev);
|
|
98
|
+
// A function result is treated as the next "prev" (legacy) — unless the fn
|
|
99
|
+
// never returns one, in which case onCleanup is the cleanup path. We keep the
|
|
100
|
+
// legacy carry-the-value behaviour; cleanups use onCleanup.
|
|
101
|
+
prev = r;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* createComputed — an EAGER, owner-managed reactive computation (returns void).
|
|
106
|
+
* Unlike a lazy memo it runs immediately and re-runs eagerly when a dep changes;
|
|
107
|
+
* used for derivations with side effects (e.g. store internals). On L1 that is an
|
|
108
|
+
* eager effect whose dispose is owned by the current scope.
|
|
109
|
+
*/
|
|
110
|
+
export function createComputed(fn, value) {
|
|
111
|
+
let prev = value;
|
|
112
|
+
l1effect(() => {
|
|
113
|
+
prev = fn(prev);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* createRenderEffect — an effect that runs in the render phase. For the reactive
|
|
118
|
+
* core this is an eager effect (the render-vs-commit timing distinction is a DOM-
|
|
119
|
+
* layer concern); returns a dispose like createEffect.
|
|
120
|
+
*/
|
|
121
|
+
export function createRenderEffect(fn, value) {
|
|
122
|
+
let prev = value;
|
|
123
|
+
return l1effect(() => {
|
|
124
|
+
prev = fn(prev);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** batch — coalesce writes so dependent effects flush once. */
|
|
128
|
+
export function batch(fn) {
|
|
129
|
+
return l1batch(fn);
|
|
130
|
+
}
|
|
131
|
+
/** untrack — read without subscribing. */
|
|
132
|
+
export function untrack(fn) {
|
|
133
|
+
return l0untrack(fn);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* startTransition — run `fn`'s updates as one batch. (Minimal port: the concurrent
|
|
137
|
+
* time-slicing/deferral of the legacy implementation is a later refinement; the
|
|
138
|
+
* batched-commit semantics that callers rely on are preserved.)
|
|
139
|
+
*/
|
|
140
|
+
export function startTransition(fn) {
|
|
141
|
+
l1batch(fn);
|
|
142
|
+
return Promise.resolve();
|
|
143
|
+
}
|
|
144
|
+
/** useTransition — `[isPending, startTransition]`. isPending is synchronous here (no deferral). */
|
|
145
|
+
export function useTransition() {
|
|
146
|
+
return [() => false, startTransition];
|
|
147
|
+
}
|
|
148
|
+
/** Force any pending effects to run now. */
|
|
149
|
+
export { flushSync };
|
|
150
|
+
/**
|
|
151
|
+
* flush — drain pending effects, returning a Promise (legacy `flush()` is async; many
|
|
152
|
+
* callers do `flush().then(...)`). Effects already flush synchronously on each write via
|
|
153
|
+
* afterWrite, so this just settles any remainder and resolves.
|
|
154
|
+
*/
|
|
155
|
+
export function flush() {
|
|
156
|
+
flushSync();
|
|
157
|
+
return Promise.resolve();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* on(deps, fn, { defer }) — an explicit-dependency helper. Reads `deps` (tracked),
|
|
161
|
+
* then runs `fn(value, prevValue, prevResult)` untracked. `defer` skips the first run.
|
|
162
|
+
*/
|
|
163
|
+
export function on(deps, fn, options) {
|
|
164
|
+
const isArray = Array.isArray(deps);
|
|
165
|
+
let prevValue;
|
|
166
|
+
let defer = options?.defer ?? false;
|
|
167
|
+
return (prevResult) => {
|
|
168
|
+
const value = (isArray ? deps.map((d) => d()) : deps());
|
|
169
|
+
if (defer) {
|
|
170
|
+
defer = false;
|
|
171
|
+
prevValue = value;
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const result = l0untrack(() => fn(value, prevValue, prevResult));
|
|
175
|
+
prevValue = value;
|
|
176
|
+
return result;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var E="__fluixi_signal_next_state__",R=e=>{typeof queueMicrotask=="function"?queueMicrotask(e):Promise.resolve().then(e)},n=globalThis[E]??={consumer:null,untracked:!1,owner:null,queue:[],scheduled:!1,flushing:!1,batchDepth:0,hostSchedule:R,onSuspend:null,onError:null};var g=0,k=1,d=2,h=class{constructor(){this.observers=null;this.watchers=null;this.version=0}updateIfNecessary(){}track(){if(n.untracked)return;let t=n.consumer;t&&((this.observers??=new Set).add(t),(t.sources??=new Set).add(this))}},p=class extends h{constructor(t,r){super(),this._value=t,this._equals=r?.equals===!1?()=>!1:r?.equals??Object.is}get(){return this.track(),this._value}set(t){if(!this._equals(this._value,t)){if(this._value=t,this.version++,this.observers)for(let r of[...this.observers])r.markDirty(d);m(this)}}peek(){return this._value}},c=class extends h{constructor(r,o){super();this._threw=!1;this._initialized=!1;this.state=d;this.sources=null;this._fn=r,this._equals=o?.equals===!1?()=>!1:o?.equals??Object.is}get(){if(n.consumer===this)throw new Error("Signal.Computed cannot read itself");if(this.track(),this.updateIfNecessary(),this._threw)throw this._error;return this._value}updateIfNecessary(){if(this.state===k&&this.sources){for(let r of this.sources)if(r.updateIfNecessary(),this.state===d)break}(this.state===d||!this._initialized)&&this.recompute(),this.state=g}recompute(){if(this.sources){for(let l of this.sources)l.observers?.delete(this);this.sources.clear()}let r=n.consumer,o=n.untracked;n.consumer=this,n.untracked=!1;let s,i=!1,u;try{s=this._fn()}catch(l){i=!0,u=l,s=this._value}finally{n.consumer=r,n.untracked=o}let a=!this._initialized||this._threw!==i||(i?u!==this._error:!this._equals(this._value,s));if(this._initialized=!0,this._threw=i,this._error=u,this._value=s,a){if(this.version++,this.observers)for(let l of[...this.observers])l.markDirty(d);m(this)}}markDirty(r){if(this.state>=r)return;let o=this.state===g;if(this.state=r,o){if(this.observers)for(let s of[...this.observers])s.markDirty(k);m(this)}}peek(){if(this.updateIfNecessary(),this._threw)throw this._error;return this._value}};function m(e){if(e.watchers)for(let t of[...e.watchers])t._notify(e)}var w=class{constructor(t){this._watched=new Set;this._pending=new Set;this._notifyFn=t}watch(...t){for(let r of t)this._watched.add(r),(r.watchers??=new Set).add(this)}unwatch(...t){for(let r of t)this._watched.delete(r),r.watchers?.delete(this),this._pending.delete(r)}getPending(){let t=[...this._pending];return this._pending.clear(),t}_notify(t){if(!this._watched.has(t)||this._pending.has(t))return;let r=this._pending.size===0;this._pending.add(t),r&&this._notifyFn()}};function b(e){if(n.untracked)return e();n.untracked=!0;try{return e()}finally{n.untracked=!1}}function O(e){return{cleanups:null,owned:null,parent:e,contexts:null}}function q(e){return n.onSuspend?(n.onSuspend(e,n.owner),!0):!1}function S(){return n.owner}function N(e){let t=n.owner;return n.owner=e,t}function T(e,t){let r=n.owner;n.owner=e;try{return t()}finally{n.owner=r}}function M(e){n.owner&&(n.owner.cleanups??=[]).push(e)}function v(e){if(e.owned){for(let t=e.owned.length-1;t>=0;t--)v(e.owned[t]);e.owned=null}if(e.cleanups){for(let t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}}function I(e){let t=O(n.owner);n.owner&&(n.owner.owned??=[]).push(t);let r=n.owner;n.owner=t;try{return e(()=>v(t))}finally{n.owner=r}}function C(e){n.hostSchedule=e??(t=>{Promise.resolve().then(t)})}function W(e){e.queued||(e.queued=!0,n.queue.push(e),!n.scheduled&&n.batchDepth===0&&(n.scheduled=!0,n.hostSchedule(f)))}function f(){if(n.scheduled=!1,n.flushing)return;n.flushing=!0;let e=0;try{for(;n.queue.length;){if(++e>1e5)throw new Error("[fluixi] effect flush did not settle (cycle?)");let t=n.queue;n.queue=[];for(let r of t)r.queued=!1,r.disposed||r.run()}}finally{n.flushing=!1}}function P(){n.batchDepth===0&&f()}function _(e){n.batchDepth++;try{return e()}finally{--n.batchDepth===0&&f()}}var x=class{constructor(t){this.queued=!1;this.disposed=!1;this.owner=O(n.owner),n.owner&&(n.owner.cleanups??=[]).push(()=>this.dispose()),this.computed=new c(()=>{v(this.owner),T(this.owner,()=>{let r=t();typeof r=="function"&&(this.owner.cleanups??=[]).push(r)})}),this.watcher=new w(()=>W(this)),this.run(),this.watcher.watch(this.computed)}run(){if(!this.disposed){this.watcher.getPending();try{this.computed.get()}catch(t){if(t&&typeof t.then=="function"&&n.onSuspend){n.onSuspend(t,this.owner);return}if(n.onError&&n.onError(t,this.owner))return;throw t}}}dispose(){this.disposed||(this.disposed=!0,this.watcher.unwatch(this.computed),v(this.owner))}};function y(e){let t=new x(e);return()=>t.dispose()}function z(e){return!!e&&typeof e.then=="function"}C(()=>{});var D=Symbol.for("signal"),G=Symbol.for("memo"),j=Symbol.for("signal-node");function J(e,t){let r=new p(e,{equals:t?.equals}),o=(()=>r.get()),s=(i=>{let u=typeof i=="function"?i(r.peek()):i;return r.set(u),P(),u});return Object.defineProperty(o,D,{value:!0,enumerable:!0}),Object.defineProperty(o,j,{value:{get value(){return r.peek()},get version(){return r.version}},enumerable:!1}),[o,s]}function Q(e,t,r){let o=t,s=S(),i=new c(()=>T(s,()=>{try{return o=e(o)}catch(a){if(z(a))return q(a),o;throw a}}),{equals:r?.equals}),u=(()=>i.get());return Object.defineProperty(u,D,{value:!0,enumerable:!0}),Object.defineProperty(u,G,{value:!0,enumerable:!0}),u}function X(e,t){let r=t;return y(()=>{r=e(r)})}function Z(e,t){let r=t;y(()=>{r=e(r)})}function ee(e,t){let r=t;return y(()=>{r=e(r)})}function te(e){return _(e)}function ne(e){return b(e)}function V(e){return _(e),Promise.resolve()}function re(){return[()=>!1,V]}function oe(){return f(),Promise.resolve()}function se(e,t,r){let o=Array.isArray(e),s,i=r?.defer??!1;return u=>{let a=o?e.map(A=>A()):e();if(i){i=!1,s=a;return}let l=b(()=>t(a,s,u));return s=a,l}}export{te as batch,Z as createComputed,X as createEffect,Q as createMemo,ee as createRenderEffect,I as createRoot,J as createSignal,oe as flush,f as flushSync,S as getOwner,se as on,M as onCleanup,T as runWithOwner,N as setOwner,V as startTransition,ne as untrack,re as useTransition};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var l=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var S=(s,e)=>{for(var t in e)l(s,t,{get:e[t],enumerable:!0})},m=(s,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of T(e))!k.call(s,i)&&i!==t&&l(s,i,{get:()=>e[i],enumerable:!(o=b(e,i))||o.enumerable});return s};var g=s=>m(l({},"__esModule",{value:!0}),s);var D={};S(D,{Computed:()=>f,State:()=>d,Watcher:()=>p,currentComputed:()=>N,hasSinks:()=>E,untrack:()=>C});module.exports=g(D);var q="__fluixi_signal_next_state__",x=s=>{typeof queueMicrotask=="function"?queueMicrotask(s):Promise.resolve().then(s)},r=globalThis[q]??={consumer:null,untracked:!1,owner:null,queue:[],scheduled:!1,flushing:!1,batchDepth:0,hostSchedule:x,onSuspend:null,onError:null};var _=0,w=1,a=2,h=class{constructor(){this.observers=null;this.watchers=null;this.version=0}updateIfNecessary(){}track(){if(r.untracked)return;let e=r.consumer;e&&((this.observers??=new Set).add(e),(e.sources??=new Set).add(this))}},d=class extends h{constructor(e,t){super(),this._value=e,this._equals=t?.equals===!1?()=>!1:t?.equals??Object.is}get(){return this.track(),this._value}set(e){if(!this._equals(this._value,e)){if(this._value=e,this.version++,this.observers)for(let t of[...this.observers])t.markDirty(a);v(this)}}peek(){return this._value}},f=class extends h{constructor(t,o){super();this._threw=!1;this._initialized=!1;this.state=a;this.sources=null;this._fn=t,this._equals=o?.equals===!1?()=>!1:o?.equals??Object.is}get(){if(r.consumer===this)throw new Error("Signal.Computed cannot read itself");if(this.track(),this.updateIfNecessary(),this._threw)throw this._error;return this._value}updateIfNecessary(){if(this.state===w&&this.sources){for(let t of this.sources)if(t.updateIfNecessary(),this.state===a)break}(this.state===a||!this._initialized)&&this.recompute(),this.state=_}recompute(){if(this.sources){for(let n of this.sources)n.observers?.delete(this);this.sources.clear()}let t=r.consumer,o=r.untracked;r.consumer=this,r.untracked=!1;let i,u=!1,c;try{i=this._fn()}catch(n){u=!0,c=n,i=this._value}finally{r.consumer=t,r.untracked=o}let y=!this._initialized||this._threw!==u||(u?c!==this._error:!this._equals(this._value,i));if(this._initialized=!0,this._threw=u,this._error=c,this._value=i,y){if(this.version++,this.observers)for(let n of[...this.observers])n.markDirty(a);v(this)}}markDirty(t){if(this.state>=t)return;let o=this.state===_;if(this.state=t,o){if(this.observers)for(let i of[...this.observers])i.markDirty(w);v(this)}}peek(){if(this.updateIfNecessary(),this._threw)throw this._error;return this._value}};function v(s){if(s.watchers)for(let e of[...s.watchers])e._notify(s)}var p=class{constructor(e){this._watched=new Set;this._pending=new Set;this._notifyFn=e}watch(...e){for(let t of e)this._watched.add(t),(t.watchers??=new Set).add(this)}unwatch(...e){for(let t of e)this._watched.delete(t),t.watchers?.delete(this),this._pending.delete(t)}getPending(){let e=[...this._pending];return this._pending.clear(),e}_notify(e){if(!this._watched.has(e)||this._pending.has(e))return;let t=this._pending.size===0;this._pending.add(e),t&&this._notifyFn()}};function C(s){if(r.untracked)return s();r.untracked=!0;try{return s()}finally{r.untracked=!1}}function N(){return r.consumer}function E(s){return!!(s.observers&&s.observers.size)||!!(s.watchers&&s.watchers.size)}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0 — the TC39-shaped reactive graph core.
|
|
3
|
+
*
|
|
4
|
+
* Class-based primitives (`State`, `Computed`, `Watcher`) that own ONLY the
|
|
5
|
+
* dependency graph: values, versions, source/observer edges, the lazy push-pull
|
|
6
|
+
* state machine, and glitch-free pull evaluation. They know nothing about
|
|
7
|
+
* ownership, scheduling, effects, suspense, or resources — those live in the L1
|
|
8
|
+
* runtime that sits on top (see ../signal.ts). This is a re-shaping of the exact
|
|
9
|
+
* algorithm the existing graph already runs (CLEAN/CHECK/DIRTY + version + the
|
|
10
|
+
* `Listener`/observers edges), so behaviour is preserved; it's just relocated
|
|
11
|
+
* onto classes whose shape matches the TC39 Signals proposal.
|
|
12
|
+
*
|
|
13
|
+
* The model is the "push the dirt, pull the value" scheme (Reactively / SolidJS /
|
|
14
|
+
* the TC39 polyfill): a write PUSHES a maybe-dirty mark down to observers without
|
|
15
|
+
* recomputing; a read PULLS, settling upstream lazily and recomputing only what
|
|
16
|
+
* actually changed (equality-gated). Reads outside a tracking scope don't subscribe.
|
|
17
|
+
*/
|
|
18
|
+
declare const CLEAN = 0;
|
|
19
|
+
declare const CHECK = 1;
|
|
20
|
+
declare const DIRTY = 2;
|
|
21
|
+
type NodeState = typeof CLEAN | typeof CHECK | typeof DIRTY;
|
|
22
|
+
/** A thing that can be depended upon: holds observers + a monotonic version. */
|
|
23
|
+
declare abstract class Source {
|
|
24
|
+
/** Computeds (and watched leaves) that read this node. (was the signalObservers entry) */
|
|
25
|
+
observers: Set<Computed<any>> | null;
|
|
26
|
+
/** Watchers eagerly observing this node, for effect scheduling (L1). */
|
|
27
|
+
watchers: Set<Watcher> | null;
|
|
28
|
+
/** Bumped whenever this node's value actually changes (equality-gated). */
|
|
29
|
+
version: number;
|
|
30
|
+
/** Settle this node so a reader sees a current value/version. Overridden by Computed. */
|
|
31
|
+
updateIfNecessary(): void;
|
|
32
|
+
/** Link the current consumer (if any) as an observer of this node. */
|
|
33
|
+
protected track(): void;
|
|
34
|
+
}
|
|
35
|
+
export declare class State<T> extends Source {
|
|
36
|
+
private _value;
|
|
37
|
+
private readonly _equals;
|
|
38
|
+
constructor(value: T, options?: {
|
|
39
|
+
equals?: ((a: T, b: T) => boolean) | false;
|
|
40
|
+
});
|
|
41
|
+
get(): T;
|
|
42
|
+
set(next: T): void;
|
|
43
|
+
/** Read without subscribing (peek). */
|
|
44
|
+
peek(): T;
|
|
45
|
+
}
|
|
46
|
+
export declare class Computed<T> extends Source {
|
|
47
|
+
private _value;
|
|
48
|
+
private _error;
|
|
49
|
+
private _threw;
|
|
50
|
+
private _initialized;
|
|
51
|
+
state: NodeState;
|
|
52
|
+
/** Nodes this computation read on its last run. (was Computation.sources) */
|
|
53
|
+
sources: Set<Source> | null;
|
|
54
|
+
private readonly _fn;
|
|
55
|
+
private readonly _equals;
|
|
56
|
+
constructor(fn: () => T, options?: {
|
|
57
|
+
equals?: ((a: T, b: T) => boolean) | false;
|
|
58
|
+
});
|
|
59
|
+
get(): T;
|
|
60
|
+
/** Settle: if CHECK, verify sources; if DIRTY (or never run), recompute. */
|
|
61
|
+
updateIfNecessary(): void;
|
|
62
|
+
private recompute;
|
|
63
|
+
/**
|
|
64
|
+
* Push a maybe-dirty mark. A direct observer of a changed node gets DIRTY; its
|
|
65
|
+
* own observers get CHECK (recurse) — stopping where already marked, so the
|
|
66
|
+
* push is O(newly-dirtied) and never recomputes.
|
|
67
|
+
*/
|
|
68
|
+
markDirty(level: NodeState): void;
|
|
69
|
+
peek(): T;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Signal.subtle.Watcher — the only eager observation primitive. Its callback fires
|
|
73
|
+
* when a watched signal MAY have changed; the callback MUST NOT read or write
|
|
74
|
+
* signals. Effects (L1) are built on this + a scheduler.
|
|
75
|
+
*/
|
|
76
|
+
export declare class Watcher {
|
|
77
|
+
private readonly _notifyFn;
|
|
78
|
+
private readonly _watched;
|
|
79
|
+
private _pending;
|
|
80
|
+
constructor(notify: () => void);
|
|
81
|
+
watch(...nodes: Source[]): void;
|
|
82
|
+
unwatch(...nodes: Source[]): void;
|
|
83
|
+
/** Watched nodes that may have changed since the last getPending(). */
|
|
84
|
+
getPending(): Source[];
|
|
85
|
+
/** @internal — a watched node became non-clean. Edge-triggered: notify once. */
|
|
86
|
+
_notify(node: Source): void;
|
|
87
|
+
}
|
|
88
|
+
/** Signal.subtle.untrack — run `fn` without subscribing the current consumer. */
|
|
89
|
+
export declare function untrack<T>(fn: () => T): T;
|
|
90
|
+
/** Signal.subtle.currentComputed — the computed currently evaluating, or null. */
|
|
91
|
+
export declare function currentComputed(): Computed<unknown> | null;
|
|
92
|
+
/** Whether a node currently has observers/watchers (Signal.subtle.hasSinks). */
|
|
93
|
+
export declare function hasSinks(node: Source): boolean;
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,QAAA,MAAM,KAAK,IAAI,CAAC;AAChB,QAAA,MAAM,KAAK,IAAI,CAAC;AAChB,QAAA,MAAM,KAAK,IAAI,CAAC;AAChB,KAAK,SAAS,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,CAAC;AAK5D,gFAAgF;AAChF,uBAAe,MAAM;IACnB,0FAA0F;IAE1F,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAQ;IAC5C,wEAAwE;IACxE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAQ;IACrC,2EAA2E;IAC3E,OAAO,SAAK;IAEZ,yFAAyF;IACzF,iBAAiB,IAAI,IAAI;IAEzB,sEAAsE;IACtE,SAAS,CAAC,KAAK,IAAI,IAAI;CAOxB;AAED,qBAAa,KAAK,CAAC,CAAC,CAAE,SAAQ,MAAM;IAClC,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,KAAK,CAAA;KAAE;IAM9E,GAAG,IAAI,CAAC;IAKR,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IAYlB,uCAAuC;IACvC,IAAI,IAAI,CAAC;CAGV;AAED,qBAAa,QAAQ,CAAC,CAAC,CAAE,SAAQ,MAAM;IACrC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAS;IAC7B,KAAK,EAAE,SAAS,CAAS;IACzB,6EAA6E;IAC7E,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,KAAK,CAAA;KAAE;IAMjF,GAAG,IAAI,CAAC;IAWR,4EAA4E;IACnE,iBAAiB,IAAI,IAAI;IAelC,OAAO,CAAC,SAAS;IA0CjB;;;;OAIG;IACH,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAUjC,IAAI,IAAI,CAAC;CAKV;AAOD;;;;GAIG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAqB;gBAEzB,MAAM,EAAE,MAAM,IAAI;IAI9B,KAAK,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAO/B,OAAO,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAQjC,uEAAuE;IACvE,UAAU,IAAI,MAAM,EAAE;IAMtB,gFAAgF;IAChF,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAM5B;AAED,iFAAiF;AACjF,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQzC;AAED,kFAAkF;AAClF,wBAAgB,eAAe,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAE1D;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0 — the TC39-shaped reactive graph core.
|
|
3
|
+
*
|
|
4
|
+
* Class-based primitives (`State`, `Computed`, `Watcher`) that own ONLY the
|
|
5
|
+
* dependency graph: values, versions, source/observer edges, the lazy push-pull
|
|
6
|
+
* state machine, and glitch-free pull evaluation. They know nothing about
|
|
7
|
+
* ownership, scheduling, effects, suspense, or resources — those live in the L1
|
|
8
|
+
* runtime that sits on top (see ../signal.ts). This is a re-shaping of the exact
|
|
9
|
+
* algorithm the existing graph already runs (CLEAN/CHECK/DIRTY + version + the
|
|
10
|
+
* `Listener`/observers edges), so behaviour is preserved; it's just relocated
|
|
11
|
+
* onto classes whose shape matches the TC39 Signals proposal.
|
|
12
|
+
*
|
|
13
|
+
* The model is the "push the dirt, pull the value" scheme (Reactively / SolidJS /
|
|
14
|
+
* the TC39 polyfill): a write PUSHES a maybe-dirty mark down to observers without
|
|
15
|
+
* recomputing; a read PULLS, settling upstream lazily and recomputing only what
|
|
16
|
+
* actually changed (equality-gated). Reads outside a tracking scope don't subscribe.
|
|
17
|
+
*/
|
|
18
|
+
import { G } from './state.js';
|
|
19
|
+
// Node states. CHECK = "a source may have changed — verify on read" (your STALE);
|
|
20
|
+
// DIRTY = "definitely recompute"; CLEAN = "cached value is current".
|
|
21
|
+
const CLEAN = 0;
|
|
22
|
+
const CHECK = 1;
|
|
23
|
+
const DIRTY = 2;
|
|
24
|
+
// The current consumer + untrack flag live on the shared globalThis-backed state (G)
|
|
25
|
+
// so duplicate module copies share one graph. `consumer`/`untracked` proxy them.
|
|
26
|
+
/** A thing that can be depended upon: holds observers + a monotonic version. */
|
|
27
|
+
class Source {
|
|
28
|
+
/** Computeds (and watched leaves) that read this node. (was the signalObservers entry) */
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
observers = null;
|
|
31
|
+
/** Watchers eagerly observing this node, for effect scheduling (L1). */
|
|
32
|
+
watchers = null;
|
|
33
|
+
/** Bumped whenever this node's value actually changes (equality-gated). */
|
|
34
|
+
version = 0;
|
|
35
|
+
/** Settle this node so a reader sees a current value/version. Overridden by Computed. */
|
|
36
|
+
updateIfNecessary() { }
|
|
37
|
+
/** Link the current consumer (if any) as an observer of this node. */
|
|
38
|
+
track() {
|
|
39
|
+
if (G.untracked)
|
|
40
|
+
return;
|
|
41
|
+
const consumer = G.consumer;
|
|
42
|
+
if (!consumer)
|
|
43
|
+
return;
|
|
44
|
+
(this.observers ??= new Set()).add(consumer);
|
|
45
|
+
(consumer.sources ??= new Set()).add(this);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class State extends Source {
|
|
49
|
+
_value;
|
|
50
|
+
_equals;
|
|
51
|
+
constructor(value, options) {
|
|
52
|
+
super();
|
|
53
|
+
this._value = value;
|
|
54
|
+
this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
|
|
55
|
+
}
|
|
56
|
+
get() {
|
|
57
|
+
this.track();
|
|
58
|
+
return this._value;
|
|
59
|
+
}
|
|
60
|
+
set(next) {
|
|
61
|
+
if (this._equals(this._value, next))
|
|
62
|
+
return;
|
|
63
|
+
this._value = next;
|
|
64
|
+
this.version++;
|
|
65
|
+
// Push: direct observers are DIRTY, their transitive observers are CHECK.
|
|
66
|
+
// Snapshot first — a synchronous scheduler can re-run an effect mid-propagation,
|
|
67
|
+
// which re-subscribes (mutates `observers`); iterating the live Set would then
|
|
68
|
+
// re-visit the re-added node forever. (Matches the legacy Array.from(observers).)
|
|
69
|
+
if (this.observers)
|
|
70
|
+
for (const o of [...this.observers])
|
|
71
|
+
o.markDirty(DIRTY);
|
|
72
|
+
notifyWatchers(this);
|
|
73
|
+
}
|
|
74
|
+
/** Read without subscribing (peek). */
|
|
75
|
+
peek() {
|
|
76
|
+
return this._value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class Computed extends Source {
|
|
80
|
+
_value;
|
|
81
|
+
_error;
|
|
82
|
+
_threw = false;
|
|
83
|
+
_initialized = false;
|
|
84
|
+
state = DIRTY;
|
|
85
|
+
/** Nodes this computation read on its last run. (was Computation.sources) */
|
|
86
|
+
sources = null;
|
|
87
|
+
_fn;
|
|
88
|
+
_equals;
|
|
89
|
+
constructor(fn, options) {
|
|
90
|
+
super();
|
|
91
|
+
this._fn = fn;
|
|
92
|
+
this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
|
|
93
|
+
}
|
|
94
|
+
get() {
|
|
95
|
+
// Cycle guard: a computed reading itself.
|
|
96
|
+
if (G.consumer === this) {
|
|
97
|
+
throw new Error('Signal.Computed cannot read itself');
|
|
98
|
+
}
|
|
99
|
+
this.track();
|
|
100
|
+
this.updateIfNecessary();
|
|
101
|
+
if (this._threw)
|
|
102
|
+
throw this._error;
|
|
103
|
+
return this._value;
|
|
104
|
+
}
|
|
105
|
+
/** Settle: if CHECK, verify sources; if DIRTY (or never run), recompute. */
|
|
106
|
+
updateIfNecessary() {
|
|
107
|
+
if (this.state === CHECK) {
|
|
108
|
+
// Pull each source; if one recomputes to a new value it will have flipped
|
|
109
|
+
// us to DIRTY (markDirty), so stop early.
|
|
110
|
+
if (this.sources) {
|
|
111
|
+
for (const s of this.sources) {
|
|
112
|
+
s.updateIfNecessary();
|
|
113
|
+
if (this.state === DIRTY)
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (this.state === DIRTY || !this._initialized)
|
|
119
|
+
this.recompute();
|
|
120
|
+
this.state = CLEAN;
|
|
121
|
+
}
|
|
122
|
+
recompute() {
|
|
123
|
+
// Drop old edges (we re-collect during the run).
|
|
124
|
+
if (this.sources) {
|
|
125
|
+
for (const s of this.sources)
|
|
126
|
+
s.observers?.delete(this);
|
|
127
|
+
this.sources.clear();
|
|
128
|
+
}
|
|
129
|
+
const prevConsumer = G.consumer;
|
|
130
|
+
const prevUntracked = G.untracked;
|
|
131
|
+
G.consumer = this;
|
|
132
|
+
G.untracked = false;
|
|
133
|
+
let next;
|
|
134
|
+
let threw = false;
|
|
135
|
+
let err;
|
|
136
|
+
try {
|
|
137
|
+
next = this._fn();
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
threw = true;
|
|
141
|
+
err = e;
|
|
142
|
+
next = this._value;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
G.consumer = prevConsumer;
|
|
146
|
+
G.untracked = prevUntracked;
|
|
147
|
+
}
|
|
148
|
+
const changed = !this._initialized ||
|
|
149
|
+
this._threw !== threw ||
|
|
150
|
+
(threw ? err !== this._error : !this._equals(this._value, next));
|
|
151
|
+
this._initialized = true;
|
|
152
|
+
this._threw = threw;
|
|
153
|
+
this._error = err;
|
|
154
|
+
this._value = next;
|
|
155
|
+
if (changed) {
|
|
156
|
+
this.version++;
|
|
157
|
+
// Our value moved → observers that were merely CHECK must now recompute.
|
|
158
|
+
if (this.observers)
|
|
159
|
+
for (const o of [...this.observers])
|
|
160
|
+
o.markDirty(DIRTY);
|
|
161
|
+
notifyWatchers(this);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Push a maybe-dirty mark. A direct observer of a changed node gets DIRTY; its
|
|
166
|
+
* own observers get CHECK (recurse) — stopping where already marked, so the
|
|
167
|
+
* push is O(newly-dirtied) and never recomputes.
|
|
168
|
+
*/
|
|
169
|
+
markDirty(level) {
|
|
170
|
+
if (this.state >= level)
|
|
171
|
+
return;
|
|
172
|
+
const wasClean = this.state === CLEAN;
|
|
173
|
+
this.state = level;
|
|
174
|
+
if (wasClean) {
|
|
175
|
+
if (this.observers)
|
|
176
|
+
for (const o of [...this.observers])
|
|
177
|
+
o.markDirty(CHECK);
|
|
178
|
+
notifyWatchers(this);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
peek() {
|
|
182
|
+
this.updateIfNecessary();
|
|
183
|
+
if (this._threw)
|
|
184
|
+
throw this._error;
|
|
185
|
+
return this._value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function notifyWatchers(node) {
|
|
189
|
+
if (!node.watchers)
|
|
190
|
+
return;
|
|
191
|
+
for (const w of [...node.watchers])
|
|
192
|
+
w._notify(node);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Signal.subtle.Watcher — the only eager observation primitive. Its callback fires
|
|
196
|
+
* when a watched signal MAY have changed; the callback MUST NOT read or write
|
|
197
|
+
* signals. Effects (L1) are built on this + a scheduler.
|
|
198
|
+
*/
|
|
199
|
+
export class Watcher {
|
|
200
|
+
_notifyFn;
|
|
201
|
+
_watched = new Set();
|
|
202
|
+
_pending = new Set();
|
|
203
|
+
constructor(notify) {
|
|
204
|
+
this._notifyFn = notify;
|
|
205
|
+
}
|
|
206
|
+
watch(...nodes) {
|
|
207
|
+
for (const n of nodes) {
|
|
208
|
+
this._watched.add(n);
|
|
209
|
+
(n.watchers ??= new Set()).add(this);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
unwatch(...nodes) {
|
|
213
|
+
for (const n of nodes) {
|
|
214
|
+
this._watched.delete(n);
|
|
215
|
+
n.watchers?.delete(this);
|
|
216
|
+
this._pending.delete(n);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Watched nodes that may have changed since the last getPending(). */
|
|
220
|
+
getPending() {
|
|
221
|
+
const out = [...this._pending];
|
|
222
|
+
this._pending.clear();
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
/** @internal — a watched node became non-clean. Edge-triggered: notify once. */
|
|
226
|
+
_notify(node) {
|
|
227
|
+
if (!this._watched.has(node) || this._pending.has(node))
|
|
228
|
+
return;
|
|
229
|
+
const wasEmpty = this._pending.size === 0;
|
|
230
|
+
this._pending.add(node);
|
|
231
|
+
if (wasEmpty)
|
|
232
|
+
this._notifyFn();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Signal.subtle.untrack — run `fn` without subscribing the current consumer. */
|
|
236
|
+
export function untrack(fn) {
|
|
237
|
+
if (G.untracked)
|
|
238
|
+
return fn();
|
|
239
|
+
G.untracked = true;
|
|
240
|
+
try {
|
|
241
|
+
return fn();
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
G.untracked = false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Signal.subtle.currentComputed — the computed currently evaluating, or null. */
|
|
248
|
+
export function currentComputed() {
|
|
249
|
+
return G.consumer;
|
|
250
|
+
}
|
|
251
|
+
/** Whether a node currently has observers/watchers (Signal.subtle.hasSinks). */
|
|
252
|
+
export function hasSinks(node) {
|
|
253
|
+
return !!(node.observers && node.observers.size) || !!(node.watchers && node.watchers.size);
|
|
254
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var y="__fluixi_signal_next_state__",b=s=>{typeof queueMicrotask=="function"?queueMicrotask(s):Promise.resolve().then(s)},r=globalThis[y]??={consumer:null,untracked:!1,owner:null,queue:[],scheduled:!1,flushing:!1,batchDepth:0,hostSchedule:b,onSuspend:null,onError:null};var d=0,f=1,a=2,h=class{constructor(){this.observers=null;this.watchers=null;this.version=0}updateIfNecessary(){}track(){if(r.untracked)return;let e=r.consumer;e&&((this.observers??=new Set).add(e),(e.sources??=new Set).add(this))}},v=class extends h{constructor(e,t){super(),this._value=e,this._equals=t?.equals===!1?()=>!1:t?.equals??Object.is}get(){return this.track(),this._value}set(e){if(!this._equals(this._value,e)){if(this._value=e,this.version++,this.observers)for(let t of[...this.observers])t.markDirty(a);l(this)}}peek(){return this._value}},p=class extends h{constructor(t,i){super();this._threw=!1;this._initialized=!1;this.state=a;this.sources=null;this._fn=t,this._equals=i?.equals===!1?()=>!1:i?.equals??Object.is}get(){if(r.consumer===this)throw new Error("Signal.Computed cannot read itself");if(this.track(),this.updateIfNecessary(),this._threw)throw this._error;return this._value}updateIfNecessary(){if(this.state===f&&this.sources){for(let t of this.sources)if(t.updateIfNecessary(),this.state===a)break}(this.state===a||!this._initialized)&&this.recompute(),this.state=d}recompute(){if(this.sources){for(let n of this.sources)n.observers?.delete(this);this.sources.clear()}let t=r.consumer,i=r.untracked;r.consumer=this,r.untracked=!1;let o,u=!1,c;try{o=this._fn()}catch(n){u=!0,c=n,o=this._value}finally{r.consumer=t,r.untracked=i}let w=!this._initialized||this._threw!==u||(u?c!==this._error:!this._equals(this._value,o));if(this._initialized=!0,this._threw=u,this._error=c,this._value=o,w){if(this.version++,this.observers)for(let n of[...this.observers])n.markDirty(a);l(this)}}markDirty(t){if(this.state>=t)return;let i=this.state===d;if(this.state=t,i){if(this.observers)for(let o of[...this.observers])o.markDirty(f);l(this)}}peek(){if(this.updateIfNecessary(),this._threw)throw this._error;return this._value}};function l(s){if(s.watchers)for(let e of[...s.watchers])e._notify(s)}var _=class{constructor(e){this._watched=new Set;this._pending=new Set;this._notifyFn=e}watch(...e){for(let t of e)this._watched.add(t),(t.watchers??=new Set).add(this)}unwatch(...e){for(let t of e)this._watched.delete(t),t.watchers?.delete(this),this._pending.delete(t)}getPending(){let e=[...this._pending];return this._pending.clear(),e}_notify(e){if(!this._watched.has(e)||this._pending.has(e))return;let t=this._pending.size===0;this._pending.add(e),t&&this._notifyFn()}};function S(s){if(r.untracked)return s();r.untracked=!0;try{return s()}finally{r.untracked=!1}}function m(){return r.consumer}function g(s){return!!(s.observers&&s.observers.size)||!!(s.watchers&&s.watchers.size)}export{p as Computed,v as State,_ as Watcher,m as currentComputed,g as hasSinks,S as untrack};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var w=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var E=(n,e)=>{for(var t in e)w(n,t,{get:e[t],enumerable:!0})},N=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of P(e))!A.call(n,o)&&o!==t&&w(n,o,{get:()=>e[o],enumerable:!(s=D(e,o))||s.enumerable});return n};var R=n=>N(w({},"__esModule",{value:!0}),n);var H={};E(H,{createDebounce:()=>V,createDeferred:()=>L,createSelector:()=>U});module.exports=R(H);var M="__fluixi_signal_next_state__",I=n=>{typeof queueMicrotask=="function"?queueMicrotask(n):Promise.resolve().then(n)},r=globalThis[M]??={consumer:null,untracked:!1,owner:null,queue:[],scheduled:!1,flushing:!1,batchDepth:0,hostSchedule:I,onSuspend:null,onError:null};var _=0,k=1,c=2,f=class{constructor(){this.observers=null;this.watchers=null;this.version=0}updateIfNecessary(){}track(){if(r.untracked)return;let e=r.consumer;e&&((this.observers??=new Set).add(e),(e.sources??=new Set).add(this))}},p=class extends f{constructor(e,t){super(),this._value=e,this._equals=t?.equals===!1?()=>!1:t?.equals??Object.is}get(){return this.track(),this._value}set(e){if(!this._equals(this._value,e)){if(this._value=e,this.version++,this.observers)for(let t of[...this.observers])t.markDirty(c);v(this)}}peek(){return this._value}},d=class extends f{constructor(t,s){super();this._threw=!1;this._initialized=!1;this.state=c;this.sources=null;this._fn=t,this._equals=s?.equals===!1?()=>!1:s?.equals??Object.is}get(){if(r.consumer===this)throw new Error("Signal.Computed cannot read itself");if(this.track(),this.updateIfNecessary(),this._threw)throw this._error;return this._value}updateIfNecessary(){if(this.state===k&&this.sources){for(let t of this.sources)if(t.updateIfNecessary(),this.state===c)break}(this.state===c||!this._initialized)&&this.recompute(),this.state=_}recompute(){if(this.sources){for(let a of this.sources)a.observers?.delete(this);this.sources.clear()}let t=r.consumer,s=r.untracked;r.consumer=this,r.untracked=!1;let o,i=!1,u;try{o=this._fn()}catch(a){i=!0,u=a,o=this._value}finally{r.consumer=t,r.untracked=s}let l=!this._initialized||this._threw!==i||(i?u!==this._error:!this._equals(this._value,o));if(this._initialized=!0,this._threw=i,this._error=u,this._value=o,l){if(this.version++,this.observers)for(let a of[...this.observers])a.markDirty(c);v(this)}}markDirty(t){if(this.state>=t)return;let s=this.state===_;if(this.state=t,s){if(this.observers)for(let o of[...this.observers])o.markDirty(k);v(this)}}peek(){if(this.updateIfNecessary(),this._threw)throw this._error;return this._value}};function v(n){if(n.watchers)for(let e of[...n.watchers])e._notify(n)}var h=class{constructor(e){this._watched=new Set;this._pending=new Set;this._notifyFn=e}watch(...e){for(let t of e)this._watched.add(t),(t.watchers??=new Set).add(this)}unwatch(...e){for(let t of e)this._watched.delete(t),t.watchers?.delete(this),this._pending.delete(t)}getPending(){let e=[...this._pending];return this._pending.clear(),e}_notify(e){if(!this._watched.has(e)||this._pending.has(e))return;let t=this._pending.size===0;this._pending.add(e),t&&this._notifyFn()}};function j(n){return{cleanups:null,owned:null,parent:n,contexts:null}}function y(n,e){let t=r.owner;r.owner=n;try{return e()}finally{r.owner=t}}function b(n){r.owner&&(r.owner.cleanups??=[]).push(n)}function T(n){if(n.owned){for(let e=n.owned.length-1;e>=0;e--)T(n.owned[e]);n.owned=null}if(n.cleanups){for(let e=n.cleanups.length-1;e>=0;e--)n.cleanups[e]();n.cleanups=null}}function O(n){r.hostSchedule=n??(e=>{Promise.resolve().then(e)})}function W(n){n.queued||(n.queued=!0,r.queue.push(n),!r.scheduled&&r.batchDepth===0&&(r.scheduled=!0,r.hostSchedule(g)))}function g(){if(r.scheduled=!1,r.flushing)return;r.flushing=!0;let n=0;try{for(;r.queue.length;){if(++n>1e5)throw new Error("[fluixi] effect flush did not settle (cycle?)");let e=r.queue;r.queue=[];for(let t of e)t.queued=!1,t.disposed||t.run()}}finally{r.flushing=!1}}function q(){r.batchDepth===0&&g()}var m=class{constructor(e){this.queued=!1;this.disposed=!1;this.owner=j(r.owner),r.owner&&(r.owner.cleanups??=[]).push(()=>this.dispose()),this.computed=new d(()=>{T(this.owner),y(this.owner,()=>{let t=e();typeof t=="function"&&(this.owner.cleanups??=[]).push(t)})}),this.watcher=new h(()=>W(this)),this.run(),this.watcher.watch(this.computed)}run(){if(!this.disposed){this.watcher.getPending();try{this.computed.get()}catch(e){if(e&&typeof e.then=="function"&&r.onSuspend){r.onSuspend(e,this.owner);return}if(r.onError&&r.onError(e,this.owner))return;throw e}}}dispose(){this.disposed||(this.disposed=!0,this.watcher.unwatch(this.computed),T(this.owner))}};function C(n){let e=new m(n);return()=>e.dispose()}O(()=>{});var G=Symbol.for("signal");var F=Symbol.for("signal-node");function x(n,e){let t=new p(n,{equals:e?.equals}),s=(()=>t.get()),o=(i=>{let u=typeof i=="function"?i(t.peek()):i;return t.set(u),q(),u});return Object.defineProperty(s,G,{value:!0,enumerable:!0}),Object.defineProperty(s,F,{value:{get value(){return t.peek()},get version(){return t.version}},enumerable:!1}),[s,o]}function S(n,e){let t=e;return C(()=>{t=n(t)})}function U(n,e=(t,s)=>t===s){let t=new Map;return S(()=>{let s=n();for(let[o,i]of t){let[u,l]=i,a=e(o,s);u()!==a&&l(a)}}),s=>{let o=t.get(s);if(!o){let[i,u]=x(!1);o=[i,u],t.set(s,o);let l=n();u(e(s,l))}return o[0]()}}function L(n,e){let[t,s]=x(n(),e);return S(()=>{let o=n();e?.timeoutMs?setTimeout(()=>s(()=>o),e.timeoutMs):typeof requestIdleCallback=="function"?requestIdleCallback(()=>s(()=>o)):Promise.resolve().then(()=>s(()=>o))}),t}function V(n,e){let t,s=(...o)=>{t!==void 0&&clearTimeout(t),t=setTimeout(()=>{if(t=void 0,o.length>0){let i=o[o.length-1];typeof i=="object"&&i&&!i.tagName&&i.target&&i.target.tagName&&(i=i.target),i&&typeof i=="object"&&i.tagName&&(i.tagName.toLowerCase()==="input"||i.tagName.toLowerCase()==="textarea")&&i.focus()}n(...o)},e)};return b(()=>{t!==void 0&&clearTimeout(t)}),s}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Accessor, EqualityFn } from '../signal.js';
|
|
2
|
+
/** Keyed selector: `selector(key)` is a fine-grained boolean that flips only when its own key's match changes. */
|
|
3
|
+
export declare function createSelector<T, U = T>(source: () => T, fn?: (key: U, value: T) => boolean): (key: U) => boolean;
|
|
4
|
+
/** A signal that tracks `source` but applies updates on idle/timeout, keeping the current UI responsive. */
|
|
5
|
+
export declare function createDeferred<T>(source: Accessor<T>, options?: {
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
equals?: false | EqualityFn<T>;
|
|
8
|
+
}): Accessor<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Debounce wrapper — delays invoking `fn` until `delay` ms after the last call.
|
|
11
|
+
* Pending timers are cancelled when the owning scope disposes (via onCleanup).
|
|
12
|
+
*/
|
|
13
|
+
export declare function createDebounce<T extends (...args: any[]) => any>(fn: T, delay: number): T;
|
|
14
|
+
//# sourceMappingURL=derived.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/derived.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAA8B,MAAM,cAAc,CAAC;AAErF,kHAAkH;AAClH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EACrC,MAAM,EAAE,MAAM,CAAC,EACf,EAAE,GAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAA6C,GACtE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAqBrB;AAED,4GAA4G;AAC5G,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;CAAE,GAC/D,QAAQ,CAAC,CAAC,CAAC,CAUb;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAiCzF"}
|