zero-query 1.0.9 → 1.2.0
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 -21
- package/README.md +2 -0
- package/cli/args.js +33 -33
- package/cli/commands/build-api.js +443 -0
- package/cli/commands/build.js +254 -216
- package/cli/commands/bundle.js +1228 -1183
- package/cli/commands/create.js +137 -121
- package/cli/commands/dev/devtools/index.js +56 -56
- package/cli/commands/dev/devtools/js/components.js +49 -49
- package/cli/commands/dev/devtools/js/core.js +423 -423
- package/cli/commands/dev/devtools/js/elements.js +421 -421
- package/cli/commands/dev/devtools/js/network.js +166 -166
- package/cli/commands/dev/devtools/js/performance.js +73 -73
- package/cli/commands/dev/devtools/js/router.js +105 -105
- package/cli/commands/dev/devtools/js/source.js +132 -132
- package/cli/commands/dev/devtools/js/stats.js +35 -35
- package/cli/commands/dev/devtools/js/tabs.js +79 -79
- package/cli/commands/dev/devtools/panel.html +95 -95
- package/cli/commands/dev/devtools/styles.css +244 -244
- package/cli/commands/dev/index.js +107 -107
- package/cli/commands/dev/logger.js +75 -75
- package/cli/commands/dev/overlay.js +858 -858
- package/cli/commands/dev/server.js +220 -167
- package/cli/commands/dev/validator.js +94 -94
- package/cli/commands/dev/watcher.js +172 -172
- package/cli/help.js +114 -112
- package/cli/index.js +52 -52
- package/cli/scaffold/default/LICENSE +21 -21
- package/cli/scaffold/default/app/app.js +207 -207
- package/cli/scaffold/default/app/components/about.js +201 -201
- package/cli/scaffold/default/app/components/api-demo.js +143 -143
- package/cli/scaffold/default/app/components/contact-card.js +231 -231
- package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
- package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
- package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
- package/cli/scaffold/default/app/components/counter.js +127 -127
- package/cli/scaffold/default/app/components/home.js +249 -249
- package/cli/scaffold/default/app/components/not-found.js +16 -16
- package/cli/scaffold/default/app/components/playground/playground.css +115 -115
- package/cli/scaffold/default/app/components/playground/playground.html +161 -161
- package/cli/scaffold/default/app/components/playground/playground.js +116 -116
- package/cli/scaffold/default/app/components/todos.js +225 -225
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
- package/cli/scaffold/default/app/routes.js +15 -15
- package/cli/scaffold/default/app/store.js +101 -101
- package/cli/scaffold/default/global.css +552 -552
- package/cli/scaffold/default/index.html +99 -99
- package/cli/scaffold/minimal/app/app.js +85 -85
- package/cli/scaffold/minimal/app/components/about.js +68 -68
- package/cli/scaffold/minimal/app/components/counter.js +122 -122
- package/cli/scaffold/minimal/app/components/home.js +68 -68
- package/cli/scaffold/minimal/app/components/not-found.js +16 -16
- package/cli/scaffold/minimal/app/routes.js +9 -9
- package/cli/scaffold/minimal/app/store.js +36 -36
- package/cli/scaffold/minimal/global.css +300 -300
- package/cli/scaffold/minimal/index.html +44 -44
- package/cli/scaffold/ssr/app/app.js +41 -41
- package/cli/scaffold/ssr/app/components/about.js +55 -55
- package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
- package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
- package/cli/scaffold/ssr/app/components/home.js +37 -37
- package/cli/scaffold/ssr/app/components/not-found.js +15 -15
- package/cli/scaffold/ssr/app/routes.js +8 -8
- package/cli/scaffold/ssr/global.css +228 -228
- package/cli/scaffold/ssr/index.html +37 -37
- package/cli/scaffold/ssr/package.json +8 -8
- package/cli/scaffold/ssr/server/data/posts.js +144 -144
- package/cli/scaffold/ssr/server/index.js +213 -213
- package/cli/scaffold/webrtc/app/app.js +11 -0
- package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
- package/cli/scaffold/webrtc/app/lib/room.js +252 -0
- package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
- package/cli/scaffold/webrtc/global.css +250 -0
- package/cli/scaffold/webrtc/index.html +21 -0
- package/cli/utils.js +305 -287
- package/dist/API.md +7264 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +10313 -6252
- package/dist/zquery.min.js +8 -601
- package/index.d.ts +570 -365
- package/index.js +311 -232
- package/package.json +76 -69
- package/src/component.js +1709 -1454
- package/src/core.js +921 -921
- package/src/diff.js +497 -497
- package/src/errors.js +209 -209
- package/src/expression.js +922 -922
- package/src/http.js +242 -242
- package/src/package.json +1 -1
- package/src/reactive.js +255 -254
- package/src/router.js +843 -773
- package/src/ssr.js +418 -418
- package/src/store.js +318 -272
- package/src/utils.js +515 -515
- package/src/webrtc/e2ee.js +351 -0
- package/src/webrtc/errors.js +116 -0
- package/src/webrtc/ice.js +301 -0
- package/src/webrtc/index.js +131 -0
- package/src/webrtc/joinToken.js +119 -0
- package/src/webrtc/observe.js +172 -0
- package/src/webrtc/peer.js +351 -0
- package/src/webrtc/reactive.js +268 -0
- package/src/webrtc/room.js +625 -0
- package/src/webrtc/sdp.js +302 -0
- package/src/webrtc/sfu/index.js +43 -0
- package/src/webrtc/sfu/livekit.js +131 -0
- package/src/webrtc/sfu/mediasoup.js +150 -0
- package/src/webrtc/signaling.js +373 -0
- package/src/webrtc/turn.js +237 -0
- package/tests/_helpers/webrtcFakes.js +289 -0
- package/tests/audit.test.js +4158 -4158
- package/tests/cli.test.js +1136 -1023
- package/tests/compare.test.js +497 -0
- package/tests/component.test.js +3969 -3938
- package/tests/core.test.js +1910 -1910
- package/tests/dev-server.test.js +489 -0
- package/tests/diff.test.js +1416 -1416
- package/tests/docs.test.js +1664 -0
- package/tests/electron-features.test.js +864 -0
- package/tests/errors.test.js +619 -619
- package/tests/expression.test.js +1056 -1056
- package/tests/http.test.js +648 -648
- package/tests/reactive.test.js +819 -819
- package/tests/router.test.js +2327 -2327
- package/tests/ssr.test.js +870 -870
- package/tests/store.test.js +830 -830
- package/tests/test-minifier.js +153 -153
- package/tests/test-ssr.js +27 -27
- package/tests/utils.test.js +1377 -1377
- package/tests/webrtc/e2ee.test.js +283 -0
- package/tests/webrtc/ice.test.js +202 -0
- package/tests/webrtc/joinToken.test.js +89 -0
- package/tests/webrtc/observe.test.js +111 -0
- package/tests/webrtc/peer.test.js +373 -0
- package/tests/webrtc/reactive.test.js +235 -0
- package/tests/webrtc/room.test.js +406 -0
- package/tests/webrtc/sdp.test.js +151 -0
- package/tests/webrtc/sfu-livekit.test.js +119 -0
- package/tests/webrtc/sfu.test.js +160 -0
- package/tests/webrtc/signaling.test.js +251 -0
- package/tests/webrtc/turn.test.js +256 -0
- package/types/collection.d.ts +383 -383
- package/types/component.d.ts +186 -186
- package/types/errors.d.ts +135 -135
- package/types/http.d.ts +92 -92
- package/types/misc.d.ts +201 -201
- package/types/reactive.d.ts +98 -98
- package/types/router.d.ts +190 -190
- package/types/ssr.d.ts +102 -102
- package/types/store.d.ts +146 -145
- package/types/utils.d.ts +245 -245
- package/types/webrtc.d.ts +653 -0
package/src/reactive.js
CHANGED
|
@@ -1,254 +1,255 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* zQuery Reactive - Proxy-based deep reactivity system
|
|
3
|
-
*
|
|
4
|
-
* Creates observable objects that trigger callbacks on mutation.
|
|
5
|
-
* Used internally by components and store for auto-updates.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { reportError, ErrorCode } from './errors.js';
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// Deep reactive proxy
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
export function reactive(target, onChange, _path = '') {
|
|
14
|
-
if (typeof target !== 'object' || target === null) return target;
|
|
15
|
-
if (typeof onChange !== 'function') {
|
|
16
|
-
reportError(ErrorCode.REACTIVE_CALLBACK, 'reactive() onChange must be a function', { received: typeof onChange });
|
|
17
|
-
onChange = () => {};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const proxyCache = new WeakMap();
|
|
21
|
-
|
|
22
|
-
const handler = {
|
|
23
|
-
get(obj, key) {
|
|
24
|
-
if (key === '__isReactive') return true;
|
|
25
|
-
if (key === '__raw') return obj;
|
|
26
|
-
|
|
27
|
-
const value = obj[key];
|
|
28
|
-
if (typeof value === 'object' && value !== null) {
|
|
29
|
-
// Return cached proxy or create new one
|
|
30
|
-
if (proxyCache.has(value)) return proxyCache.get(value);
|
|
31
|
-
const childProxy = new Proxy(value, handler);
|
|
32
|
-
proxyCache.set(value, childProxy);
|
|
33
|
-
return childProxy;
|
|
34
|
-
}
|
|
35
|
-
return value;
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
set(obj, key, value) {
|
|
39
|
-
const old = obj[key];
|
|
40
|
-
if (old === value) return true;
|
|
41
|
-
obj[key] = value;
|
|
42
|
-
// Invalidate proxy cache for the old value (it may have been replaced)
|
|
43
|
-
if (old && typeof old === 'object') proxyCache.delete(old);
|
|
44
|
-
try {
|
|
45
|
-
onChange(key, value, old);
|
|
46
|
-
} catch (err) {
|
|
47
|
-
reportError(ErrorCode.REACTIVE_CALLBACK, `Reactive onChange threw for key "${String(key)}"`, { key, value, old }, err);
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
deleteProperty(obj, key) {
|
|
53
|
-
const old = obj[key];
|
|
54
|
-
delete obj[key];
|
|
55
|
-
if (old && typeof old === 'object') proxyCache.delete(old);
|
|
56
|
-
try {
|
|
57
|
-
onChange(key, undefined, old);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
reportError(ErrorCode.REACTIVE_CALLBACK, `Reactive onChange threw for key "${String(key)}"`, { key, old }, err);
|
|
60
|
-
}
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
return new Proxy(target, handler);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Signal - lightweight reactive primitive (inspired by Solid/Preact signals)
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
export class Signal {
|
|
73
|
-
constructor(value) {
|
|
74
|
-
this._value = value;
|
|
75
|
-
this._subscribers = new Set();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
get value() {
|
|
79
|
-
// Track dependency if there's an active effect
|
|
80
|
-
if (Signal._activeEffect) {
|
|
81
|
-
this._subscribers.add(Signal._activeEffect);
|
|
82
|
-
// Record this signal in the effect's dependency set for proper cleanup
|
|
83
|
-
if (Signal._activeEffect._deps) {
|
|
84
|
-
Signal._activeEffect._deps.add(this);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return this._value;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
set value(newVal) {
|
|
91
|
-
if (this._value === newVal) return;
|
|
92
|
-
this._value = newVal;
|
|
93
|
-
this._notify();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
peek() { return this._value; }
|
|
97
|
-
|
|
98
|
-
_notify() {
|
|
99
|
-
if (Signal._batching) {
|
|
100
|
-
Signal._batchQueue.add(this);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
// Snapshot subscribers before iterating - a subscriber might modify
|
|
104
|
-
// the set (e.g., an effect re-running, adding itself back)
|
|
105
|
-
const subs = [...this._subscribers];
|
|
106
|
-
for (let i = 0; i < subs.length; i++) {
|
|
107
|
-
try { subs[i](); }
|
|
108
|
-
catch (err) {
|
|
109
|
-
reportError(ErrorCode.SIGNAL_CALLBACK, 'Signal subscriber threw', { signal: this }, err);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
subscribe(fn) {
|
|
115
|
-
this._subscribers.add(fn);
|
|
116
|
-
return () => this._subscribers.delete(fn);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
toString() { return String(this._value); }
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Active effect tracking
|
|
123
|
-
Signal._activeEffect = null;
|
|
124
|
-
// Batch state
|
|
125
|
-
Signal._batching = false;
|
|
126
|
-
Signal._batchQueue = new Set();
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Create a signal
|
|
130
|
-
* @param {*} initial - initial value
|
|
131
|
-
* @returns {Signal}
|
|
132
|
-
*/
|
|
133
|
-
export function signal(initial) {
|
|
134
|
-
return new Signal(initial);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Create a computed signal (derived from other signals)
|
|
139
|
-
* @param {Function} fn - computation function
|
|
140
|
-
* @returns {Signal}
|
|
141
|
-
*/
|
|
142
|
-
export function computed(fn) {
|
|
143
|
-
const s = new Signal(undefined);
|
|
144
|
-
effect(() => {
|
|
145
|
-
const v = fn();
|
|
146
|
-
if (v !== s._value) {
|
|
147
|
-
s._value = v;
|
|
148
|
-
s._notify();
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
return s;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Create a side-effect that auto-tracks signal dependencies.
|
|
156
|
-
* Returns a dispose function that removes the effect from all
|
|
157
|
-
* signals it subscribed to - prevents memory leaks.
|
|
158
|
-
*
|
|
159
|
-
* @param {Function} fn - effect function
|
|
160
|
-
* @returns {Function} - dispose function
|
|
161
|
-
*/
|
|
162
|
-
export function effect(fn) {
|
|
163
|
-
const execute = () => {
|
|
164
|
-
// Clean up old subscriptions before re-running so stale
|
|
165
|
-
// dependencies from a previous run are properly removed
|
|
166
|
-
if (execute._deps) {
|
|
167
|
-
for (const sig of execute._deps) {
|
|
168
|
-
sig._subscribers.delete(execute);
|
|
169
|
-
}
|
|
170
|
-
execute._deps.clear();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
Signal._activeEffect = execute;
|
|
174
|
-
try { fn(); }
|
|
175
|
-
catch (err) {
|
|
176
|
-
reportError(ErrorCode.EFFECT_EXEC, 'Effect function threw', {}, err);
|
|
177
|
-
}
|
|
178
|
-
finally { Signal._activeEffect = null; }
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Track which signals this effect reads from
|
|
182
|
-
execute._deps = new Set();
|
|
183
|
-
|
|
184
|
-
execute();
|
|
185
|
-
return () => {
|
|
186
|
-
// Dispose: remove this effect from every signal it subscribed to
|
|
187
|
-
if (execute._deps) {
|
|
188
|
-
for (const sig of execute._deps) {
|
|
189
|
-
sig._subscribers.delete(execute);
|
|
190
|
-
}
|
|
191
|
-
execute._deps.clear();
|
|
192
|
-
}
|
|
193
|
-
// Don't clobber _activeEffect - another effect may be running
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
// batch() - defer signal notifications until the batch completes
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Batch multiple signal writes - subscribers and effects fire once at the end.
|
|
204
|
-
* @param {Function} fn - function that performs signal writes
|
|
205
|
-
*/
|
|
206
|
-
export function batch(fn) {
|
|
207
|
-
if (Signal._batching) {
|
|
208
|
-
// Already inside a batch, just run
|
|
209
|
-
fn();
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Signal.
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
fn();
|
|
216
|
-
} finally {
|
|
217
|
-
Signal._batching = false;
|
|
218
|
-
// Collect all unique subscribers across all queued signals
|
|
219
|
-
// so each subscriber/effect runs exactly once
|
|
220
|
-
const subs = new Set();
|
|
221
|
-
for (const sig of Signal._batchQueue) {
|
|
222
|
-
for (const sub of sig._subscribers) {
|
|
223
|
-
subs.add(sub);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
Signal._batchQueue.clear();
|
|
227
|
-
for (const sub of subs) {
|
|
228
|
-
try { sub(); }
|
|
229
|
-
catch (err) {
|
|
230
|
-
reportError(ErrorCode.SIGNAL_CALLBACK, 'Signal subscriber threw', {}, err);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
*
|
|
244
|
-
* @
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
Signal._activeEffect
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* zQuery Reactive - Proxy-based deep reactivity system
|
|
3
|
+
*
|
|
4
|
+
* Creates observable objects that trigger callbacks on mutation.
|
|
5
|
+
* Used internally by components and store for auto-updates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { reportError, ErrorCode } from './errors.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Deep reactive proxy
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export function reactive(target, onChange, _path = '') {
|
|
14
|
+
if (typeof target !== 'object' || target === null) return target;
|
|
15
|
+
if (typeof onChange !== 'function') {
|
|
16
|
+
reportError(ErrorCode.REACTIVE_CALLBACK, 'reactive() onChange must be a function', { received: typeof onChange });
|
|
17
|
+
onChange = () => {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const proxyCache = new WeakMap();
|
|
21
|
+
|
|
22
|
+
const handler = {
|
|
23
|
+
get(obj, key) {
|
|
24
|
+
if (key === '__isReactive') return true;
|
|
25
|
+
if (key === '__raw') return obj;
|
|
26
|
+
|
|
27
|
+
const value = obj[key];
|
|
28
|
+
if (typeof value === 'object' && value !== null) {
|
|
29
|
+
// Return cached proxy or create new one
|
|
30
|
+
if (proxyCache.has(value)) return proxyCache.get(value);
|
|
31
|
+
const childProxy = new Proxy(value, handler);
|
|
32
|
+
proxyCache.set(value, childProxy);
|
|
33
|
+
return childProxy;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
set(obj, key, value) {
|
|
39
|
+
const old = obj[key];
|
|
40
|
+
if (old === value) return true;
|
|
41
|
+
obj[key] = value;
|
|
42
|
+
// Invalidate proxy cache for the old value (it may have been replaced)
|
|
43
|
+
if (old && typeof old === 'object') proxyCache.delete(old);
|
|
44
|
+
try {
|
|
45
|
+
onChange(key, value, old);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
reportError(ErrorCode.REACTIVE_CALLBACK, `Reactive onChange threw for key "${String(key)}"`, { key, value, old }, err);
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
deleteProperty(obj, key) {
|
|
53
|
+
const old = obj[key];
|
|
54
|
+
delete obj[key];
|
|
55
|
+
if (old && typeof old === 'object') proxyCache.delete(old);
|
|
56
|
+
try {
|
|
57
|
+
onChange(key, undefined, old);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
reportError(ErrorCode.REACTIVE_CALLBACK, `Reactive onChange threw for key "${String(key)}"`, { key, old }, err);
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return new Proxy(target, handler);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Signal - lightweight reactive primitive (inspired by Solid/Preact signals)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
export class Signal {
|
|
73
|
+
constructor(value) {
|
|
74
|
+
this._value = value;
|
|
75
|
+
this._subscribers = new Set();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get value() {
|
|
79
|
+
// Track dependency if there's an active effect
|
|
80
|
+
if (Signal._activeEffect) {
|
|
81
|
+
this._subscribers.add(Signal._activeEffect);
|
|
82
|
+
// Record this signal in the effect's dependency set for proper cleanup
|
|
83
|
+
if (Signal._activeEffect._deps) {
|
|
84
|
+
Signal._activeEffect._deps.add(this);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return this._value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set value(newVal) {
|
|
91
|
+
if (this._value === newVal) return;
|
|
92
|
+
this._value = newVal;
|
|
93
|
+
this._notify();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
peek() { return this._value; }
|
|
97
|
+
|
|
98
|
+
_notify() {
|
|
99
|
+
if (Signal._batching) {
|
|
100
|
+
Signal._batchQueue.add(this);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Snapshot subscribers before iterating - a subscriber might modify
|
|
104
|
+
// the set (e.g., an effect re-running, adding itself back)
|
|
105
|
+
const subs = [...this._subscribers];
|
|
106
|
+
for (let i = 0; i < subs.length; i++) {
|
|
107
|
+
try { subs[i](); }
|
|
108
|
+
catch (err) {
|
|
109
|
+
reportError(ErrorCode.SIGNAL_CALLBACK, 'Signal subscriber threw', { signal: this }, err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
subscribe(fn) {
|
|
115
|
+
this._subscribers.add(fn);
|
|
116
|
+
return () => this._subscribers.delete(fn);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
toString() { return String(this._value); }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Active effect tracking
|
|
123
|
+
Signal._activeEffect = null;
|
|
124
|
+
// Batch state
|
|
125
|
+
Signal._batching = false;
|
|
126
|
+
Signal._batchQueue = new Set();
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create a signal
|
|
130
|
+
* @param {*} initial - initial value
|
|
131
|
+
* @returns {Signal}
|
|
132
|
+
*/
|
|
133
|
+
export function signal(initial) {
|
|
134
|
+
return new Signal(initial);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create a computed signal (derived from other signals)
|
|
139
|
+
* @param {Function} fn - computation function
|
|
140
|
+
* @returns {Signal}
|
|
141
|
+
*/
|
|
142
|
+
export function computed(fn) {
|
|
143
|
+
const s = new Signal(undefined);
|
|
144
|
+
effect(() => {
|
|
145
|
+
const v = fn();
|
|
146
|
+
if (v !== s._value) {
|
|
147
|
+
s._value = v;
|
|
148
|
+
s._notify();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return s;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create a side-effect that auto-tracks signal dependencies.
|
|
156
|
+
* Returns a dispose function that removes the effect from all
|
|
157
|
+
* signals it subscribed to - prevents memory leaks.
|
|
158
|
+
*
|
|
159
|
+
* @param {Function} fn - effect function
|
|
160
|
+
* @returns {Function} - dispose function
|
|
161
|
+
*/
|
|
162
|
+
export function effect(fn) {
|
|
163
|
+
const execute = () => {
|
|
164
|
+
// Clean up old subscriptions before re-running so stale
|
|
165
|
+
// dependencies from a previous run are properly removed
|
|
166
|
+
if (execute._deps) {
|
|
167
|
+
for (const sig of execute._deps) {
|
|
168
|
+
sig._subscribers.delete(execute);
|
|
169
|
+
}
|
|
170
|
+
execute._deps.clear();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
Signal._activeEffect = execute;
|
|
174
|
+
try { fn(); }
|
|
175
|
+
catch (err) {
|
|
176
|
+
reportError(ErrorCode.EFFECT_EXEC, 'Effect function threw', {}, err);
|
|
177
|
+
}
|
|
178
|
+
finally { Signal._activeEffect = null; }
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Track which signals this effect reads from
|
|
182
|
+
execute._deps = new Set();
|
|
183
|
+
|
|
184
|
+
execute();
|
|
185
|
+
return () => {
|
|
186
|
+
// Dispose: remove this effect from every signal it subscribed to
|
|
187
|
+
if (execute._deps) {
|
|
188
|
+
for (const sig of execute._deps) {
|
|
189
|
+
sig._subscribers.delete(execute);
|
|
190
|
+
}
|
|
191
|
+
execute._deps.clear();
|
|
192
|
+
}
|
|
193
|
+
// Don't clobber _activeEffect - another effect may be running
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// batch() - defer signal notifications until the batch completes
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Batch multiple signal writes - subscribers and effects fire once at the end.
|
|
204
|
+
* @param {Function} fn - function that performs signal writes
|
|
205
|
+
*/
|
|
206
|
+
export function batch(fn) {
|
|
207
|
+
if (Signal._batching) {
|
|
208
|
+
// Already inside a batch, just run
|
|
209
|
+
return fn();
|
|
210
|
+
}
|
|
211
|
+
Signal._batching = true;
|
|
212
|
+
Signal._batchQueue.clear();
|
|
213
|
+
let result;
|
|
214
|
+
try {
|
|
215
|
+
result = fn();
|
|
216
|
+
} finally {
|
|
217
|
+
Signal._batching = false;
|
|
218
|
+
// Collect all unique subscribers across all queued signals
|
|
219
|
+
// so each subscriber/effect runs exactly once
|
|
220
|
+
const subs = new Set();
|
|
221
|
+
for (const sig of Signal._batchQueue) {
|
|
222
|
+
for (const sub of sig._subscribers) {
|
|
223
|
+
subs.add(sub);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
Signal._batchQueue.clear();
|
|
227
|
+
for (const sub of subs) {
|
|
228
|
+
try { sub(); }
|
|
229
|
+
catch (err) {
|
|
230
|
+
reportError(ErrorCode.SIGNAL_CALLBACK, 'Signal subscriber threw', {}, err);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// untracked() - read signals without creating dependencies
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Execute a function without tracking signal reads as dependencies.
|
|
244
|
+
* @param {Function} fn - function to run
|
|
245
|
+
* @returns {*} the return value of fn
|
|
246
|
+
*/
|
|
247
|
+
export function untracked(fn) {
|
|
248
|
+
const prev = Signal._activeEffect;
|
|
249
|
+
Signal._activeEffect = null;
|
|
250
|
+
try {
|
|
251
|
+
return fn();
|
|
252
|
+
} finally {
|
|
253
|
+
Signal._activeEffect = prev;
|
|
254
|
+
}
|
|
255
|
+
}
|