sibujs 4.5.0 → 4.6.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 (109) hide show
  1. package/dist/browser.cjs +309 -129
  2. package/dist/browser.d.cts +46 -14
  3. package/dist/browser.d.ts +46 -14
  4. package/dist/browser.js +8 -6
  5. package/dist/build.cjs +184 -72
  6. package/dist/build.js +15 -16
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +11 -11
  9. package/dist/cdn.full.global.js +10 -10
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  12. package/dist/chunk-3ISI6ACU.js +44 -0
  13. package/dist/{chunk-ONOHFDLG.js → chunk-47M47FOM.js} +3 -3
  14. package/dist/chunk-5HZXGZ6T.js +24 -0
  15. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  16. package/dist/{chunk-UGRX3S57.js → chunk-5Q4R7HCL.js} +12 -3
  17. package/dist/{chunk-S373NSMK.js → chunk-7GQCWFOE.js} +150 -59
  18. package/dist/{chunk-FTIR4QW2.js → chunk-7NCARGJW.js} +6 -8
  19. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  20. package/dist/{chunk-VZKNK2V7.js → chunk-AIF3Z2T7.js} +296 -38
  21. package/dist/{chunk-4PMLNECI.js → chunk-BAAG6ZTI.js} +98 -29
  22. package/dist/{chunk-XZZOBQAY.js → chunk-D33YTSX5.js} +2 -2
  23. package/dist/{chunk-TBYTO6BS.js → chunk-DKGBBKOF.js} +136 -80
  24. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  25. package/dist/{chunk-GW3SCCZG.js → chunk-HYXDKS4N.js} +153 -110
  26. package/dist/chunk-J6FW5TV6.js +233 -0
  27. package/dist/{chunk-PCT43HW3.js → chunk-JSXPZCET.js} +1 -1
  28. package/dist/chunk-NYNYSPK7.js +318 -0
  29. package/dist/{chunk-RJE2BNI4.js → chunk-ORMZXBKQ.js} +108 -45
  30. package/dist/{chunk-OMJJM3KM.js → chunk-OXUY2A6L.js} +287 -130
  31. package/dist/{chunk-3JZ4L5TJ.js → chunk-PK6FK2G2.js} +181 -66
  32. package/dist/{chunk-RBTPLM32.js → chunk-QE4TTDU3.js} +2 -2
  33. package/dist/{chunk-2DCGACUU.js → chunk-SXXVZMKZ.js} +251 -66
  34. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  35. package/dist/{chunk-7LN645I6.js → chunk-UZQ6ALFS.js} +3 -3
  36. package/dist/{chunk-KEISJXBU.js → chunk-VVWPJ543.js} +6 -8
  37. package/dist/{chunk-RIXRAYIU.js → chunk-XC4MEKGA.js} +3 -3
  38. package/dist/{chunk-B3WHI2QA.js → chunk-XRRZKZYX.js} +55 -31
  39. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  40. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  41. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  42. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  43. package/dist/data.cjs +213 -117
  44. package/dist/data.d.cts +47 -9
  45. package/dist/data.d.ts +47 -9
  46. package/dist/data.js +12 -9
  47. package/dist/devtools.cjs +81 -57
  48. package/dist/devtools.js +7 -8
  49. package/dist/dispose-GEIG2KOF.js +28 -0
  50. package/dist/ecosystem.cjs +309 -104
  51. package/dist/ecosystem.d.cts +19 -3
  52. package/dist/ecosystem.d.ts +19 -3
  53. package/dist/ecosystem.js +12 -12
  54. package/dist/extras.cjs +2133 -863
  55. package/dist/extras.d.cts +10 -9
  56. package/dist/extras.d.ts +10 -9
  57. package/dist/extras.js +39 -28
  58. package/dist/index.cjs +184 -72
  59. package/dist/index.d.cts +22 -5
  60. package/dist/index.d.ts +22 -5
  61. package/dist/index.js +24 -27
  62. package/dist/motion.cjs +107 -41
  63. package/dist/motion.js +5 -5
  64. package/dist/patterns.cjs +317 -39
  65. package/dist/patterns.d.cts +27 -9
  66. package/dist/patterns.d.ts +27 -9
  67. package/dist/patterns.js +8 -8
  68. package/dist/performance.cjs +308 -216
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +8 -9
  72. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  73. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  74. package/dist/plugins.cjs +549 -202
  75. package/dist/plugins.d.cts +3 -3
  76. package/dist/plugins.d.ts +3 -3
  77. package/dist/plugins.js +35 -23
  78. package/dist/signal-EotCj4hS.d.cts +110 -0
  79. package/dist/signal-EotCj4hS.d.ts +110 -0
  80. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  81. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  82. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  83. package/dist/ssr.cjs +176 -72
  84. package/dist/ssr.d.cts +9 -3
  85. package/dist/ssr.d.ts +9 -3
  86. package/dist/ssr.js +11 -12
  87. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  88. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  89. package/dist/{tagFactory-DVoDpHye.d.ts → tagFactory-DFstCLQV.d.cts} +2 -100
  90. package/dist/{tagFactory-DVoDpHye.d.cts → tagFactory-DkaNVUNV.d.ts} +2 -100
  91. package/dist/testing.cjs +2484 -2185
  92. package/dist/testing.d.cts +56 -5
  93. package/dist/testing.d.ts +56 -5
  94. package/dist/testing.js +580 -307
  95. package/dist/ui.cjs +707 -317
  96. package/dist/ui.d.cts +40 -7
  97. package/dist/ui.d.ts +40 -7
  98. package/dist/ui.js +151 -56
  99. package/dist/widgets.cjs +246 -278
  100. package/dist/widgets.js +9 -10
  101. package/package.json +1 -1
  102. package/dist/chunk-2WLZ6757.js +0 -149
  103. package/dist/chunk-CCSJMTRN.js +0 -15
  104. package/dist/chunk-QKRPLZ2V.js +0 -108
  105. package/dist/chunk-VUF4ALSW.js +0 -60
  106. package/dist/chunk-WWV3SJ3L.js +0 -131
  107. package/dist/dispose-46BOMMQJ.js +0 -19
  108. package/dist/plugin-D30wlGW5.d.cts +0 -71
  109. package/dist/plugin-D30wlGW5.d.ts +0 -71
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.6.0" : "dev";
637
664
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
638
665
  function resolveReactiveApi() {
639
666
  const g = globalThis;
@@ -1555,7 +1582,7 @@ function mutation(mutationFn, options = {}) {
1555
1582
  abortController = new AbortController();
1556
1583
  const signal2 = abortController.signal;
1557
1584
  const myRun = ++runId;
1558
- let context2;
1585
+ let context;
1559
1586
  batch(() => {
1560
1587
  setLoading(true);
1561
1588
  setError(void 0);
@@ -1563,7 +1590,7 @@ function mutation(mutationFn, options = {}) {
1563
1590
  });
1564
1591
  try {
1565
1592
  if (options.onMutate) {
1566
- context2 = await options.onMutate(variables);
1593
+ context = await options.onMutate(variables);
1567
1594
  }
1568
1595
  const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
1569
1596
  if (myRun !== runId) return result;
@@ -1574,8 +1601,8 @@ function mutation(mutationFn, options = {}) {
1574
1601
  setLoading(false);
1575
1602
  setStatus("success");
1576
1603
  });
1577
- runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context2));
1578
- runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context2));
1604
+ runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context));
1605
+ runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context));
1579
1606
  return result;
