sibujs 1.4.0 → 2.0.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.
Files changed (190) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +288 -80
  3. package/dist/browser.d.cts +19 -9
  4. package/dist/browser.d.ts +19 -9
  5. package/dist/browser.js +6 -6
  6. package/dist/build.cjs +1019 -313
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +15 -13
  10. package/dist/cdn.global.js +17 -16
  11. package/dist/chunk-2RA7SHDA.js +65 -0
  12. package/dist/chunk-2UPRY23K.js +80 -0
  13. package/dist/chunk-3JHCYHWN.js +125 -0
  14. package/dist/{chunk-ZWKZCBO6.js → chunk-3LR7GLWQ.js} +154 -33
  15. package/dist/{chunk-3AIRKM3B.js → chunk-3NSGB5JN.js} +115 -34
  16. package/dist/{chunk-3ARAQO7B.js → chunk-52YJLLRO.js} +29 -6
  17. package/dist/chunk-54EDRCEF.js +93 -0
  18. package/dist/chunk-7JDB7I65.js +1327 -0
  19. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  20. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  21. package/dist/{chunk-WR5D4EGH.js → chunk-GTBNNBJ6.js} +14 -2
  22. package/dist/chunk-HB24TBAF.js +121 -0
  23. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  24. package/dist/{chunk-JAKHTMQU.js → chunk-JA6667UN.js} +206 -46
  25. package/dist/{chunk-77L6NL3X.js → chunk-JXMMDLBY.js} +306 -183
  26. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  27. package/dist/{chunk-F3FA4F32.js → chunk-KLRMB5ZS.js} +135 -79
  28. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  29. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  30. package/dist/{chunk-TSOKIX5Z.js → chunk-MIUAXB7K.js} +126 -74
  31. package/dist/{chunk-QWZG56ET.js → chunk-ND2664SF.js} +558 -190
  32. package/dist/{chunk-JCI5M6U6.js → chunk-O2MNQFLP.js} +261 -79
  33. package/dist/{chunk-EWFVA3TJ.js → chunk-R73P76YZ.js} +1 -1
  34. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  35. package/dist/chunk-UCS6AMJ7.js +79 -0
  36. package/dist/{chunk-ZD6OAMTH.js → chunk-VLPPXTYG.js} +90 -35
  37. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  38. package/dist/{contracts-xo5ckdRP.d.cts → contracts-ey_Qh8ef.d.cts} +7 -8
  39. package/dist/{contracts-xo5ckdRP.d.ts → contracts-ey_Qh8ef.d.ts} +7 -8
  40. package/dist/{customElement-D2DJp_xn.d.cts → customElement-CPfIrbvg.d.cts} +18 -9
  41. package/dist/{customElement-D2DJp_xn.d.ts → customElement-CPfIrbvg.d.ts} +18 -9
  42. package/dist/data.cjs +452 -100
  43. package/dist/data.d.cts +20 -2
  44. package/dist/data.d.ts +20 -2
  45. package/dist/data.js +11 -9
  46. package/dist/devtools.cjs +535 -247
  47. package/dist/devtools.d.cts +1 -1
  48. package/dist/devtools.d.ts +1 -1
  49. package/dist/devtools.js +34 -30
  50. package/dist/ecosystem.cjs +499 -143
  51. package/dist/ecosystem.d.cts +13 -11
  52. package/dist/ecosystem.d.ts +13 -11
  53. package/dist/ecosystem.js +12 -11
  54. package/dist/extras.cjs +3639 -1629
  55. package/dist/extras.d.cts +11 -11
  56. package/dist/extras.d.ts +11 -11
  57. package/dist/extras.js +58 -45
  58. package/dist/index.cjs +1023 -313
  59. package/dist/index.d.cts +128 -55
  60. package/dist/index.d.ts +128 -55
  61. package/dist/index.js +28 -16
  62. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  63. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  64. package/dist/motion.cjs +90 -36
  65. package/dist/motion.d.cts +1 -1
  66. package/dist/motion.d.ts +1 -1
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +414 -81
  69. package/dist/patterns.d.cts +53 -20
  70. package/dist/patterns.d.ts +53 -20
  71. package/dist/patterns.js +7 -7
  72. package/dist/performance.cjs +364 -108
  73. package/dist/performance.d.cts +29 -17
  74. package/dist/performance.d.ts +29 -17
  75. package/dist/performance.js +13 -6
  76. package/dist/plugin-D30wlGW5.d.cts +71 -0
  77. package/dist/plugin-D30wlGW5.d.ts +71 -0
  78. package/dist/plugins.cjs +652 -271
  79. package/dist/plugins.d.cts +13 -6
  80. package/dist/plugins.d.ts +13 -6
  81. package/dist/plugins.js +116 -50
  82. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  83. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  84. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  85. package/dist/ssr.cjs +648 -219
  86. package/dist/ssr.d.cts +27 -7
  87. package/dist/ssr.d.ts +27 -7
  88. package/dist/ssr.js +12 -11
  89. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.cts} +9 -1
  90. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.ts} +9 -1
  91. package/dist/testing.cjs +252 -63
  92. package/dist/testing.d.cts +17 -4
  93. package/dist/testing.d.ts +17 -4
  94. package/dist/testing.js +100 -44
  95. package/dist/ui.cjs +576 -168
  96. package/dist/ui.d.cts +13 -16
  97. package/dist/ui.d.ts +13 -16
  98. package/dist/ui.js +20 -17
  99. package/dist/widgets.cjs +1001 -93
  100. package/dist/widgets.d.cts +104 -2
  101. package/dist/widgets.d.ts +104 -2
  102. package/dist/widgets.js +9 -7
  103. package/package.json +8 -2
  104. package/dist/chunk-32DY64NT.js +0 -282
  105. package/dist/chunk-3CRQALYP.js +0 -877
  106. package/dist/chunk-4EI4AG32.js +0 -482
  107. package/dist/chunk-4MYMUBRS.js +0 -21
  108. package/dist/chunk-6HLLIF3K.js +0 -398
  109. package/dist/chunk-6LSNVCS2.js +0 -937
  110. package/dist/chunk-6SA3QQES.js +0 -61
  111. package/dist/chunk-7BF6TK55.js +0 -1097
  112. package/dist/chunk-7TQKR4PP.js +0 -294
  113. package/dist/chunk-7V26P53V.js +0 -712
  114. package/dist/chunk-AZ3ISID5.js +0 -298
  115. package/dist/chunk-B7SWRFUT.js +0 -332
  116. package/dist/chunk-BGN5ZMP4.js +0 -26
  117. package/dist/chunk-BTU3TJDS.js +0 -365
  118. package/dist/chunk-BW3WT46K.js +0 -937
  119. package/dist/chunk-C6KFWOFV.js +0 -616
  120. package/dist/chunk-CHF5OHIA.js +0 -61
  121. package/dist/chunk-CHJ27IGK.js +0 -26
  122. package/dist/chunk-CMBFNA7L.js +0 -27
  123. package/dist/chunk-DAHRH4ON.js +0 -331
  124. package/dist/chunk-DKOHBI74.js +0 -924
  125. package/dist/chunk-DTCOOBMX.js +0 -725
  126. package/dist/chunk-EBGIRKQY.js +0 -616
  127. package/dist/chunk-EUZND3CB.js +0 -27
  128. package/dist/chunk-EVCZO745.js +0 -365
  129. package/dist/chunk-FGOEVHY3.js +0 -60
  130. package/dist/chunk-G3BOQPVO.js +0 -365
  131. package/dist/chunk-GCOK2LC3.js +0 -282
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-K5ZUMYVS.js +0 -89
  134. package/dist/chunk-KQPDEVVS.js +0 -398
  135. package/dist/chunk-L6JRBDNS.js +0 -60
  136. package/dist/chunk-LA6KQEDU.js +0 -712
  137. package/dist/chunk-MDVXJWFN.js +0 -304
  138. package/dist/chunk-MEZVEBPN.js +0 -2008
  139. package/dist/chunk-MK4ERFYL.js +0 -2249
  140. package/dist/chunk-MLKGABMK.js +0 -9
  141. package/dist/chunk-MQ5GOYPH.js +0 -2249
  142. package/dist/chunk-N6IZB6KJ.js +0 -567
  143. package/dist/chunk-NEKUBFPT.js +0 -60
  144. package/dist/chunk-NHUC2QWH.js +0 -282
  145. package/dist/chunk-NMRUZALC.js +0 -1097
  146. package/dist/chunk-NYVAC6P5.js +0 -37
  147. package/dist/chunk-OF7UZIVB.js +0 -725
  148. package/dist/chunk-P6W3STU4.js +0 -2249
  149. package/dist/chunk-PBHF5WKN.js +0 -616
  150. package/dist/chunk-PTQJDMRT.js +0 -146
  151. package/dist/chunk-PZEGYCF5.js +0 -61
  152. package/dist/chunk-QBMDLBU2.js +0 -975
  153. package/dist/chunk-RQGQSLQK.js +0 -725
  154. package/dist/chunk-SDLZDHKP.js +0 -107
  155. package/dist/chunk-TNQWPPE6.js +0 -37
  156. package/dist/chunk-UHNL42EF.js +0 -2730
  157. package/dist/chunk-UNXCEF6S.js +0 -21
  158. package/dist/chunk-V2XTI523.js +0 -347
  159. package/dist/chunk-VAU366PN.js +0 -2241
  160. package/dist/chunk-VMVDTCXB.js +0 -712
  161. package/dist/chunk-VRW3FULF.js +0 -725
  162. package/dist/chunk-WADYRCO2.js +0 -304
  163. package/dist/chunk-WILQZRO4.js +0 -282
  164. package/dist/chunk-WUHJISPP.js +0 -298
  165. package/dist/chunk-XYU6TZOW.js +0 -182
  166. package/dist/chunk-Y6GP4QGG.js +0 -276
  167. package/dist/chunk-YECR7UIA.js +0 -347
  168. package/dist/chunk-YUTWTI4B.js +0 -654
  169. package/dist/chunk-Z65KYU7I.js +0 -26
  170. package/dist/chunk-Z6POF5YC.js +0 -975
  171. package/dist/chunk-ZBJP6WFL.js +0 -482
  172. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  173. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  174. package/dist/contracts-DOrhwbke.d.cts +0 -245
  175. package/dist/contracts-DOrhwbke.d.ts +0 -245
  176. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  177. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  178. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  179. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  180. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  181. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  182. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  183. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  184. package/dist/ssr-3RXHP5ES.js +0 -38
  185. package/dist/ssr-6GIMY5MX.js +0 -38
  186. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  187. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  188. package/dist/ssr-WKUPVSSK.js +0 -36
  189. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  190. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/data.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // data.ts
