sibujs 4.5.0 → 4.7.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 (116) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +326 -133
  3. package/dist/browser.d.cts +46 -14
  4. package/dist/browser.d.ts +46 -14
  5. package/dist/browser.js +8 -6
  6. package/dist/build.cjs +2094 -1033
  7. package/dist/build.d.cts +133 -44
  8. package/dist/build.d.ts +133 -44
  9. package/dist/build.js +1372 -574
  10. package/dist/cdn.dev.global.js +10 -10
  11. package/dist/cdn.full.dev.global.js +11 -11
  12. package/dist/cdn.full.global.js +10 -10
  13. package/dist/cdn.global.js +10 -10
  14. package/dist/{chunk-2DCGACUU.js → chunk-2BPG2XDA.js} +251 -66
  15. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  16. package/dist/{chunk-4PMLNECI.js → chunk-3QWBSL5R.js} +98 -29
  17. package/dist/{chunk-KKLW7YWL.js → chunk-4AWA2PVD.js} +275 -144
  18. package/dist/chunk-5HZXGZ6T.js +24 -0
  19. package/dist/{chunk-PCT43HW3.js → chunk-7LUJQAOJ.js} +1 -1
  20. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  21. package/dist/chunk-BC2SECJD.js +44 -0
  22. package/dist/{chunk-TBYTO6BS.js → chunk-BSY63EM6.js} +136 -80
  23. package/dist/{chunk-FTIR4QW2.js → chunk-DKXRACVN.js} +6 -8
  24. package/dist/{chunk-ONOHFDLG.js → chunk-FNJXNGYZ.js} +3 -3
  25. package/dist/{chunk-XZZOBQAY.js → chunk-FQRUXCEE.js} +6 -5
  26. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  27. package/dist/chunk-J6FW5TV6.js +233 -0
  28. package/dist/{chunk-UGRX3S57.js → chunk-JWKYU5GV.js} +22 -4
  29. package/dist/chunk-NYNYSPK7.js +318 -0
  30. package/dist/{chunk-RBTPLM32.js → chunk-RUSSKG6G.js} +13 -12
  31. package/dist/{chunk-7LN645I6.js → chunk-SLM3IA34.js} +3 -3
  32. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  33. package/dist/{chunk-B3WHI2QA.js → chunk-UNWRJRKC.js} +55 -31
  34. package/dist/{chunk-KEISJXBU.js → chunk-UOL2ECCS.js} +44 -17
  35. package/dist/{chunk-RJE2BNI4.js → chunk-VZSG24LS.js} +108 -45
  36. package/dist/{chunk-RIXRAYIU.js → chunk-WEQ3DMVL.js} +10 -4
  37. package/dist/{chunk-3JZ4L5TJ.js → chunk-WOLJZUFQ.js} +354 -90
  38. package/dist/{chunk-S373NSMK.js → chunk-XYV3EDB7.js} +283 -123
  39. package/dist/{chunk-VZKNK2V7.js → chunk-XZR4PXRE.js} +296 -38
  40. package/dist/{chunk-GW3SCCZG.js → chunk-Z3OHK6QT.js} +153 -110
  41. package/dist/{chunk-OMJJM3KM.js → chunk-ZVL7TY4K.js} +287 -130
  42. package/dist/{contracts-DBdg9J_a.d.ts → contracts-CLqzJnOV.d.ts} +36 -17
  43. package/dist/{contracts-DBdg9J_a.d.cts → contracts-CTOJXu-x.d.cts} +36 -17
  44. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  45. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  46. package/dist/data.cjs +410 -155
  47. package/dist/data.d.cts +166 -12
  48. package/dist/data.d.ts +166 -12
  49. package/dist/data.js +12 -9
  50. package/dist/devtools.cjs +98 -61
  51. package/dist/devtools.js +7 -8
  52. package/dist/dispose-GEIG2KOF.js +28 -0
  53. package/dist/ecosystem.cjs +372 -126
  54. package/dist/ecosystem.d.cts +20 -3
  55. package/dist/ecosystem.d.ts +20 -3
  56. package/dist/ecosystem.js +12 -12
  57. package/dist/extras.cjs +2366 -909
  58. package/dist/extras.d.cts +11 -9
  59. package/dist/extras.d.ts +11 -9
  60. package/dist/extras.js +39 -28
  61. package/dist/index.cjs +421 -158
  62. package/dist/index.d.cts +213 -171
  63. package/dist/index.d.ts +213 -171
  64. package/dist/index.js +24 -27
  65. package/dist/motion.cjs +118 -44
  66. package/dist/motion.js +5 -5
  67. package/dist/patterns.cjs +344 -53
  68. package/dist/patterns.d.cts +28 -9
  69. package/dist/patterns.d.ts +28 -9
  70. package/dist/patterns.js +8 -8
  71. package/dist/performance.cjs +325 -220
  72. package/dist/performance.d.cts +2 -2
  73. package/dist/performance.d.ts +2 -2
  74. package/dist/performance.js +8 -9
  75. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  76. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  77. package/dist/plugins.cjs +664 -233
  78. package/dist/plugins.d.cts +127 -14
  79. package/dist/plugins.d.ts +127 -14
  80. package/dist/plugins.js +96 -42
  81. package/dist/signal-EotCj4hS.d.cts +110 -0
  82. package/dist/signal-EotCj4hS.d.ts +110 -0
  83. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  84. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  85. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  86. package/dist/ssr.cjs +229 -84
  87. package/dist/ssr.d.cts +9 -3
  88. package/dist/ssr.d.ts +9 -3
  89. package/dist/ssr.js +11 -12
  90. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  91. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  92. package/dist/tagFactory-8qL9LCIx.d.cts +156 -0
  93. package/dist/tagFactory-BL2fymez.d.ts +156 -0
  94. package/dist/testing.cjs +2503 -2191
  95. package/dist/testing.d.cts +56 -5
  96. package/dist/testing.d.ts +56 -5
  97. package/dist/testing.js +580 -307
  98. package/dist/types-CJFViL6Q.d.cts +26 -0
  99. package/dist/types-CJFViL6Q.d.ts +26 -0
  100. package/dist/ui.cjs +732 -329
  101. package/dist/ui.d.cts +41 -7
  102. package/dist/ui.d.ts +41 -7
  103. package/dist/ui.js +151 -56
  104. package/dist/widgets.cjs +267 -291
  105. package/dist/widgets.js +9 -10
  106. package/package.json +4 -2
  107. package/dist/chunk-2WLZ6757.js +0 -149
  108. package/dist/chunk-CCSJMTRN.js +0 -15
  109. package/dist/chunk-QKRPLZ2V.js +0 -108
  110. package/dist/chunk-VUF4ALSW.js +0 -60
  111. package/dist/chunk-WWV3SJ3L.js +0 -131
  112. package/dist/dispose-46BOMMQJ.js +0 -19
  113. package/dist/plugin-D30wlGW5.d.cts +0 -71
  114. package/dist/plugin-D30wlGW5.d.ts +0 -71
  115. package/dist/tagFactory-DVoDpHye.d.cts +0 -215
  116. package/dist/tagFactory-DVoDpHye.d.ts +0 -215