1580
1607
  } catch (err) {
1581
1608
  if (isAbortError(err)) {
@@ -1597,8 +1624,8 @@ function mutation(mutationFn, options = {}) {
1597
1624
  setLoading(false);
1598
1625
  setStatus("error");
1599
1626
  });
1600
- runCallback("mutation onError", () => options.onError?.(errorObj, variables, context2));
1601
- runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context2));
1627
+ runCallback("mutation onError", () => options.onError?.(errorObj, variables, context));
1628
+ runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context));
1602
1629
  throw errorObj;
1603
1630
  }
1604
1631
  }
@@ -1736,18 +1763,21 @@ function infiniteQuery(key, fetcher, options) {
1736
1763
  }
1737
1764
  });
1738
1765
  function fetchNextPage() {
1766
+ if (disposed) return Promise.resolve();
1739
1767
  if (inFlight) return inFlight;
1740
1768
  const param = nextPageParam();
1741
1769
  if (param === void 0) return Promise.resolve();
1742
1770
  return fetchPage(param, "next");
1743
1771
  }
1744
1772
  function fetchPreviousPage() {
1773
+ if (disposed) return Promise.resolve();
1745
1774
  if (inFlight) return inFlight;
1746
1775
  const param = prevPageParam();
1747
1776
  if (param === void 0) return Promise.resolve();
1748
1777
  return fetchPage(param, "prev");
1749
1778
  }