21
21
  var data_exports = {};
22
22
  __export(data_exports, {
23
+ __resetQueryCache: () => __resetQueryCache,
23
24
  calculateDelay: () => calculateDelay,
24
25
  clearQueryCache: () => clearQueryCache,
25
26
  debounce: () => debounce,
@@ -50,12 +51,12 @@ function isDev() {
50
51
  var _isDev = isDev();
51
52
  function devAssert(condition, message) {
52
53
  if (_isDev && !condition) {
53
- throw new Error(`[Sibu] ${message}`);
54
+ throw new Error(`[SibuJS] ${message}`);
54
55
  }
55
56
  }
56
57
  function devWarn(message) {
57
58
  if (_isDev) {
58
- console.warn(`[Sibu] ${message}`);
59
+ console.warn(`[SibuJS] ${message}`);
59
60
  }
60
61
  }
61
62
 
@@ -65,11 +66,11 @@ var subscriberStack = new Array(32);
65
66
  var stackCapacity = 32;
66
67
  var stackTop = -1;
67
68
  var currentSubscriber = null;
68
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
69
69
  var SUBS = "__s";
70
70
  var notifyDepth = 0;
71
71
  var pendingQueue = [];
72
72
  var pendingSet = /* @__PURE__ */ new Set();
73
+ var propagateStack = [];
73
74
  function safeInvoke(sub) {
74
75
  try {
75
76
  sub();
@@ -78,6 +79,15 @@ function safeInvoke(sub) {
78
79
  }
79
80
  }
80
81
  var trackingSuspended = false;
82
+ function retrack(effectFn, subscriber) {
83
+ const prev = currentSubscriber;
84
+ currentSubscriber = subscriber;
85
+ try {
86
+ effectFn();
87
+ } finally {
88
+ currentSubscriber = prev;
89
+ }
90
+ }
81
91
  function track(effectFn, subscriber) {
82
92
  if (!subscriber) subscriber = effectFn;
83
93
  cleanup(subscriber);
@@ -116,7 +126,6 @@ function recordDependency(signal2) {
116
126
  let subs = signal2[SUBS];
117
127
  if (!subs) {
118
128
  subs = /* @__PURE__ */ new Set();
119
- signalSubscribers.set(signal2, subs);
120
129
  signal2[SUBS] = subs;
121
130
  }
122
131
  subs.add(currentSubscriber);
@@ -138,57 +147,71 @@ function queueSignalNotification(signal2) {
138
147
  }
139
148
  }
140
149
  }
150
+ var maxDrainIterations = 1e5;
141
151
  function drainNotificationQueue() {
142
152
  if (notifyDepth > 0) return;
143
153
  notifyDepth++;
144
154
  try {
145
155
  let i = 0;
146
156
  while (i < pendingQueue.length) {
157
+ if (i >= maxDrainIterations) {
158
+ if (typeof console !== "undefined") {
159
+ console.error(
160
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
161
+ );
162
+ }
163
+ break;
164
+ }
147
165
  safeInvoke(pendingQueue[i]);
148
166
  i++;
149
167
  }
150
168
  } finally {
151
- pendingQueue.length = 0;
152
- pendingSet.clear();
153
169
  notifyDepth--;
170
+ if (notifyDepth === 0) {
171
+ pendingQueue.length = 0;
172
+ pendingSet.clear();
173
+ }
154
174
  }
155
175
  }
156
176
  function propagateDirty(sub) {
157
177
  sub();
158
- let sig = sub._sig;
159
- while (sig) {
178
+ const rootSig = sub._sig;
179
+ if (!rootSig) return;
180
+ const stack = propagateStack;
181
+ const baseLen = stack.length;
182
+ stack.push(rootSig);
183
+ while (stack.length > baseLen) {
184
+ const sig = stack.pop();
160
185
  const first = sig.__f;
161
186
  if (first) {
162
187
  if (first._c) {
163
188
  const nSig = first._sig;
164
- nSig._d = true;
165
- sig = nSig;
166
- continue;
167
- }
168
- if (!pendingSet.has(first)) {
189
+ if (!nSig._d) {
190
+ nSig._d = true;
191
+ stack.push(nSig);
192
+ }
193
+ } else if (!pendingSet.has(first)) {
169
194
  pendingSet.add(first);
170
195
  pendingQueue.push(first);
171
196
  }
172
- break;
197
+ continue;
173
198
  }
174
199
  const subs = sig[SUBS];
175
- if (!subs) break;
176
- let nextSig;
200
+ if (!subs) continue;
177
201
  for (const s of subs) {
178
202
  if (s._c) {
179
- s();
180
203
  const nSig = s._sig;
181
- if (nSig && !nextSig) {
182
- nextSig = nSig;
183
- } else if (nSig) {
184
- propagateDirty(s);
204
+ if (nSig && !nSig._d) {
205
+ nSig._d = true;
206
+ stack.push(nSig);
207
+ } else if (!nSig) {
208
+ s();
185
209
  }
186
210
  } else if (!pendingSet.has(s)) {
187
211
  pendingSet.add(s);
188
212
  pendingQueue.push(s);
189
213
  }
190
214
  }
191
- sig = nextSig;
192
215
  }
193
216
  }
194
217
  function notifySubscribers(signal2) {
@@ -212,13 +235,23 @@ function notifySubscribers(signal2) {
212
235
  }
213
236
  let i = 0;
214
237
  while (i < pendingQueue.length) {
238
+ if (i >= maxDrainIterations) {
239
+ if (typeof console !== "undefined") {
240
+ console.error(
241
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
242
+ );
243
+ }
244
+ break;
245
+ }
215
246
  safeInvoke(pendingQueue[i]);
216
247
  i++;
217
248
  }
218
249
  } finally {
219
- pendingQueue.length = 0;
220
- pendingSet.clear();
221
250
  notifyDepth--;
251
+ if (notifyDepth === 0) {
252
+ pendingQueue.length = 0;
253
+ pendingSet.clear();
254
+ }
222
255
  }
223
256
  return;
224
257
  }
@@ -238,30 +271,48 @@ function notifySubscribers(signal2) {
238
271
  notifyDepth++;
239
272
  try {
240
273
  let directCount = 0;
274
+ let hasComputedSub = false;
241
275
  for (const sub of subs) {
276
+ if (sub._c) hasComputedSub = true;
242
277
  pendingQueue[directCount++] = sub;
243
278
  }
244
- for (let i2 = 0; i2 < directCount; i2++) {
245
- if (pendingQueue[i2]._c) {
246
- propagateDirty(pendingQueue[i2]);
279
+ if (!hasComputedSub) {
280
+ for (let i2 = 0; i2 < directCount; i2++) {
281
+ safeInvoke(pendingQueue[i2]);
247
282
  }
248
- }
249
- for (let i2 = 0; i2 < directCount; i2++) {
250
- if (!pendingQueue[i2]._c) {
251
- if (!pendingSet.has(pendingQueue[i2])) {
252
- safeInvoke(pendingQueue[i2]);
283
+ } else {
284
+ for (let i2 = 0; i2 < directCount; i2++) {
285
+ if (pendingQueue[i2]._c) {
286
+ propagateDirty(pendingQueue[i2]);
287
+ }
288
+ }
289
+ for (let i2 = 0; i2 < directCount; i2++) {
290
+ const sub = pendingQueue[i2];
291
+ if (!sub._c && !pendingSet.has(sub)) {
292
+ pendingSet.add(sub);
293
+ safeInvoke(sub);
253
294
  }
254
295
  }
255
296
  }
256
297
  let i = directCount;
257
298
  while (i < pendingQueue.length) {
299
+ if (i - directCount >= maxDrainIterations) {
300
+ if (typeof console !== "undefined") {
301
+ console.error(
302
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
303
+ );
304
+ }
305
+ break;
306
+ }
258
307
  safeInvoke(pendingQueue[i]);
259
308
  i++;
260
309
  }
261
310
  } finally {
262
- pendingQueue.length = 0;
263
- pendingSet.clear();
264
311
  notifyDepth--;
312
+ if (notifyDepth === 0) {
313
+ pendingQueue.length = 0;
314
+ pendingSet.clear();
315
+ }
265
316
  }
266
317
  }
267
318
  function cleanup(subscriber) {
@@ -272,7 +323,9 @@ function cleanup(subscriber) {
272
323
  if (subs) {
273
324
  subs.delete(subscriber);
274
325
  if (singleDep.__f === subscriber) {
275
- singleDep.__f = void 0;
326
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
327
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
328
+ singleDep.__f = subs.values().next().value;
276
329
  }
277
330
  }
278
331
  sub._dep = void 0;
@@ -285,7 +338,9 @@ function cleanup(subscriber) {
285
338
  if (subs) {
286
339
  subs.delete(subscriber);
287
340
  if (signal2.__f === subscriber) {
288
- signal2.__f = void 0;
341
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
342
+ } else if (subs.size === 1 && signal2.__f === void 0) {
343
+ signal2.__f = subs.values().next().value;
289
344
  }
290
345
  }
291
346
  }
@@ -296,6 +351,7 @@ function cleanup(subscriber) {
296
351
  function derived(getter, options) {
297
352
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
298
353
  const debugName = options?.name;
354
+ const equals = options?.equals;
299
355
  const cs = {};
300
356
  cs._d = false;
301
357
  cs._g = getter;
@@ -306,25 +362,56 @@ function derived(getter, options) {
306
362
  markDirty._c = 1;
307
363
  markDirty._sig = cs;
308
364
  track(() => {
309
- cs._d = false;
310
- cs._v = getter();
365
+ let threw = true;
366
+ try {
367
+ cs._v = getter();
368
+ cs._d = false;
369
+ threw = false;
370
+ } finally {
371
+ if (threw) cs._d = true;
372
+ }
311
373
  }, markDirty);
312
374
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
375
+ let evaluating = false;
313
376
  function computedGetter() {
377
+ if (evaluating) {
378
+ throw new Error(
379
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
380
+ );
381
+ }
314
382
  if (trackingSuspended) {
315
383
  if (cs._d) {
316
- cs._d = false;
317
- cs._v = getter();
384
+ evaluating = true;
385
+ let threw = true;
386
+ try {
387
+ retrack(() => {
388
+ cs._v = getter();
389
+ cs._d = false;
390
+ threw = false;
391
+ }, markDirty);
392
+ } finally {
393
+ evaluating = false;
394
+ if (threw) cs._d = true;
395
+ }
318
396
  }
319
397
  return cs._v;
320
398
  }
321
399
  recordDependency(cs);
322
400
  if (cs._d) {
323
401
  const oldValue = cs._v;
324
- track(() => {
325
- cs._d = false;
326
- cs._v = getter();
327
- }, markDirty);
402
+ evaluating = true;
403
+ let threw = true;
404
+ try {
405
+ retrack(() => {
406
+ const next = getter();
407
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
408
+ cs._d = false;
409
+ threw = false;
410
+ }, markDirty);
411
+ } finally {
412
+ evaluating = false;
413
+ if (threw) cs._d = true;
414
+ }
328
415
  if (hook && oldValue !== cs._v) {
329
416
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
330
417
  }
@@ -341,9 +428,28 @@ function derived(getter, options) {
341
428
  }
342
429
 
343
430
  // src/core/ssr-context.ts
344
- var ssrMode = false;
431
+ var als = null;
432
+ try {
433
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
434
+ const req = Function("return typeof require==='function'?require:null")();
435
+ if (req) {
436
+ const mod = req("node:async_hooks");
437
+ als = new mod.AsyncLocalStorage();
438
+ }
439
+ }
440
+ } catch {
441
+ als = null;
442
+ }
443
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
444
+ function getSSRStore() {
445
+ if (als) {
446
+ const s = als.getStore();
447
+ if (s) return s;
448
+ }
449
+ return fallbackStore;
450
+ }
345
451
  function isSSR() {
346
- return ssrMode;
452
+ return getSSRStore().ssr;
347
453
  }
348
454
 
349
455
  // src/core/signals/effect.ts
@@ -353,26 +459,86 @@ function effect(effectFn, options) {
353
459
  if (isSSR()) return () => {
354
460
  };
355
461
  const onError = options?.onError;
462
+ let userCleanups = [];
463
+ const onCleanup = (fn) => {
464
+ userCleanups.push(fn);
465
+ };
466
+ const runUserCleanups = () => {
467
+ if (userCleanups.length === 0) return;
468
+ const list = userCleanups;
469
+ userCleanups = [];
470
+ for (let i = list.length - 1; i >= 0; i--) {
471
+ try {
472
+ list[i]();
473
+ } catch (err) {
474
+ if (typeof console !== "undefined") {
475
+ console.warn("[SibuJS effect] onCleanup threw:", err);
476
+ }
477
+ }
478
+ }
479
+ };
480
+ const invokeBody = () => effectFn(onCleanup);
356
481
  const wrappedFn = onError ? () => {
357
482
  try {
358
- effectFn();
483
+ invokeBody();
359
484
  } catch (err) {
360
485
  onError(err);
361
486
  }
362
- } : effectFn;
487
+ } : invokeBody;
363
488
  let cleanupHandle = () => {
364
489
  };
490
+ let running = false;
365
491
  const subscriber = () => {
366
- cleanupHandle();
367
- cleanupHandle = track(wrappedFn, subscriber);
492
+ if (running) {
493
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
494
+ console.warn(
495
+ "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
496
+ );
497
+ }
498
+ return;
499
+ }
500
+ running = true;
501
+ try {
502
+ runUserCleanups();
503
+ cleanupHandle();
504
+ cleanupHandle = track(wrappedFn, subscriber);
505
+ } finally {
506
+ running = false;
507
+ }
368
508
  };
369
- cleanupHandle = track(wrappedFn, subscriber);
509
+ running = true;
510
+ try {
511
+ cleanupHandle = track(wrappedFn, subscriber);
512
+ } finally {
513
+ running = false;
514
+ }
370
515
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
371
516
  if (hook) hook.emit("effect:create", { effectFn });
517
+ let disposed = false;
372
518
  return () => {
519
+ if (disposed) return;
520
+ disposed = true;
373
521
  const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
374
- if (h) h.emit("effect:destroy", { effectFn });
375
- cleanupHandle();
522
+ if (h) {
523
+ try {
524
+ h.emit("effect:destroy", { effectFn });
525
+ } catch {
526
+ }
527
+ }
528
+ try {
529
+ runUserCleanups();
530
+ } catch (err) {
531
+ if (typeof console !== "undefined") {
532
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
533
+ }
534
+ }
535
+ try {
536
+ cleanupHandle();
537
+ } catch (err) {
538
+ if (typeof console !== "undefined") {
539
+ console.warn("[SibuJS effect] dispose threw:", err);
540
+ }
541
+ }
376
542
  };
377
543
  }
378
544
 
@@ -396,10 +562,13 @@ function enqueueBatchedSignal(signal2) {
396
562
  return true;
397
563
  }
398
564
  function flushBatch() {
399
- for (const signal2 of pendingSignals) {
400
- queueSignalNotification(signal2);
565
+ try {
566
+ for (const signal2 of pendingSignals) {
567
+ queueSignalNotification(signal2);
568
+ }
569
+ } finally {
570
+ pendingSignals.clear();
401
571
  }
402
- pendingSignals.clear();
403
572
  drainNotificationQueue();
404
573
  }
405
574
 
@@ -456,10 +625,12 @@ function calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter) {
456
625
  break;
457
626
  }
458
627
  delay = Math.min(delay, maxDelay);
628
+ if (!Number.isFinite(delay)) delay = Number.MAX_SAFE_INTEGER;
459
629
  if (jitter > 0) {
460
630
  const jitterRange = delay * jitter;
461
631
  delay += (Math.random() * 2 - 1) * jitterRange;
462
632
  }
633
+ if (!Number.isFinite(delay) || Number.isNaN(delay)) delay = 0;
463
634
  return Math.max(0, delay);
464
635
  }
465
636
  async function withRetry(fn, options, onRetry, signal2) {
@@ -473,6 +644,7 @@ async function withRetry(fn, options, onRetry, signal2) {
473
644
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
474
645
  if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
475
646
  try {
647
+ if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
476
648
  return await fn();
477
649
  } catch (error) {
478
650
  lastError = error;
@@ -480,9 +652,13 @@ async function withRetry(fn, options, onRetry, signal2) {
480
652
  const delay = calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter);
481
653
  onRetry?.(error, attempt, delay);
482
654
  await new Promise((resolve, reject) => {
483
- const timer = setTimeout(resolve, delay);
655
+ let onAbort = null;
656
+ const timer = setTimeout(() => {
657
+ if (onAbort && signal2) signal2.removeEventListener("abort", onAbort);
658
+ resolve();
659
+ }, delay);
484
660
  if (signal2) {
485
- const onAbort = () => {
661
+ onAbort = () => {
486
662
  clearTimeout(timer);
487
663
  reject(new DOMException("Aborted", "AbortError"));
488
664
  };
@@ -550,24 +726,46 @@ function query(key, fetcher, options = {}) {
550
726
  let entry = queryCache.get(key2);
551
727
  if (!entry) {
552
728
  entry = getOrCreateEntry(key2);
553
- entry.subscribers++;
554
729
  entry.listeners.add(onCacheUpdate);
555
730
  entry.refetchers.add(doFetch);
556
731
  }
557
732
  if (entry.promise) {
558
733
  setIsFetching(true);
734
+ const captured = entry.promise;
559
735
  try {
560
- await entry.promise;
736
+ await captured;
737
+ if (disposed || currentKey !== key2) return;
738
+ if (entry.promise === captured) {
739
+ onCacheUpdate();
740
+ if (entry.error) onError?.(entry.error);
741
+ else if (entry.data !== void 0) onSuccess?.(entry.data);
742
+ }
561
743
  } catch {
744
+ if (disposed || currentKey !== key2) return;
745
+ if (entry.promise === captured) {
746
+ onCacheUpdate();
747
+ if (entry.error) onError?.(entry.error);
748
+ }
749
+ } finally {
750
+ if (!disposed && currentKey === key2) onSettled?.();
562
751
  }
563
- onCacheUpdate();
564
752
  return;
565
753
  }
566
754
  abortController?.abort();
567
755
  abortController = new AbortController();
568
756
  const signal2 = abortController.signal;
569
757
  setIsFetching(true);
570
- const promise = withRetry(() => fetcher({ signal: signal2, key: key2 }), retryOptions, void 0, signal2);
758
+ let promise;
759
+ try {
760
+ promise = withRetry(() => fetcher({ signal: signal2, key: key2 }), retryOptions, void 0, signal2);
761
+ } catch (err) {
762
+ setIsFetching(false);
763
+ const errorObj = err instanceof Error ? err : new Error(String(err));
764
+ entry.error = errorObj;
765
+ onError?.(errorObj);
766
+ onSettled?.();
767
+ return;
768
+ }
571
769
  entry.promise = promise;
572
770
  try {
573
771
  const result = await promise;
@@ -629,13 +827,15 @@ function query(key, fetcher, options = {}) {
629
827
  oldEntry.subscribers--;
630
828
  if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
631
829
  const oldKey = currentKey;
830
+ if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
632
831
  oldEntry.gcTimer = setTimeout(() => queryCache.delete(oldKey), cacheTime);
633
832
  }
634
833
  }
635
834
  }
835
+ const keyChanged = currentKey !== key2;
636
836
  currentKey = key2;
637
837
  const entry = getOrCreateEntry(key2, initialData);
638
- entry.subscribers++;
838
+ if (keyChanged) entry.subscribers++;
639
839
  if (entry.gcTimer !== null) {
640
840
  clearTimeout(entry.gcTimer);
641
841
  entry.gcTimer = null;
@@ -650,6 +850,11 @@ function query(key, fetcher, options = {}) {
650
850
  setError(entry.error);
651
851
  });
652
852
  }
853
+ if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
854
+ const isDataStale2 = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
855
+ if (enabled && isDataStale2 && !entry.promise) doFetch();
856
+ return;
857
+ }
653
858
  const isDataStale = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
654
859
  if (enabled && (entry.data === void 0 || isDataStale)) {
655
860
  doFetch();
@@ -677,6 +882,7 @@ function query(key, fetcher, options = {}) {
677
882
  }
678
883
  }
679
884
  function dispose() {
885
+ if (disposed) return;
680
886
  disposed = true;
681
887
  abortController?.abort();
682
888
  effectCleanup();
@@ -689,12 +895,17 @@ function query(key, fetcher, options = {}) {
689
895
  entry.subscribers--;
690
896
  if (entry.subscribers <= 0 && cacheTime >= 0) {
691
897
  const key2 = currentKey;
898
+ if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
692
899
  entry.gcTimer = setTimeout(() => queryCache.delete(key2), cacheTime);
693
900
  }
694
901
  }
695
902
  }
696
- if (focusHandler) globalThis.removeEventListener("focus", focusHandler);
697
- if (onlineHandler) globalThis.removeEventListener("online", onlineHandler);
903
+ if (focusHandler && typeof globalThis.removeEventListener === "function") {
904
+ globalThis.removeEventListener("focus", focusHandler);
905
+ }
906
+ if (onlineHandler && typeof globalThis.removeEventListener === "function") {
907
+ globalThis.removeEventListener("online", onlineHandler);
908
+ }
698
909
  }
699
910
  return {
700
911
  data,
@@ -738,7 +949,19 @@ function clearQueryCache() {
738
949
  }
739
950
  queryCache.clear();
740
951
  for (const listener of activeListeners) listener();
741
- for (const refetcher of activeRefetchers) refetcher();
952
+ for (const refetcher of activeRefetchers) {
953
+ refetcher().catch((err) => {
954
+ if (typeof console !== "undefined") {
955
+ console.warn("[SibuJS query] refetch after clearQueryCache failed:", err);
956
+ }
957
+ });
958
+ }
959
+ }
960
+ function __resetQueryCache() {
961
+ for (const entry of queryCache.values()) {
962
+ if (entry.gcTimer) clearTimeout(entry.gcTimer);
963
+ }
964
+ queryCache.clear();
742
965
  }
743
966
 
744
967
  // src/data/mutation.ts
@@ -749,7 +972,9 @@ function mutation(mutationFn, options = {}) {
749
972
  const [status, setStatus] = signal("idle");
750
973
  const isSuccess = derived(() => status() === "success");
751
974
  const isIdle = derived(() => status() === "idle");
975
+ let runId = 0;
752
976
  async function execute(variables) {
977
+ const myRun = ++runId;
753
978
  let context2;
754
979
  batch(() => {
755
980
  setLoading(true);
@@ -761,6 +986,7 @@ function mutation(mutationFn, options = {}) {
761
986
  context2 = await options.onMutate(variables);
762
987
  }
763
988
  const result = await withRetry(() => mutationFn(variables), options.retry);
989
+ if (myRun !== runId) return result;
764
990
  batch(() => {
765
991
  setData(result);
766
992
  setLoading(false);
@@ -771,6 +997,7 @@ function mutation(mutationFn, options = {}) {
771
997
  return result;
772
998
  } catch (err) {
773
999
  const errorObj = err instanceof Error ? err : new Error(String(err));
1000
+ if (myRun !== runId) throw errorObj;
774
1001
  batch(() => {
775
1002
  setError(errorObj);
776
1003
  setLoading(false);
@@ -782,6 +1009,7 @@ function mutation(mutationFn, options = {}) {
782
1009
  }
783
1010
  }
784
1011
  function reset() {
1012
+ runId++;
785
1013
  batch(() => {
786
1014
  setData(void 0);
787
1015
  setError(void 0);
@@ -796,7 +1024,10 @@ function mutation(mutationFn, options = {}) {
796
1024
  isSuccess,
797
1025
  isIdle,
798
1026
  mutate: (variables) => {
799
- execute(variables).catch(() => {
1027
+ execute(variables).catch((err) => {
1028
+ if (typeof console !== "undefined") {
1029
+ console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
1030
+ }
800
1031
  });
801
1032
  },
802
1033
  mutateAsync: execute,
@@ -832,11 +1063,13 @@ function infiniteQuery(key, fetcher, options) {
832
1063
  const hasPreviousPage = derived(() => prevPageParam() !== void 0);
833
1064
  let abortController = null;
834
1065
  let disposed = false;
1066
+ let runId = 0;
835
1067
  async function fetchPage(pageParam, direction) {
836
1068
  if (disposed) return;
837
1069
  abortController?.abort();
838
1070
  abortController = new AbortController();
839
1071
  const signal2 = abortController.signal;
1072
+ const myRun = ++runId;
840
1073
  batch(() => {
841
1074
  setIsFetching(true);
842
1075
  if (direction === "next") setIsFetchingNext(true);
@@ -845,7 +1078,7 @@ function infiniteQuery(key, fetcher, options) {
845
1078
  });
846
1079
  try {
847
1080
  const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
848
- if (disposed) return;
1081
+ if (disposed || myRun !== runId) return;
849
1082
  const currentPages = pages();
850
1083
  let newPages;
851
1084
  if (direction === "prev") {
@@ -865,7 +1098,7 @@ function infiniteQuery(key, fetcher, options) {
865
1098
  });
866
1099
  onSuccess?.(newPages);
867
1100
  } catch (err) {
868
- if (disposed) return;
1101
+ if (disposed || myRun !== runId) return;
869
1102
  if (err instanceof DOMException && err.name === "AbortError") return;
870
1103
  const errorObj = err instanceof Error ? err : new Error(String(err));
871
1104
  batch(() => {
@@ -880,9 +1113,12 @@ function infiniteQuery(key, fetcher, options) {
880
1113
  const effectCleanup = effect(() => {
881
1114
  resolveKey();
882
1115
  if (enabled) {
883
- setPages([]);
884
- setNextPageParam(initialPageParam);
885
- setPrevPageParam(void 0);
1116
+ abortController?.abort();
1117
+ batch(() => {
1118
+ setPages([]);
1119
+ setNextPageParam(initialPageParam);
1120
+ setPrevPageParam(void 0);
1121
+ });
886
1122
  fetchPage(initialPageParam, "initial");
887
1123
  }
888
1124
  });
@@ -1031,7 +1267,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1031
1267
  options.onSuccess?.(result);
1032
1268
  } catch (err) {
1033
1269
  if (version !== fetchVersion || disposed) return;
1034
- if (err instanceof DOMException && err.name === "AbortError") return;
1270
+ if (err instanceof DOMException && err.name === "AbortError") {
1271
+ if (version === fetchVersion) setLoading(false);
1272
+ return;
1273
+ }
1035
1274
  const errorObj = err instanceof Error ? err : new Error(String(err));
1036
1275
  batch(() => {
1037
1276
  setError(errorObj);
@@ -1116,18 +1355,57 @@ function idbPut(db, store, item) {
1116
1355
  tx.onerror = () => reject(tx.error);
1117
1356
  });
1118
1357
  }
1119
- function idbDelete(db, store, key) {
1358
+ function idbPutWithChange(db, item, change) {
1359
+ return new Promise((resolve, reject) => {
1360
+ const tx = db.transaction(["items", "_changes"], "readwrite");
1361
+ tx.objectStore("items").put(item);
1362
+ tx.objectStore("_changes").put(change);
1363
+ tx.oncomplete = () => resolve();
1364
+ tx.onerror = () => reject(tx.error);
1365
+ });
1366
+ }
1367
+ function idbDeleteWithChange(db, key, change) {
1368
+ return new Promise((resolve, reject) => {
1369
+ const tx = db.transaction(["items", "_changes"], "readwrite");
1370
+ tx.objectStore("items").delete(key);
1371
+ tx.objectStore("_changes").put(change);
1372
+ tx.oncomplete = () => resolve();
1373
+ tx.onerror = () => reject(tx.error);
1374
+ });
1375
+ }
1376
+ function idbGetAllWithKeys(db, store) {
1377
+ return new Promise((resolve, reject) => {
1378
+ const tx = db.transaction(store, "readonly");
1379
+ const out = [];
1380
+ const req = tx.objectStore(store).openCursor();
1381
+ req.onsuccess = () => {
1382
+ const cursor = req.result;
1383
+ if (cursor) {
1384
+ out.push({ key: cursor.primaryKey, value: cursor.value });
1385
+ cursor.continue();
1386
+ } else {
1387
+ resolve(out);
1388
+ }
1389
+ };
1390
+ req.onerror = () => reject(req.error);
1391
+ });
1392
+ }
1393
+ function idbDeleteKeys(db, store, keys) {
1394
+ if (keys.length === 0) return Promise.resolve();
1120
1395
  return new Promise((resolve, reject) => {
1121
1396
  const tx = db.transaction(store, "readwrite");
1122
- tx.objectStore(store).delete(key);
1397
+ const objStore = tx.objectStore(store);
1398
+ for (const k of keys) objStore.delete(k);
1123
1399
  tx.oncomplete = () => resolve();
1124
1400
  tx.onerror = () => reject(tx.error);
1125
1401
  });
1126
1402
  }
1127
- function idbClear(db, store) {
1403
+ function idbPutMany(db, store, items) {
1404
+ if (items.length === 0) return Promise.resolve();
1128
1405
  return new Promise((resolve, reject) => {
1129
1406
  const tx = db.transaction(store, "readwrite");
1130
- tx.objectStore(store).clear();
1407
+ const objStore = tx.objectStore(store);
1408
+ for (const item of items) objStore.put(item);
1131
1409
  tx.oncomplete = () => resolve();
1132
1410
  tx.onerror = () => reject(tx.error);
1133
1411
  });
@@ -1150,15 +1428,13 @@ async function offlineStore(options) {
1150
1428
  setPendingCount(changes.length);
1151
1429
  }
1152
1430
  async function put(item) {
1153
- await idbPut(db, "items", item);
1154
- await idbPut(db, "_changes", { type: "put", item, timestamp: Date.now() });
1431
+ await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() });
1155
1432
  await refreshData();
1156
1433
  }
1157
1434
  async function remove(key) {
1158
1435
  const existing = await idbGet(db, "items", key);
1159
1436
  if (existing) {
1160
- await idbDelete(db, "items", key);
1161
- await idbPut(db, "_changes", { type: "delete", item: existing, timestamp: Date.now() });
1437
+ await idbDeleteWithChange(db, key, { type: "delete", item: existing, timestamp: Date.now() });
1162
1438
  await refreshData();
1163
1439
  }
1164
1440
  }
@@ -1169,25 +1445,45 @@ async function offlineStore(options) {
1169
1445
  return data().filter(filter);
1170
1446
  }
1171
1447
  async function sync() {
1172
- if (!adapter || isSyncing()) return;
1448
+ if (!adapter || isSyncing() || closed) return;
1173
1449
  setIsSyncing(true);
1174
1450
  try {
1175
- const changes = await idbGetAll(db, "_changes");
1176
- if (changes.length > 0) {
1177
- const result = await adapter.push(changes);
1451
+ const snapshot = await idbGetAllWithKeys(db, "_changes");
1452
+ if (closed) return;
1453
+ if (snapshot.length > 0) {
1454
+ const result = await adapter.push(snapshot.map((e) => e.value));
1455
+ if (closed) return;
1178
1456
  if (result.ok) {
1179
- await idbClear(db, "_changes");
1457
+ await idbDeleteKeys(
1458
+ db,
1459
+ "_changes",
1460
+ snapshot.map((e) => e.key)
1461
+ );
1462
+ if (closed) return;
1180
1463
  }
1181
1464
  }
1182
1465
  const remoteItems = await adapter.pull(lastSynced());
1183
- for (const item of remoteItems) {
1184
- await idbPut(db, "items", item);
1466
+ if (closed) return;
1467
+ const pendingChanges = await idbGetAll(db, "_changes");
1468
+ if (closed) return;
1469
+ const pendingKeys = /* @__PURE__ */ new Set();
1470
+ for (const c of pendingChanges) {
1471
+ const k = c.item[keyPath];
1472
+ if (k != null) pendingKeys.add(k);
1185
1473
  }
1474
+ const safeRemote = remoteItems.filter((item) => {
1475
+ const k = item[keyPath];
1476
+ return k == null || !pendingKeys.has(k);
1477
+ });
1478
+ await idbPutMany(db, "items", safeRemote);
1479
+ if (closed) return;
1186
1480
  const now = Date.now();
1187
1481
  await idbPut(db, "_meta", now);
1482
+ if (closed) return;
1188
1483
  setLastSynced(now);
1189
1484
  await refreshData();
1190
- } catch {
1485
+ } catch (err) {
1486
+ if (typeof console !== "undefined") console.warn("[offlineStore] sync failed", err);
1191
1487
  } finally {
1192
1488
  setIsSyncing(false);
1193
1489
  }
@@ -1195,13 +1491,21 @@ async function offlineStore(options) {
1195
1491
  function attach(newAdapter) {
1196
1492
  adapter = newAdapter;
1197
1493
  }
1494
+ let onlineHandler = null;
1495
+ let closed = false;
1198
1496
  function close() {
1497
+ closed = true;
1498
+ if (onlineHandler && typeof window !== "undefined") {
1499
+ window.removeEventListener("online", onlineHandler);
1500
+ onlineHandler = null;
1501
+ }
1199
1502
  db.close();
1200
1503
  }
1201
1504
  if (autoSync && typeof window !== "undefined") {
1202
- window.addEventListener("online", () => {
1505
+ onlineHandler = () => {
1203
1506
  sync();
1204
- });
1507
+ };
1508
+ window.addEventListener("online", onlineHandler);
1205
1509
  }
1206
1510
  return {
1207
1511
  data,
@@ -1224,9 +1528,11 @@ function syncAdapter(config) {
1224
1528
  // src/core/rendering/context.ts
1225
1529
  function context(defaultValue) {
1226
1530
  const [getValue, setValue] = signal(defaultValue);
1227
- return {
1531
+ const ctx = {
1228
1532
  provide(value) {
1533
+ const previous2 = getValue();
1229
1534
  setValue(value);
1535
+ return () => setValue(previous2);
1230
1536
  },
1231
1537
  use() {
1232
1538
  return getValue;
@@ -1236,8 +1542,18 @@ function context(defaultValue) {
1236
1542
  },
1237
1543
  set(value) {
1238
1544
  setValue(value);
1545
+ },
1546
+ withContext(value, fn) {
1547
+ const previous2 = getValue();
1548
+ setValue(value);
1549
+ try {
1550
+ return fn();
1551
+ } finally {
1552
+ setValue(previous2);
1553
+ }
1239
1554
  }
1240
1555
  };
1556
+ return ctx;
1241
1557
  }
1242
1558
 
1243
1559
  // src/data/routeLoader.ts
@@ -1258,9 +1574,16 @@ function loaderData() {
1258
1574
  error: resource2.error
1259
1575
  };
1260
1576
  }
1261
- async function preloadRoute(route, context2) {
1577
+ async function preloadRoute(route, context2, callerSignal) {
1262
1578
  if (!route.loader) return void 0;
1263
1579
  const controller = new AbortController();
1580
+ if (callerSignal) {
1581
+ if (callerSignal.aborted) {
1582
+ controller.abort();
1583
+ } else {
1584
+ callerSignal.addEventListener("abort", () => controller.abort(), { once: true });
1585
+ }
1586
+ }
1264
1587
  return route.loader(context2, { signal: controller.signal });
1265
1588
  }
1266
1589
 
@@ -1275,7 +1598,7 @@ function validateWsUrl(raw) {
1275
1598
  function socket(url, options) {
1276
1599
  const autoReconnect = options?.autoReconnect ?? false;
1277
1600
  const reconnectDelay = options?.reconnectDelay ?? 1e3;
1278
- const maxReconnects = options?.maxReconnects ?? Infinity;
1601
+ const maxReconnects = options?.maxReconnects ?? 10;
1279
1602
  const heartbeat = options?.heartbeat;
1280
1603
  const protocols = options?.protocols;
1281
1604
  const [data, setData] = signal(null);
@@ -1285,6 +1608,7 @@ function socket(url, options) {
1285
1608
  let reconnectTimer = null;
1286
1609
  let heartbeatTimer = null;
1287
1610
  let disposed = false;
1611
+ let manuallyClosed = false;
1288
1612
  function getUrl() {
1289
1613
  return typeof url === "function" ? url() : url;
1290
1614
  }
@@ -1308,11 +1632,18 @@ function socket(url, options) {
1308
1632
  ws.onclose = () => {
1309
1633
  setStatus("closed");
1310
1634
  stopHeartbeat();
1311
- if (autoReconnect && !disposed && reconnectCount < maxReconnects) {
1635
+ const wasManual = manuallyClosed;
1636
+ manuallyClosed = false;
1637
+ if (autoReconnect && !disposed && !wasManual && reconnectCount < maxReconnects) {
1638
+ const cap = 3e4;
1639
+ const delay = Math.min(cap, reconnectDelay * 2 ** reconnectCount);
1640
+ const jittered = delay * (0.5 + Math.random() * 0.5);
1312
1641
  reconnectCount++;
1313
1642
  reconnectTimer = setTimeout(() => {
1643
+ reconnectTimer = null;
1644
+ if (disposed || manuallyClosed) return;
1314
1645
  connect();
1315
- }, reconnectDelay);
1646
+ }, jittered);
1316
1647
  }
1317
1648
  };
1318
1649
  ws.onerror = () => {
@@ -1339,6 +1670,7 @@ function socket(url, options) {
1339
1670
  }
1340
1671
  }
1341
1672
  function close() {
1673
+ manuallyClosed = true;
1342
1674
  if (reconnectTimer !== null) {
1343
1675
  clearTimeout(reconnectTimer);
1344
1676
  reconnectTimer = null;
@@ -1358,13 +1690,24 @@ function socket(url, options) {
1358
1690
  }
1359
1691
 
1360
1692
  // src/utils/sanitize.ts
1693
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1361
1694
  function sanitizeUrl(url) {
1362
1695
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
1363
1696
  if (!trimmed) return "";
1364
1697
  const lower = trimmed.toLowerCase();
1365
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
1366
- return "";
1698
+ let schemeEnd = -1;
1699
+ for (let i = 0; i < lower.length; i++) {
1700
+ const ch = lower.charCodeAt(i);
1701
+ if (ch === 58) {
1702
+ schemeEnd = i;
1703
+ break;
1704
+ }
1705
+ if (ch === 47 || ch === 63 || ch === 35) break;
1367
1706
  }
1707
+ if (schemeEnd === -1) return trimmed;
1708
+ const scheme = lower.slice(0, schemeEnd + 1);
1709
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1710
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1368
1711
  return trimmed;
1369
1712
  }
1370
1713
 
@@ -1376,12 +1719,16 @@ function validateSseUrl(raw) {
1376
1719
  }
1377
1720
  function stream(url, options) {
1378
1721
  const autoReconnect = options?.autoReconnect ?? false;
1722
+ const maxReconnects = options?.maxReconnects ?? 10;
1723
+ const baseMs = options?.reconnectBaseMs ?? 1e3;
1724
+ const maxMs = options?.reconnectMaxMs ?? 3e4;
1379
1725
  const [data, setData] = signal(null);
1380
1726
  const [event, setEvent] = signal(null);
1381
1727
  const [status, setStatus] = signal("connecting");
1382
1728
  let source = null;
1383
1729
  let disposed = false;
1384
1730
  let reconnectTimer = null;
1731
+ let attempts = 0;
1385
1732
  function connect() {
1386
1733
  if (disposed) return;
1387
1734
  const safeUrl = validateSseUrl(url);
@@ -1395,6 +1742,7 @@ function stream(url, options) {
1395
1742
  });
1396
1743
  source.onopen = () => {
1397
1744
  setStatus("open");
1745
+ attempts = 0;
1398
1746
  };
1399
1747
  source.onmessage = (evt) => {
1400
1748
  setData(evt.data);
@@ -1404,11 +1752,14 @@ function stream(url, options) {
1404
1752
  if (source && source.readyState === EventSource.CLOSED) {
1405
1753
  setStatus("closed");
1406
1754
  source = null;
1407
- if (autoReconnect && !disposed) {
1755
+ if (autoReconnect && !disposed && attempts < maxReconnects) {
1756
+ const delay = Math.min(maxMs, baseMs * 2 ** attempts);
1757
+ const jittered = delay * (0.5 + Math.random() * 0.5);
1758
+ attempts++;
1408
1759
  reconnectTimer = setTimeout(() => {
1409
1760
  reconnectTimer = null;
1410
1761
  connect();
1411
- }, 1e3);
1762
+ }, jittered);
1412
1763
  }
1413
1764
  }
1414
1765
  };
@@ -1433,6 +1784,7 @@ function stream(url, options) {
1433
1784
  }
1434
1785
  // Annotate the CommonJS export names for ESM import in node:
1435
1786
  0 && (module.exports = {
1787
+ __resetQueryCache,
1436
1788
  calculateDelay,
1437
1789
  clearQueryCache,
1438
1790
  debounce,