zero-query 1.1.1 → 1.2.2

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