package/dist/data.cjs CHANGED
@@ -34,12 +34,14 @@ __export(data_exports, {
34
34
  preloadRoute: () => preloadRoute,
35
35
  previous: () => previous,
36
36
  query: () => query,
37
+ renderWithLoader: () => renderWithLoader,
37
38
  resource: () => resource,
38
39
  setQueryData: () => setQueryData,
39
40
  socket: () => socket,
40
41
  stream: () => stream,
41
42
  syncAdapter: () => syncAdapter,
42
43
  throttle: () => throttle,
44
+ withLoader: () => withLoader,
43
45
  withRetry: () => withRetry
44
46
  });
45
47
  module.exports = __toCommonJS(data_exports);
@@ -83,8 +85,8 @@ function safeStringify(value) {
83
85
  return Object.prototype.toString.call(value);
84
86
  }
85
87
  }
86
- function describe(context2) {
87
- return context2.name ? `${context2.phase} "${context2.name}"` : context2.phase;
88
+ function describe(context) {
89
+ return context.name ? `${context.phase} "${context.name}"` : context.phase;
88
90
  }
89
91
  var REACTIVE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
90
92
  function untrackedRun(fn) {
@@ -92,8 +94,8 @@ function untrackedRun(fn) {
92
94
  return api?.untracked ? api.untracked(fn) : fn();
93
95
  }
94
96
  var PROPAGATE_EVENT = "sibu:error-propagate";
95
- function claimedByBoundary(error, context2) {
96
- const raw = context2.node;
97
+ function claimedByBoundary(error, context) {
98
+ const raw = context.node;
97
99
  if (!raw || typeof CustomEvent === "undefined") return false;
98
100
  const target = resolveEventTarget(raw);
99
101
  if (!target) return false;
@@ -101,7 +103,7 @@ function claimedByBoundary(error, context2) {
101
103
  const event = new CustomEvent(PROPAGATE_EVENT, {
102
104
  bubbles: true,
103
105
  cancelable: true,
104
- detail: { error, context: context2 }
106
+ detail: { error, context }
105
107
  });
106
108
  return target.dispatchEvent(event) === false;
107
109
  } catch {
@@ -120,20 +122,20 @@ function resolveEventTarget(node) {
120
122
  const self = node;
121
123
  return typeof self.dispatchEvent === "function" ? self : null;
122
124
  }
123
- function defaultReport(error, context2) {
125
+ function defaultReport(error, context) {
124
126
  if (typeof console === "undefined") return;
125
- console.error(`[SibuJS] Uncaught error during ${describe(context2)}: ${error.message}`, error);
127
+ console.error(`[SibuJS] Uncaught error during ${describe(context)}: ${error.message}`, error);
126
128
  }
127
- function reportError(error, context2) {
129
+ function reportError(error, context) {
128
130
  const err = normalizeError(error);
129
131
  untrackedRun(() => {
130
- if (claimedByBoundary(err, context2)) return;
132
+ if (claimedByBoundary(err, context)) return;
131
133
  const s = state();
132
134
  const handler = s.handler;
133
135
  if (handler && s.handlerDepth === 0) {
134
136
  s.handlerDepth++;
135
137
  try {
136
- handler(error instanceof Error ? error : err, context2);
138
+ handler(error instanceof Error ? error : err, context);
137
139
  return;
138
140
  } catch (handlerError) {
139
141
  if (typeof console !== "undefined") {
@@ -144,10 +146,29 @@ function reportError(error, context2) {
144
146
  s.handlerDepth--;
145
147
  }
146
148
  }
147
- defaultReport(err, context2);
149
+ defaultReport(err, context);
148
150
  });
149
151
  }
150
152
 
153
+ // src/core/rendering/dispose.ts
154
+ function resolveCapture(capture) {
155
+ let frame = capture;
156
+ while (frame?.closed) frame = frame.forwardTo;
157
+ return frame ?? null;
158
+ }
159
+ var registrationCaptures = [];
160
+ function currentDisposerCapture() {
161
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
162
+ }
163
+ function beginDisposerCapture(capture) {
164
+ if (capture === null && registrationCaptures.length === 0) return false;
165
+ registrationCaptures.push(resolveCapture(capture));
166
+ return true;
167
+ }
168
+ function endDisposerCapture() {
169
+ registrationCaptures.pop();
170
+ }
171
+
151
172
  // src/reactivity/track-core.ts
152
173
  var POOL_MAX = 4096;
153
174
  var nodePool = [];
@@ -306,6 +327,7 @@ function retrack(effectFn, subscriber) {
306
327
  }
307
328
  currentSubscriber = subscriber;
308
329
  const sub = subscriber;
330
+ if (sub._cap === void 0) sub._cap = currentDisposerCapture();
309
331
  const epoch = ++subscriberEpochCounter;
310
332
  sub._epoch = epoch;
311
333
  sub._structDirty = false;
@@ -497,7 +519,12 @@ function drainQueue() {
497
519
  quarantined.add(sub);
498
520
  continue;
499
521
  }
500
- safeInvoke(sub);
522
+ const pushed = beginDisposerCapture(sub._cap ?? null);
523
+ try {
524
+ safeInvoke(sub);
525
+ } finally {
526
+ if (pushed) endDisposerCapture();
527
+ }
501
528
  }
502
529
  }
503
530
  function drainNotificationQueue() {
@@ -633,7 +660,7 @@ function forEachSubscriber(signal2, visit) {
633
660
  }
634
661
 
635
662
  // src/reactivity/track.ts
636
- var _runtimeVersion = true ? "4.5.0" : "dev";
663
+ var _runtimeVersion = true ? "4.7.0" : "dev";
637
664
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
638
665
  function resolveReactiveApi() {
639
666
  const g = globalThis;
@@ -689,6 +716,14 @@ var getSubscriberCount2 = API.getSubscriberCount;
689
716
  var getSubscriberDeps2 = API.getSubscriberDeps;
690
717
  var forEachSubscriber2 = API.forEachSubscriber;
691
718
 
719
+ // src/core/devtoolsHook.ts
720
+ function emitDevtools(hook, event, payload) {
721
+ try {
722
+ hook.emit(event, payload);
723
+ } catch {
724
+ }
725
+ }
726
+
692
727
  // src/core/signals/derived.ts
693
728
  function derived(getter, options) {
694
729
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
@@ -715,17 +750,17 @@ function derived(getter, options) {
715
750
  cs._d = false;
716
751
  cs._init = true;
717
752
  };
718
- track2(() => {
719
- let threw = true;
720
- try {
753
+ try {
754
+ track2(() => {
721
755
  cs._v = getter();
722
756
  cs._d = false;
723
757
  cs._init = true;
724
- threw = false;
725
- } finally {
726
- if (threw) cs._d = true;
727
- }
728
- }, markDirty);
758
+ }, markDirty);
759
+ } catch (err) {
760
+ disposed = true;
761
+ cleanup2(markDirty);
762
+ throw err;
763
+ }
729
764
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
730
765
  const validate = () => {
731
766
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -744,7 +779,7 @@ function derived(getter, options) {
744
779
  if (disposed) cleanup2(markDirty);
745
780
  }
746
781
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
747
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
782
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
748
783
  }
749
784
  };
750
785
  cs._validate = validate;
@@ -795,7 +830,7 @@ function derived(getter, options) {
795
830
  }
796
831
  }
797
832
  };
798
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
833
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
799
834
  return computedGetter;
800
835
  }
801
836
 
@@ -964,12 +999,17 @@ function effect(effectFn, options) {
964
999
  try {
965
1000
  retrack2(ctx.bodyFn, ctx.subscriber);
966
1001
  if (ctx.rerunPending) drainReruns(ctx);
1002
+ } catch (err) {
1003
+ ctx.disposed = true;
1004
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1005
+ cleanup2(ctx.subscriber);
1006
+ throw err;
967
1007
  } finally {
968
1008
  ctx.running = false;
969
1009
  ctx.rerunPending = false;
970
1010
  }
971
1011
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
972
- if (hook) hook.emit("effect:create", { effectFn });
1012
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
973
1013
  return () => disposeEffect(ctx);
974
1014
  }
975
1015
 
@@ -1056,7 +1096,7 @@ function signal(initial, options) {
1056
1096
  state2.__v++;
1057
1097
  if (DEV) {
1058
1098
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1059
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1099
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1060
1100
  }
1061
1101
  if (!enqueueBatchedSignal(state2)) {
1062
1102
  notifySubscribers2(state2);
@@ -1070,7 +1110,7 @@ function signal(initial, options) {
1070
1110
  state2.value = newValue;
1071
1111
  state2.__v++;
1072
1112
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1073
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1113
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1074
1114
  if (!enqueueBatchedSignal(state2)) {
1075
1115
  notifySubscribers2(state2);
1076
1116
  }
@@ -1089,7 +1129,7 @@ function signal(initial, options) {
1089
1129
  }
1090
1130
  if (DEV) {
1091
1131
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1092
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1132
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
1093
1133
  }
1094
1134
  return [get, set];
1095
1135
  }
@@ -1211,7 +1251,83 @@ function waitForRetryDelay(delay, signal2) {
1211
1251
  });
1212
1252
  }
1213
1253
 
1254
+ // src/data/structuralSharing.ts
1255
+ function isPlainObject(value) {
1256
+ if (value === null || typeof value !== "object") return false;
1257
+ const proto = Object.getPrototypeOf(value);
1258
+ return proto === Object.prototype || proto === null;
1259
+ }
1260
+ function isPlainArray(value) {
1261
+ return Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype;
1262
+ }
1263
+ function reconcile(prev, next, depth, walk, newRoot) {
1264
+ if (Object.is(prev, next)) return prev;
1265
+ const arrays = isPlainArray(prev) && isPlainArray(next);
1266
+ if (!arrays && !(isPlainObject(prev) && isPlainObject(next))) return next;
1267
+ if (Object.getPrototypeOf(prev) !== Object.getPrototypeOf(next)) return next;
1268
+ const nextObject = next;
1269
+ const seenAt = walk.path.get(nextObject);
1270
+ if (seenAt !== void 0) {
1271
+ walk.cycleTo = Math.min(walk.cycleTo, seenAt);
1272
+ return next;
1273
+ }
1274
+ const prevRecord = prev;
1275
+ const nextRecord = next;
1276
+ const nextKeys = arrays ? Array.from({ length: next.length }, (_, i) => i) : Object.keys(nextRecord);
1277
+ const prevSize = arrays ? prev.length : Object.keys(prevRecord).length;
1278
+ const copy = arrays ? new Array(nextKeys.length) : Object.create(Object.getPrototypeOf(next));
1279
+ let allFromPrev = prevSize === nextKeys.length;
1280
+ let allFromNext = true;
1281
+ const outerCycleTo = walk.cycleTo;
1282
+ walk.cycleTo = Number.POSITIVE_INFINITY;
1283
+ walk.path.set(nextObject, depth);
1284
+ for (const key of nextKeys) {
1285
+ if (arrays && !Object.hasOwn(nextRecord, key)) {
1286
+ if (Object.hasOwn(prevRecord, key)) allFromPrev = false;
1287
+ continue;
1288
+ }
1289
+ const nextChild = nextRecord[key];
1290
+ const hasPrev = Object.hasOwn(prevRecord, key);
1291
+ const shared = hasPrev ? reconcile(prevRecord[key], nextChild, depth + 1, walk, false) : nextChild;
1292
+ copy[key] = shared;
1293
+ if (!hasPrev || !Object.is(shared, prevRecord[key])) allFromPrev = false;
1294
+ if (!Object.is(shared, nextChild)) allFromNext = false;
1295
+ }
1296
+ walk.path.delete(nextObject);
1297
+ const reached = walk.cycleTo;
1298
+ walk.cycleTo = Math.min(outerCycleTo, reached < depth ? reached : Number.POSITIVE_INFINITY);
1299
+ if (reached <= depth) return next;
1300
+ if (allFromPrev && !newRoot) return prev;
1301
+ if (allFromNext) return next;
1302
+ return copy;
1303
+ }
1304
+ function applyStructuralSharing(option, prev, next, explicit = false) {
1305
+ if (option === false || prev === void 0) return next;
1306
+ const forceNew = explicit && !Object.is(prev, next);
1307
+ if (typeof option === "function") {
1308
+ const shared = option(prev, next);
1309
+ return forceNew && Object.is(shared, prev) ? next : shared;
1310
+ }
1311
+ return reconcile(prev, next, 0, { path: /* @__PURE__ */ new Map(), cycleTo: Number.POSITIVE_INFINITY }, forceNew);
1312
+ }
1313
+
1214
1314
  // src/data/query.ts
1315
+ function shareSafely(option, prev, next, explicit) {
1316
+ const shared = untracked2(
1317
+ () => runSelect("query structuralSharing", () => applyStructuralSharing(option, prev, next, explicit))
1318
+ );
1319
+ return shared.ok ? shared.value : next;
1320
+ }
1321
+ function sharedView(entry) {
1322
+ const raw = entry.data;
1323
+ if (raw === void 0) return void 0;
1324
+ if (entry.hasShared && Object.is(entry.sharedFrom, raw)) return entry.shared;
1325
+ const value = shareSafely(true, entry.hasShared ? entry.shared : void 0, raw, entry.explicit);
1326
+ entry.shared = value;
1327
+ entry.sharedFrom = raw;
1328
+ entry.hasShared = true;
1329
+ return value;
1330
+ }
1215
1331
  var globalQueryCache = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.query.cache.v1"), () => /* @__PURE__ */ new Map());
1216
1332
  function getActiveQueryCache() {
1217
1333
  return getRequestScopedCache("query") ?? globalQueryCache;
@@ -1237,7 +1353,11 @@ function getOrCreateEntry(cache, key, initialData) {
1237
1353
  listeners: /* @__PURE__ */ new Set(),
1238
1354
  refetchers: /* @__PURE__ */ new Set(),
1239
1355
  controller: null,
1240
- generation: 0
1356
+ generation: 0,
1357
+ explicit: false,
1358
+ shared: void 0,
1359
+ sharedFrom: void 0,
1360
+ hasShared: false
1241
1361
  };
1242
1362
  cache.set(key, entry);
1243
1363
  }
@@ -1256,13 +1376,24 @@ function query(key, fetcher, options = {}) {
1256
1376
  onSuccess,
1257
1377
  onError,
1258
1378
  onSettled,
1259
- select
1379
+ select,
1380
+ structuralSharing = true
1260
1381
  } = options;
1261
1382
  const resolveKey = typeof key === "function" ? key : () => key;
1262
1383
  const cache = getActiveQueryCache();
1263
1384
  const [data, setData] = signal(initialData);
1264
1385
  const [isFetching, setIsFetching] = signal(false);
1265
1386
  const [error, setError] = signal(void 0);
1387
+ const [updatedAt, setUpdatedAt] = signal(0);
1388
+ const [base, setBase] = signal(void 0);
1389
+ let baseEntry = null;
1390
+ let baseValue;
1391
+ let baseExplicit = false;
1392
+ let customEntry = null;
1393
+ let customFrom;
1394
+ let customValue;
1395
+ let selectedEntry = null;
1396
+ let selectedValue;
1266
1397
  let disposed = false;
1267
1398
  let currentKey = null;
1268
1399
  let attachedEntry = null;
@@ -1271,11 +1402,57 @@ function query(key, fetcher, options = {}) {
1271
1402
  const loading = derived(() => isFetching() && data() === void 0);
1272
1403
  const isStale = derived(() => {
1273
1404
  data();
1405
+ updatedAt();
1274
1406
  if (!currentKey) return true;
1275
1407
  const entry = cache.get(currentKey);
1276
1408
  if (!entry || entry.dataUpdatedAt === 0) return true;
1277
1409
  return Date.now() - entry.dataUpdatedAt >= staleTime;
1278
1410
  });
1411
+ function viewOf(entry) {
1412
+ const raw = entry.data;
1413
+ if (raw === void 0) return void 0;
1414
+ if (structuralSharing === false) return raw;
1415
+ if (typeof structuralSharing !== "function") return sharedView(entry);
1416
+ const sameEntry = customEntry === entry;
1417
+ if (sameEntry && Object.is(customFrom, raw)) return customValue;
1418
+ const value = shareSafely(structuralSharing, sameEntry ? customValue : void 0, raw, entry.explicit);
1419
+ customEntry = entry;
1420
+ customFrom = raw;
1421
+ customValue = value;
1422
+ return value;
1423
+ }
1424
+ function commitBase(entry, value) {
1425
+ const changed = !Object.is(value, baseValue);
1426
+ baseEntry = entry;
1427
+ baseValue = value;
1428
+ if (!select) {
1429
+ setData(value);
1430
+ return;
1431
+ }
1432
+ if (!changed) return;
1433
+ baseExplicit = entry?.explicit === true;
1434
+ setBase(value);
1435
+ }
1436
+ const stopSelect = select ? effect(() => {
1437
+ const raw = base();
1438
+ const entry = baseEntry;
1439
+ const explicit = baseExplicit;
1440
+ baseExplicit = false;
1441
+ if (entry === null && selectedEntry === null) return;
1442
+ if (raw === void 0) {
1443
+ selectedEntry = entry;
1444
+ selectedValue = void 0;
1445
+ setData(void 0);
1446
+ return;
1447
+ }
1448
+ const selected = runSelect("query select", () => select(raw));
1449
+ if (!selected.ok) return;
1450
+ const prev = selectedEntry === entry ? selectedValue : void 0;
1451
+ const value = shareSafely(structuralSharing, prev, selected.value, explicit);
1452
+ selectedEntry = entry;
1453
+ selectedValue = value;
1454
+ setData(value);
1455
+ }) : null;
1279
1456
  function detachFromEntry() {
1280
1457
  const entry = attachedEntry;
1281
1458
  const key2 = attachedKey;
@@ -1334,7 +1511,7 @@ function query(key, fetcher, options = {}) {
1334
1511
  const settledError = entry.error;
1335
1512
  runCallback("query onError", () => onError?.(settledError));
1336
1513
  } else if (entry.data !== void 0) {
1337
- const settledData = entry.data;
1514
+ const settledData = viewOf(entry);
1338
1515
  runCallback("query onSuccess", () => onSuccess?.(settledData));
1339
1516
  }
1340
1517
  runCallback("query onSettled", () => onSettled?.());
@@ -1365,17 +1542,14 @@ function query(key, fetcher, options = {}) {
1365
1542
  entry.promise = null;
1366
1543
  entry.controller = null;
1367
1544
  entry.data = result;
1545
+ entry.explicit = false;
1368
1546
  entry.dataUpdatedAt = Date.now();
1369
1547
  entry.error = void 0;
1370
1548
  notifyListeners(entry.listeners, "query cache listener");
1371
1549
  if (disposed || currentKey !== key2) return;
1372
- const selected = runSelect("query select", () => select ? select(result) : result);
1373
- batch(() => {
1374
- if (selected.ok) setData(selected.value);
1375
- setIsFetching(false);
1376
- setError(void 0);
1377
- });
1378
- runCallback("query onSuccess", () => onSuccess?.(result));
1550
+ if (!entry.listeners.has(onCacheUpdate)) onCacheUpdate();
1551
+ const committedResult = viewOf(entry);
1552
+ runCallback("query onSuccess", () => onSuccess?.(committedResult));
1379
1553
  } catch (err) {
1380
1554
  if (entry.generation !== generation) return;
1381
1555
  entry.promise = null;
@@ -1405,18 +1579,19 @@ function query(key, fetcher, options = {}) {
1405
1579
  const entry = cache.get(currentKey);
1406
1580
  if (!entry) {
1407
1581
  batch(() => {
1408
- setData(void 0);
1582
+ commitBase(null, void 0);
1409
1583
  setError(void 0);
1410
1584
  setIsFetching(false);
1585
+ setUpdatedAt(0);
1411
1586
  });
1412
1587
  return;
1413
1588
  }
1414
- const raw = entry.data;
1415
- const selected = raw !== void 0 && select ? runSelect("query select", () => select(raw)) : { ok: true, value: raw };
1589
+ const view = viewOf(entry);
1416
1590
  batch(() => {
1417
- if (selected.ok) setData(selected.value);
1591
+ commitBase(entry, view);
1418
1592
  setError(entry.error);
1419
1593
  if (!entry.promise) setIsFetching(false);
1594
+ setUpdatedAt(entry.dataUpdatedAt);
1420
1595
  });
1421
1596
  }
1422
1597
  const effectCleanup = effect(() => {
@@ -1426,11 +1601,11 @@ function query(key, fetcher, options = {}) {
1426
1601
  const entry = getOrCreateEntry(cache, key2, initialData);
1427
1602
  attachToEntry(entry, key2);
1428
1603
  if (entry.data !== void 0) {
1429
- const raw = entry.data;
1430
- const selected = runSelect("query select", () => select ? select(raw) : raw);
1604
+ const view = viewOf(entry);
1431
1605
  batch(() => {
1432
- if (selected.ok) setData(selected.value);
1606
+ commitBase(entry, view);
1433
1607
  setError(entry.error);
1608
+ setUpdatedAt(entry.dataUpdatedAt);
1434
1609
  });
1435
1610
  }
1436
1611
  if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
@@ -1468,6 +1643,7 @@ function query(key, fetcher, options = {}) {
1468
1643
  if (disposed) return;
1469
1644
  disposed = true;
1470
1645
  effectCleanup();
1646
+ stopSelect?.();
1471
1647
  loading.dispose();
1472
1648
  isStale.dispose();
1473
1649
  if (intervalTimer) clearInterval(intervalTimer);
@@ -1499,13 +1675,15 @@ function invalidateQueries(keyOrPredicate) {
1499
1675
  }
1500
1676
  }
1501
1677
  function getQueryData(key) {
1502
- return getActiveQueryCache().get(key)?.data;
1678
+ const entry = getActiveQueryCache().get(key);
1679
+ return entry ? sharedView(entry) : void 0;
1503
1680
  }
1504
1681
  function setQueryData(key, data) {
1505
1682
  const entry = getActiveQueryCache().get(key);
1506
1683
  if (!entry) return;
1507
- const newData = typeof data === "function" ? data(entry.data) : data;
1684
+ const newData = typeof data === "function" ? data(sharedView(entry)) : data;
1508
1685
  entry.data = newData;
1686
+ entry.explicit = true;
1509
1687
  entry.dataUpdatedAt = Date.now();
1510
1688
  notifyListeners(entry.listeners, "query cache listener");
1511
1689
  }
@@ -1555,7 +1733,7 @@ function mutation(mutationFn, options = {}) {
1555
1733
  abortController = new AbortController();
1556
1734
  const signal2 = abortController.signal;
1557
1735
  const myRun = ++runId;
1558
- let context2;
1736
+ let context;
1559
1737
  batch(() => {
1560
1738
  setLoading(true);
1561
1739
  setError(void 0);
@@ -1563,7 +1741,7 @@ function mutation(mutationFn, options = {}) {
1563
1741
  });
1564
1742
  try {
1565
1743
  if (options.onMutate) {
1566
- context2 = await options.onMutate(variables);
1744
+ context = await options.onMutate(variables);
1567
1745
  }
1568
1746
  const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
1569
1747
  if (myRun !== runId) return result;
@@ -1574,8 +1752,8 @@ function mutation(mutationFn, options = {}) {
1574
1752
  setLoading(false);
1575
1753
  setStatus("success");
1576
1754
  });
1577
- runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context2));
1578
- runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context2));
1755
+ runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context));
1756
+ runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context));
1579
1757
  return result;
1580
1758
  } catch (err) {
1581
1759
  if (isAbortError(err)) {
@@ -1597,8 +1775,8 @@ function mutation(mutationFn, options = {}) {
1597
1775
  setLoading(false);
1598
1776
  setStatus("error");
1599
1777
  });
1600
- runCallback("mutation onError", () => options.onError?.(errorObj, variables, context2));
1601
- runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context2));
1778
+ runCallback("mutation onError", () => options.onError?.(errorObj, variables, context));
1779
+ runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context));
1602
1780
  throw errorObj;
1603
1781
  }
1604
1782
  }