1750
1779
  async function refetch() {
1780
+ if (disposed) return;
1751
1781
  batch(() => {
1752
1782
  setPages([]);
1753
1783
  setNextPageParam(initialPageParam);
@@ -1831,25 +1861,26 @@ function throttle(getter, interval) {
1831
1861
  let pending = null;
1832
1862
  let lastEmitted = getter();
1833
1863
  let timer = null;
1864
+ function emit(value) {
1865
+ setThrottled(value);
1866
+ lastEmitted = value;
1867
+ cooldown = true;
1868
+ pending = null;
1869
+ timer = setTimeout(endWindow, interval);
1870
+ }
1871
+ function endWindow() {
1872
+ timer = null;
1873
+ if (pending !== null && !Object.is(pending.value, lastEmitted)) {
1874
+ emit(pending.value);
1875
+ return;
1876
+ }
1877
+ pending = null;
1878
+ cooldown = false;
1879
+ }
1834
1880
  const stop = effect(() => {
1835
1881
  const value = getter();
1836
1882
  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
- }
1883
+ if (!Object.is(value, lastEmitted)) emit(value);
1853
1884
  } else {
1854
1885
  pending = { value };
1855
1886
  }
@@ -1923,7 +1954,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1923
1954
  });
1924
1955
  runCallback("resource onError", () => options.onError?.(errorObj));
1925
1956
  } finally {
1926
- if (version === fetchVersion) {
1957
+ if (!disposed && version === fetchVersion) {
1927
1958
  runCallback("resource onSettled", () => options.onSettled?.());
1928
1959
  }
1929
1960
  }
@@ -2229,62 +2260,55 @@ function syncAdapter(config) {
2229
2260
  return config;
2230
2261
  }
2231
2262
 
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
2263
  // 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);
2264
+ var _clientScope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.v2"), () => ({ stack: [] }));
2265
+ var _disposed = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.disposed.v1"), () => /* @__PURE__ */ new WeakSet());
2266
+ function currentScope() {
2267
+ const cache = getRequestScopedCache("routeLoader");
2268
+ if (!cache) return _clientScope;
2269
+ let scope = cache.get("scope");
2270
+ if (!scope) {
2271
+ scope = { stack: [] };
2272
+ cache.set("scope", scope);
2273
+ }
2274
+ return scope;
2275
+ }
2276
+ function executeLoader(loader, context, options) {
2277
+ const res = resource(({ signal: signal2 }) => loader(context, { signal: signal2 }), options);
2278
+ const disposeResource = res.dispose;
2279
+ res.dispose = () => {
2280
+ _disposed.add(res);
2281
+ disposeResource();
2282
+ };
2283
2283
  return res;
2284
2284
  }
2285
+ function renderWithLoader(loader, render) {
2286
+ if (_disposed.has(loader)) {
2287
+ throw new Error("renderWithLoader: the loader resource has been disposed");
2288
+ }
2289
+ const scope = currentScope();
2290
+ const entry = loader;
2291
+ scope.stack.push(entry);
2292
+ try {
2293
+ return render();
2294
+ } finally {
2295
+ const index = scope.stack.lastIndexOf(entry);
2296
+ if (index !== -1) scope.stack.splice(index, 1);
2297
+ }
2298
+ }
2299
+ function withLoader(loader, context, render, options) {
2300
+ const res = executeLoader(loader, context, options);
2301
+ try {
2302
+ return { view: renderWithLoader(res, render), resource: res };
2303
+ } catch (err) {
2304
+ res.dispose();
2305
+ throw err;
2306
+ }
2307
+ }
2285
2308
  function loaderData() {
2286
- const resource2 = LoaderContext.get();
2287
- if (!resource2) {
2309
+ const scope = currentScope();
2310
+ const resource2 = scope.stack[scope.stack.length - 1];
2311
+ if (!resource2 || _disposed.has(resource2)) {
2288
2312
  throw new Error("loaderData must be used inside a route with a loader");
2289
2313
  }
2290
2314
  return {
@@ -2293,7 +2317,7 @@ function loaderData() {
2293
2317
  error: resource2.error
2294
2318
  };
2295
2319
  }
2296
- async function preloadRoute(route, context2, callerSignal) {
2320
+ async function preloadRoute(route, context, callerSignal) {
2297
2321
  if (!route.loader) return void 0;
2298
2322
  const controller = new AbortController();
2299
2323
  let onAbort = null;
@@ -2306,7 +2330,7 @@ async function preloadRoute(route, context2, callerSignal) {
2306
2330
  }
2307
2331
  }
2308
2332
  try {
2309
- return await route.loader(context2, { signal: controller.signal });
2333
+ return await route.loader(context, { signal: controller.signal });
2310
2334
  } finally {
2311
2335
  if (onAbort) callerSignal?.removeEventListener("abort", onAbort);
2312
2336
  }
@@ -2334,34 +2358,73 @@ function socket(url, options) {
2334
2358
  let heartbeatTimer = null;
2335
2359
  let disposed = false;
2336
2360
  let manuallyClosed = false;
2361
+ let lifecycle = 0;
2337
2362
  function getUrl() {
2338
2363
  return typeof url === "function" ? url() : url;
2339
2364
  }
2340
2365
  function connect() {
2341
2366
  if (disposed) return;
2367
+ const generation = lifecycle;
2368
+ const invalidated = () => disposed || generation !== lifecycle;
2342
2369
  if (typeof WebSocket === "undefined") {
2343
2370
  setStatus("closed");
2344
2371
  return;
2345
2372
  }
2346
- const safeUrl = validateWsUrl(getUrl());
2373
+ let rawUrl;
2374
+ try {
2375
+ rawUrl = getUrl();
2376
+ } catch (error) {
2377
+ setStatus("closed");
2378
+ reportError(error, { phase: "async", name: "socket" });
2379
+ return;
2380
+ }
2381
+ if (invalidated()) {
2382
+ setStatus("closed");
2383
+ return;
2384
+ }
2385
+ const safeUrl = validateWsUrl(rawUrl);
2347
2386
  if (safeUrl === null) {
2348
2387
  setStatus("closed");
2349
2388
  return;
2350
2389
  }
2351
2390
  setStatus("connecting");
2352
- ws = new WebSocket(safeUrl, protocols);
2353
- ws.onopen = () => {
2391
+ if (invalidated()) {
2392
+ setStatus("closed");
2393
+ return;
2394
+ }
2395
+ let instance;
2396
+ try {
2397
+ instance = new WebSocket(safeUrl, protocols);
2398
+ } catch (error) {
2399
+ setStatus("closed");
2400
+ reportError(error, { phase: "async", name: "socket" });
2401
+ return;
2402
+ }
2403
+ if (invalidated()) {
2404
+ instance.close();
2405
+ setStatus("closed");
2406
+ return;
2407
+ }
2408
+ ws = instance;
2409
+ instance.onopen = () => {
2410
+ if (ws !== instance) return;
2411
+ const openGeneration = lifecycle;
2354
2412
  setStatus("open");
2413
+ if (disposed || openGeneration !== lifecycle || ws !== instance) return;
2355
2414
  reconnectCount = 0;
2356
2415
  startHeartbeat();
2357
2416
  };
2358
- ws.onmessage = (event) => {
2417
+ instance.onmessage = (event) => {
2418
+ if (ws !== instance) return;
2359
2419
  setData(event.data);
2360
2420
  };
2361
- ws.onclose = () => {
2421
+ instance.onclose = () => {
2422
+ if (ws !== instance) return;
2423
+ ws = null;
2424
+ const closeGeneration = lifecycle;
2362
2425
  setStatus("closed");
2363
2426
  stopHeartbeat();
2364
- const wasManual = manuallyClosed;
2427
+ const wasManual = manuallyClosed || closeGeneration !== lifecycle;
2365
2428
  manuallyClosed = false;
2366
2429
  if (autoReconnect && !disposed && !wasManual && reconnectCount < maxReconnects) {
2367
2430
  const cap = 3e4;
@@ -2375,7 +2438,7 @@ function socket(url, options) {
2375
2438
  }, jittered);
2376
2439
  }
2377
2440
  };
2378
- ws.onerror = () => {
2441
+ instance.onerror = () => {
2379
2442
  };
2380
2443
  }
2381
2444
  function startHeartbeat() {
@@ -2399,13 +2462,23 @@ function socket(url, options) {
2399
2462
  }
2400
2463
  }
2401
2464
  function close() {
2465
+ lifecycle++;
2402
2466
  manuallyClosed = true;
2403
2467
  if (reconnectTimer !== null) {
2404
2468
  clearTimeout(reconnectTimer);
2405
2469
  reconnectTimer = null;
2406
2470
  }
2407
2471
  stopHeartbeat();
2408
- if (ws) {
2472
+ if (!ws) {
2473
+ setStatus("closed");
2474
+ return;
2475
+ }
2476
+ if (ws.readyState === WebSocket.CLOSED) {
2477
+ ws = null;
2478
+ setStatus("closed");
2479
+ return;
2480
+ }
2481
+ if (ws.readyState !== WebSocket.CLOSING) {
2409
2482
  setStatus("closing");
2410
2483
  ws.close();
2411
2484
  }
@@ -2463,6 +2536,7 @@ function stream(url, options) {
2463
2536
  let disposed = false;
2464
2537
  let reconnectTimer = null;
2465
2538
  let attempts = 0;
2539
+ let closeGeneration = 0;
2466
2540
  function connect() {
2467
2541
  if (disposed) return;
2468
2542
  if (typeof EventSource === "undefined") {
@@ -2474,44 +2548,64 @@ function stream(url, options) {
2474
2548
  setStatus("closed");
2475
2549
  return;
2476
2550
  }
2551
+ const generation = closeGeneration;
2477
2552
  setStatus("connecting");
2478
- source = new EventSource(safeUrl, {
2553
+ if (disposed || generation !== closeGeneration) return;
2554
+ const instance = new EventSource(safeUrl, {
2479
2555
  withCredentials: options?.withCredentials ?? false
2480
2556
  });
2481
- source.onopen = () => {
2557
+ if (disposed || generation !== closeGeneration) {
2558
+ instance.close();
2559
+ return;
2560
+ }
2561
+ source = instance;
2562
+ const live = () => !disposed && source === instance;
2563
+ instance.onopen = () => {
2564
+ if (!live()) return;
2482
2565
  setStatus("open");
2483
2566
  attempts = 0;
2484
2567
  };
2485
- source.onmessage = (evt) => {
2568
+ instance.onmessage = (evt) => {
2569
+ if (!live()) return;
2486
2570
  setData(evt.data);
2487
2571
  setEvent(evt.type);
2488
2572
  };
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
- }
2573
+ instance.onerror = () => {
2574
+ if (!live() || instance.readyState !== EventSource.CLOSED) return;
2575
+ detach(instance);
2576
+ source = null;
2577
+ const generation2 = closeGeneration;
2578
+ setStatus("closed");
2579
+ if (disposed || generation2 !== closeGeneration) return;
2580
+ if (autoReconnect && attempts < maxReconnects) {
2581
+ const delay = Math.min(maxMs, baseMs * 2 ** attempts);
2582
+ const jittered = delay * (0.5 + Math.random() * 0.5);
2583
+ attempts++;
2584
+ reconnectTimer = setTimeout(() => {
2585
+ reconnectTimer = null;
2586
+ connect();
2587
+ }, jittered);
2502
2588
  }
2503
2589
  };
2504
2590
  }
2591
+ function detach(instance) {
2592
+ instance.onopen = null;
2593
+ instance.onmessage = null;
2594
+ instance.onerror = null;
2595
+ }
2505
2596
  function close() {
2597
+ closeGeneration++;
2506
2598
  if (reconnectTimer !== null) {
2507
2599
  clearTimeout(reconnectTimer);
2508
2600
  reconnectTimer = null;
2509
2601
  }
2510
- if (source) {
2511
- source.close();
2512
- setStatus("closed");
2602
+ const instance = source;
2603
+ if (instance) {
2513
2604
  source = null;
2605
+ detach(instance);
2606
+ instance.close();
2514
2607
  }
2608
+ setStatus("closed");
2515
2609
  }
2516
2610
  function dispose() {
2517
2611
  disposed = true;
@@ -2536,11 +2630,13 @@ function stream(url, options) {
2536
2630
  preloadRoute,
2537
2631
  previous,
2538
2632
  query,
2633
+ renderWithLoader,
2539
2634
  resource,
2540
2635
  setQueryData,
2541
2636
  socket,
2542
2637
  stream,
2543
2638
  syncAdapter,
2544
2639
  throttle,
2640
+ withLoader,
2545
2641
  withRetry
2546
2642
  });