@@ -1736,18 +1914,21 @@ function infiniteQuery(key, fetcher, options) {
1736
1914
  }
1737
1915
  });
1738
1916
  function fetchNextPage() {
1917
+ if (disposed) return Promise.resolve();
1739
1918
  if (inFlight) return inFlight;
1740
1919
  const param = nextPageParam();
1741
1920
  if (param === void 0) return Promise.resolve();
1742
1921
  return fetchPage(param, "next");
1743
1922
  }
1744
1923
  function fetchPreviousPage() {
1924
+ if (disposed) return Promise.resolve();
1745
1925
  if (inFlight) return inFlight;
1746
1926
  const param = prevPageParam();
1747
1927
  if (param === void 0) return Promise.resolve();
1748
1928
  return fetchPage(param, "prev");
1749
1929
  }
1750
1930
  async function refetch() {
1931
+ if (disposed) return;
1751
1932
  batch(() => {
1752
1933
  setPages([]);
1753
1934
  setNextPageParam(initialPageParam);
@@ -1831,25 +2012,26 @@ function throttle(getter, interval) {
1831
2012
  let pending = null;
1832
2013
  let lastEmitted = getter();
1833
2014
  let timer = null;
2015
+ function emit(value) {
2016
+ setThrottled(value);
2017
+ lastEmitted = value;
2018
+ cooldown = true;
2019
+ pending = null;
2020
+ timer = setTimeout(endWindow, interval);
2021
+ }
2022
+ function endWindow() {
2023
+ timer = null;
2024
+ if (pending !== null && !Object.is(pending.value, lastEmitted)) {
2025
+ emit(pending.value);
2026
+ return;
2027
+ }
2028
+ pending = null;
2029
+ cooldown = false;
2030
+ }
1834
2031
  const stop = effect(() => {
1835
2032
  const value = getter();
1836
2033
  if (!cooldown) {
1837
- if (!Object.is(value, lastEmitted)) {
1838
- setThrottled(value);
1839
- lastEmitted = value;
1840
- cooldown = true;
1841
- pending = null;
1842
- timer = setTimeout(() => {
1843
- timer = null;
1844
- cooldown = false;
1845
- if (pending !== null) {
1846
- const trailingValue = pending.value;
1847
- pending = null;
1848
- setThrottled(trailingValue);
1849
- lastEmitted = trailingValue;
1850
- }
1851
- }, interval);
1852
- }
2034
+ if (!Object.is(value, lastEmitted)) emit(value);
1853
2035
  } else {
1854
2036
  pending = { value };
1855
2037
  }
@@ -1885,6 +2067,15 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1885
2067
  const [loading, setLoading] = signal(false);
1886
2068
  const [error, setError] = signal(void 0);
1887
2069
  let currentData = options.initialValue;
2070
+ function commitData(next, explicit) {
2071
+ const sharing = options.structuralSharing ?? true;
2072
+ const shared = untracked2(
2073
+ () => runSelect("resource structuralSharing", () => applyStructuralSharing(sharing, currentData, next, explicit))
2074
+ );
2075
+ currentData = shared.ok ? shared.value : next;
2076
+ setData(currentData);
2077
+ return currentData;
2078
+ }
1888
2079
  let abortController = null;
1889
2080
  let disposed = false;
1890
2081
  let effectCleanup = null;
@@ -1904,12 +2095,12 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1904
2095
  try {
1905
2096
  const result = await withRetry(() => fetcher(sourceValue, { signal: signal2, prev }), options.retry, void 0, signal2);
1906
2097
  if (version !== fetchVersion || disposed) return;
1907
- currentData = result;
2098
+ let committed = result;
1908
2099
  batch(() => {
1909
- setData(result);
2100
+ committed = commitData(result, false);
1910
2101
  setLoading(false);
1911
2102
  });
1912
- runCallback("resource onSuccess", () => options.onSuccess?.(result));
2103
+ runCallback("resource onSuccess", () => options.onSuccess?.(committed));
1913
2104
  } catch (err) {
1914
2105
  if (version !== fetchVersion || disposed) return;
1915
2106
  if (isAbortError(err)) {
@@ -1923,7 +2114,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1923
2114
  });
1924
2115
  runCallback("resource onError", () => options.onError?.(errorObj));
1925
2116
  } finally {
1926
- if (version === fetchVersion) {
2117
+ if (!disposed && version === fetchVersion) {
1927
2118
  runCallback("resource onSettled", () => options.onSettled?.());
1928
2119
  }
1929
2120
  }
@@ -1943,8 +2134,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1943
2134
  refetch: () => doFetch(source ? source() : void 0),
1944
2135
  mutate: (value) => {
1945
2136
  const newValue = typeof value === "function" ? value(currentData) : value;
1946
- currentData = newValue;
1947
- setData(newValue);
2137
+ commitData(newValue, true);
1948
2138
  },
1949
2139
  abort: () => abortController?.abort(),
1950
2140
  dispose: () => {
@@ -2229,62 +2419,55 @@ function syncAdapter(config) {
2229
2419
  return config;
2230
2420
  }
2231
2421
 
2232
- // src/core/rendering/context.ts
2233
- function context(defaultValue) {
2234
- const [getValue, setValue] = signal(defaultValue);
2235
- const warnIfSSR = (method) => {
2236
- if (!DEV || !isSSR()) return;
2237
- devWarn(
2238
- `context.${method}() called during SSR. SibuJS context() is application-global \u2014 it is NOT isolated per request, so a concurrent request can observe this value. Do not use context() for request-specific SSR data; pass it explicitly or use request-scoped storage instead.`
2239
- );
2240
- };
2241
- const ctx = {
2242
- provide(value) {
2243
- warnIfSSR("provide");
2244
- const previous2 = getValue();
2245
- setValue(value);
2246
- return () => setValue(previous2);
2247
- },
2248
- use() {
2249
- return getValue;
2250
- },
2251
- get() {
2252
- return getValue();
2253
- },
2254
- set(value) {
2255
- warnIfSSR("set");
2256
- setValue(value);
2257
- },
2258
- withContext(value, fn) {
2259
- warnIfSSR("withContext");
2260
- const previous2 = getValue();
2261
- setValue(value);
2262
- try {
2263
- const result = fn();
2264
- if (DEV && result !== null && typeof result?.then === "function") {
2265
- devWarn(
2266
- "context.withContext() received an async callback. Scoping is synchronous only \u2014 the previous value is restored as soon as the callback returns its promise, so anything after an `await` runs outside the scope."
2267
- );
2268
- }
2269
- return result;
2270
- } finally {
2271
- setValue(previous2);
2272
- }
2273
- }
2274
- };
2275
- return ctx;
2276
- }
2277
-
2278
2422
  // src/data/routeLoader.ts
2279
- var LoaderContext = context(null);
2280
- function executeLoader(loader, context2, options) {
2281
- const res = resource(({ signal: signal2 }) => loader(context2, { signal: signal2 }), options);
2282
- LoaderContext.provide(res);
2423
+ var _clientScope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.v2"), () => ({ stack: [] }));
2424
+ var _disposed = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.disposed.v1"), () => /* @__PURE__ */ new WeakSet());
2425
+ function currentScope() {
2426
+ const cache = getRequestScopedCache("routeLoader");
2427
+ if (!cache) return _clientScope;
2428
+ let scope = cache.get("scope");
2429
+ if (!scope) {
2430
+ scope = { stack: [] };
2431
+ cache.set("scope", scope);
2432
+ }
2433
+ return scope;
2434
+ }
2435
+ function executeLoader(loader, context, options) {
2436
+ const res = resource(({ signal: signal2 }) => loader(context, { signal: signal2 }), options);
2437
+ const disposeResource = res.dispose;
2438
+ res.dispose = () => {
2439
+ _disposed.add(res);
2440
+ disposeResource();
2441
+ };
2283
2442
  return res;
2284
2443
  }
2444
+ function renderWithLoader(loader, render) {
2445
+ if (_disposed.has(loader)) {
2446
+ throw new Error("renderWithLoader: the loader resource has been disposed");
2447
+ }
2448
+ const scope = currentScope();
2449
+ const entry = loader;
2450
+ scope.stack.push(entry);
2451
+ try {
2452
+ return render();
2453
+ } finally {
2454
+ const index = scope.stack.lastIndexOf(entry);
2455
+ if (index !== -1) scope.stack.splice(index, 1);
2456
+ }
2457
+ }
2458
+ function withLoader(loader, context, render, options) {
2459
+ const res = executeLoader(loader, context, options);
2460
+ try {
2461
+ return { view: renderWithLoader(res, render), resource: res };
2462
+ } catch (err) {
2463
+ res.dispose();
2464
+ throw err;
2465
+ }
2466
+ }
2285
2467
  function loaderData() {
2286
- const resource2 = LoaderContext.get();
2287
- if (!resource2) {
2468
+ const scope = currentScope();
2469
+ const resource2 = scope.stack[scope.stack.length - 1];
2470
+ if (!resource2 || _disposed.has(resource2)) {
2288
2471
  throw new Error("loaderData must be used inside a route with a loader");
2289
2472
  }
2290
2473
  return {
@@ -2293,7 +2476,7 @@ function loaderData() {
2293
2476
  error: resource2.error
2294
2477
  };
2295
2478
  }
2296
- async function preloadRoute(route, context2, callerSignal) {
2479
+ async function preloadRoute(route, context, callerSignal) {
2297
2480
  if (!route.loader) return void 0;
2298
2481
  const controller = new AbortController();
2299
2482
  let onAbort = null;
@@ -2306,7 +2489,7 @@ async function preloadRoute(route, context2, callerSignal) {
2306
2489
  }
2307
2490
  }
2308
2491
  try {
2309
- return await route.loader(context2, { signal: controller.signal });
2492
+ return await route.loader(context, { signal: controller.signal });
2310
2493
  } finally {
2311
2494
  if (onAbort) callerSignal?.removeEventListener("abort", onAbort);
2312
2495
  }
@@ -2334,34 +2517,73 @@ function socket(url, options) {
2334
2517
  let heartbeatTimer = null;
2335
2518
  let disposed = false;
2336
2519
  let manuallyClosed = false;
2520
+ let lifecycle = 0;
2337
2521
  function getUrl() {
2338
2522
  return typeof url === "function" ? url() : url;
2339
2523
  }
2340
2524
  function connect() {
2341
2525
  if (disposed) return;
2526
+ const generation = lifecycle;
2527
+ const invalidated = () => disposed || generation !== lifecycle;
2342
2528
  if (typeof WebSocket === "undefined") {
2343
2529
  setStatus("closed");
2344
2530
  return;
2345
2531
  }
2346
- const safeUrl = validateWsUrl(getUrl());
2532
+ let rawUrl;
2533
+ try {
2534
+ rawUrl = getUrl();
2535
+ } catch (error) {
2536
+ setStatus("closed");
2537
+ reportError(error, { phase: "async", name: "socket" });
2538
+ return;
2539
+ }
2540
+ if (invalidated()) {
2541
+ setStatus("closed");
2542
+ return;
2543
+ }
2544
+ const safeUrl = validateWsUrl(rawUrl);
2347
2545
  if (safeUrl === null) {
2348
2546
  setStatus("closed");
2349
2547
  return;
2350
2548
  }
2351
2549
  setStatus("connecting");
2352
- ws = new WebSocket(safeUrl, protocols);
2353
- ws.onopen = () => {
2550
+ if (invalidated()) {
2551
+ setStatus("closed");
2552
+ return;
2553
+ }
2554
+ let instance;
2555
+ try {
2556
+ instance = new WebSocket(safeUrl, protocols);
2557
+ } catch (error) {
2558
+ setStatus("closed");
2559
+ reportError(error, { phase: "async", name: "socket" });
2560
+ return;
2561
+ }
2562
+ if (invalidated()) {
2563
+ instance.close();
2564
+ setStatus("closed");
2565
+ return;
2566
+ }
2567
+ ws = instance;
2568
+ instance.onopen = () => {
2569
+ if (ws !== instance) return;
2570
+ const openGeneration = lifecycle;
2354
2571
  setStatus("open");
2572
+ if (disposed || openGeneration !== lifecycle || ws !== instance) return;
2355
2573
  reconnectCount = 0;
2356
2574
  startHeartbeat();
2357
2575
  };
2358
- ws.onmessage = (event) => {
2576
+ instance.onmessage = (event) => {
2577
+ if (ws !== instance) return;
2359
2578
  setData(event.data);
2360
2579
  };
2361
- ws.onclose = () => {
2580
+ instance.onclose = () => {
2581
+ if (ws !== instance) return;
2582
+ ws = null;
2583
+ const closeGeneration = lifecycle;
2362
2584
  setStatus("closed");
2363
2585
  stopHeartbeat();
2364
- const wasManual = manuallyClosed;
2586
+ const wasManual = manuallyClosed || closeGeneration !== lifecycle;
2365
2587
  manuallyClosed = false;
2366
2588
  if (autoReconnect && !disposed && !wasManual && reconnectCount < maxReconnects) {
2367
2589
  const cap = 3e4;
@@ -2375,7 +2597,7 @@ function socket(url, options) {
2375
2597
  }, jittered);
2376
2598
  }
2377
2599
  };
2378
- ws.onerror = () => {
2600
+ instance.onerror = () => {
2379
2601
  };
2380
2602
  }
2381
2603
  function startHeartbeat() {
@@ -2399,13 +2621,23 @@ function socket(url, options) {
2399
2621
  }
2400
2622
  }
2401
2623
  function close() {
2624
+ lifecycle++;
2402
2625
  manuallyClosed = true;
2403
2626
  if (reconnectTimer !== null) {
2404
2627
  clearTimeout(reconnectTimer);
2405
2628
  reconnectTimer = null;
2406
2629
  }
2407
2630
  stopHeartbeat();
2408
- if (ws) {
2631
+ if (!ws) {
2632
+ setStatus("closed");
2633
+ return;
2634
+ }
2635
+ if (ws.readyState === WebSocket.CLOSED) {
2636
+ ws = null;
2637
+ setStatus("closed");
2638
+ return;
2639
+ }
2640
+ if (ws.readyState !== WebSocket.CLOSING) {
2409
2641
  setStatus("closing");
2410
2642
  ws.close();
2411
2643
  }
@@ -2463,6 +2695,7 @@ function stream(url, options) {
2463
2695
  let disposed = false;
2464
2696
  let reconnectTimer = null;
2465
2697
  let attempts = 0;
2698
+ let closeGeneration = 0;
2466
2699
  function connect() {
2467
2700
  if (disposed) return;
2468
2701
  if (typeof EventSource === "undefined") {
@@ -2474,44 +2707,64 @@ function stream(url, options) {
2474
2707
  setStatus("closed");
2475
2708
  return;
2476
2709
  }
2710
+ const generation = closeGeneration;
2477
2711
  setStatus("connecting");
2478
- source = new EventSource(safeUrl, {
2712
+ if (disposed || generation !== closeGeneration) return;
2713
+ const instance = new EventSource(safeUrl, {
2479
2714
  withCredentials: options?.withCredentials ?? false
2480
2715
  });
2481
- source.onopen = () => {
2716
+ if (disposed || generation !== closeGeneration) {
2717
+ instance.close();
2718
+ return;
2719
+ }
2720
+ source = instance;
2721
+ const live = () => !disposed && source === instance;
2722
+ instance.onopen = () => {
2723
+ if (!live()) return;
2482
2724
  setStatus("open");
2483
2725
  attempts = 0;
2484
2726
  };
2485
- source.onmessage = (evt) => {
2727
+ instance.onmessage = (evt) => {
2728
+ if (!live()) return;
2486
2729
  setData(evt.data);
2487
2730
  setEvent(evt.type);
2488
2731
  };
2489
- source.onerror = () => {
2490
- if (source && source.readyState === EventSource.CLOSED) {
2491
- setStatus("closed");
2492
- source = null;
2493
- if (autoReconnect && !disposed && attempts < maxReconnects) {
2494
- const delay = Math.min(maxMs, baseMs * 2 ** attempts);
2495
- const jittered = delay * (0.5 + Math.random() * 0.5);
2496
- attempts++;
2497
- reconnectTimer = setTimeout(() => {
2498
- reconnectTimer = null;
2499
- connect();
2500
- }, jittered);
2501
- }
2732
+ instance.onerror = () => {
2733
+ if (!live() || instance.readyState !== EventSource.CLOSED) return;
2734
+ detach(instance);
2735
+ source = null;
2736
+ const generation2 = closeGeneration;
2737
+ setStatus("closed");
2738
+ if (disposed || generation2 !== closeGeneration) return;
2739
+ if (autoReconnect && attempts < maxReconnects) {
2740
+ const delay = Math.min(maxMs, baseMs * 2 ** attempts);
2741
+ const jittered = delay * (0.5 + Math.random() * 0.5);
2742
+ attempts++;
2743
+ reconnectTimer = setTimeout(() => {
2744
+ reconnectTimer = null;
2745
+ connect();
2746
+ }, jittered);
2502
2747
  }
2503
2748
  };
2504
2749
  }
2750
+ function detach(instance) {
2751
+ instance.onopen = null;
2752
+ instance.onmessage = null;
2753
+ instance.onerror = null;
2754
+ }
2505
2755
  function close() {
2756
+ closeGeneration++;
2506
2757
  if (reconnectTimer !== null) {
2507
2758
  clearTimeout(reconnectTimer);
2508
2759
  reconnectTimer = null;
2509
2760
  }
2510
- if (source) {
2511
- source.close();
2512
- setStatus("closed");
2761
+ const instance = source;
2762
+ if (instance) {
2513
2763
  source = null;
2764
+ detach(instance);
2765
+ instance.close();
2514
2766
  }
2767
+ setStatus("closed");
2515
2768
  }
2516
2769
  function dispose() {
2517
2770
  disposed = true;
@@ -2536,11 +2789,13 @@ function stream(url, options) {
2536
2789
  preloadRoute,
2537
2790
  previous,
2538
2791
  query,
2792
+ renderWithLoader,
2539
2793
  resource,
2540
2794
  setQueryData,
2541
2795
  socket,
2542
2796
  stream,
2543
2797
  syncAdapter,
2544
2798
  throttle,
2799
+ withLoader,
2545
2800
  withRetry
2546
2801
  });