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
@@ -1,25 +1,28 @@
1
- import {
2
- context
3
- } from "./chunk-VUF4ALSW.js";
4
1
  import {
5
2
  derived
6
- } from "./chunk-RBTPLM32.js";
3
+ } from "./chunk-RUSSKG6G.js";
7
4
  import {
8
5
  sanitizeUrl
9
- } from "./chunk-IXKSNWV5.js";
6
+ } from "./chunk-2INLLLMZ.js";
10
7
  import {
11
8
  globalSingleton
12
9
  } from "./chunk-7ZHH77QA.js";
13
10
  import {
14
11
  effect
15
- } from "./chunk-RIXRAYIU.js";
12
+ } from "./chunk-WEQ3DMVL.js";
16
13
  import {
17
14
  getRequestScopedCache
18
- } from "./chunk-5DXA2J44.js";
15
+ } from "./chunk-7XHATCIH.js";
19
16
  import {
20
17
  batch,
21
18
  signal
22
- } from "./chunk-XZZOBQAY.js";
19
+ } from "./chunk-FQRUXCEE.js";
20
+ import {
21
+ untracked
22
+ } from "./chunk-JWKYU5GV.js";
23
+ import {
24
+ reportError
25
+ } from "./chunk-NYNYSPK7.js";
23
26
 
24
27
  // src/data/abort.ts
25
28
  function isAbortError(error) {
@@ -132,7 +135,83 @@ function waitForRetryDelay(delay, signal2) {
132
135
  });
133
136
  }
134
137
 
138
+ // src/data/structuralSharing.ts
139
+ function isPlainObject(value) {
140
+ if (value === null || typeof value !== "object") return false;
141
+ const proto = Object.getPrototypeOf(value);
142
+ return proto === Object.prototype || proto === null;
143
+ }
144
+ function isPlainArray(value) {
145
+ return Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype;
146
+ }
147
+ function reconcile(prev, next, depth, walk, newRoot) {
148
+ if (Object.is(prev, next)) return prev;
149
+ const arrays = isPlainArray(prev) && isPlainArray(next);
150
+ if (!arrays && !(isPlainObject(prev) && isPlainObject(next))) return next;
151
+ if (Object.getPrototypeOf(prev) !== Object.getPrototypeOf(next)) return next;
152
+ const nextObject = next;
153
+ const seenAt = walk.path.get(nextObject);
154
+ if (seenAt !== void 0) {
155
+ walk.cycleTo = Math.min(walk.cycleTo, seenAt);
156
+ return next;
157
+ }
158
+ const prevRecord = prev;
159
+ const nextRecord = next;
160
+ const nextKeys = arrays ? Array.from({ length: next.length }, (_, i) => i) : Object.keys(nextRecord);
161
+ const prevSize = arrays ? prev.length : Object.keys(prevRecord).length;
162
+ const copy = arrays ? new Array(nextKeys.length) : Object.create(Object.getPrototypeOf(next));
163
+ let allFromPrev = prevSize === nextKeys.length;
164
+ let allFromNext = true;
165
+ const outerCycleTo = walk.cycleTo;
166
+ walk.cycleTo = Number.POSITIVE_INFINITY;
167
+ walk.path.set(nextObject, depth);
168
+ for (const key of nextKeys) {
169
+ if (arrays && !Object.hasOwn(nextRecord, key)) {
170
+ if (Object.hasOwn(prevRecord, key)) allFromPrev = false;
171
+ continue;
172
+ }
173
+ const nextChild = nextRecord[key];
174
+ const hasPrev = Object.hasOwn(prevRecord, key);
175
+ const shared = hasPrev ? reconcile(prevRecord[key], nextChild, depth + 1, walk, false) : nextChild;
176
+ copy[key] = shared;
177
+ if (!hasPrev || !Object.is(shared, prevRecord[key])) allFromPrev = false;
178
+ if (!Object.is(shared, nextChild)) allFromNext = false;
179
+ }
180
+ walk.path.delete(nextObject);
181
+ const reached = walk.cycleTo;
182
+ walk.cycleTo = Math.min(outerCycleTo, reached < depth ? reached : Number.POSITIVE_INFINITY);
183
+ if (reached <= depth) return next;
184
+ if (allFromPrev && !newRoot) return prev;
185
+ if (allFromNext) return next;
186
+ return copy;
187
+ }
188
+ function applyStructuralSharing(option, prev, next, explicit = false) {
189
+ if (option === false || prev === void 0) return next;
190
+ const forceNew = explicit && !Object.is(prev, next);
191
+ if (typeof option === "function") {
192
+ const shared = option(prev, next);
193
+ return forceNew && Object.is(shared, prev) ? next : shared;
194
+ }
195
+ return reconcile(prev, next, 0, { path: /* @__PURE__ */ new Map(), cycleTo: Number.POSITIVE_INFINITY }, forceNew);
196
+ }
197
+
135
198
  // src/data/query.ts
199
+ function shareSafely(option, prev, next, explicit) {
200
+ const shared = untracked(
201
+ () => runSelect("query structuralSharing", () => applyStructuralSharing(option, prev, next, explicit))
202
+ );
203
+ return shared.ok ? shared.value : next;
204
+ }
205
+ function sharedView(entry) {
206
+ const raw = entry.data;
207
+ if (raw === void 0) return void 0;
208
+ if (entry.hasShared && Object.is(entry.sharedFrom, raw)) return entry.shared;
209
+ const value = shareSafely(true, entry.hasShared ? entry.shared : void 0, raw, entry.explicit);
210
+ entry.shared = value;
211
+ entry.sharedFrom = raw;
212
+ entry.hasShared = true;
213
+ return value;
214
+ }
136
215
  var globalQueryCache = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.query.cache.v1"), () => /* @__PURE__ */ new Map());
137
216
  function getActiveQueryCache() {
138
217
  return getRequestScopedCache("query") ?? globalQueryCache;
@@ -158,7 +237,11 @@ function getOrCreateEntry(cache, key, initialData) {
158
237
  listeners: /* @__PURE__ */ new Set(),
159
238
  refetchers: /* @__PURE__ */ new Set(),
160
239
  controller: null,
161
- generation: 0
240
+ generation: 0,
241
+ explicit: false,
242
+ shared: void 0,
243
+ sharedFrom: void 0,
244
+ hasShared: false
162
245
  };
163
246
  cache.set(key, entry);
164
247
  }
@@ -177,13 +260,24 @@ function query(key, fetcher, options = {}) {
177
260
  onSuccess,
178
261
  onError,
179
262
  onSettled,
180
- select
263
+ select,
264
+ structuralSharing = true
181
265
  } = options;
182
266
  const resolveKey = typeof key === "function" ? key : () => key;
183
267
  const cache = getActiveQueryCache();
184
268
  const [data, setData] = signal(initialData);
185
269
  const [isFetching, setIsFetching] = signal(false);
186
270
  const [error, setError] = signal(void 0);
271
+ const [updatedAt, setUpdatedAt] = signal(0);
272
+ const [base, setBase] = signal(void 0);
273
+ let baseEntry = null;
274
+ let baseValue;
275
+ let baseExplicit = false;
276
+ let customEntry = null;
277
+ let customFrom;
278
+ let customValue;
279
+ let selectedEntry = null;
280
+ let selectedValue;
187
281
  let disposed = false;
188
282
  let currentKey = null;
189
283
  let attachedEntry = null;
@@ -192,11 +286,57 @@ function query(key, fetcher, options = {}) {
192
286
  const loading = derived(() => isFetching() && data() === void 0);
193
287
  const isStale = derived(() => {
194
288
  data();
289
+ updatedAt();
195
290
  if (!currentKey) return true;
196
291
  const entry = cache.get(currentKey);
197
292
  if (!entry || entry.dataUpdatedAt === 0) return true;
198
293
  return Date.now() - entry.dataUpdatedAt >= staleTime;
199
294
  });
295
+ function viewOf(entry) {
296
+ const raw = entry.data;
297
+ if (raw === void 0) return void 0;
298
+ if (structuralSharing === false) return raw;
299
+ if (typeof structuralSharing !== "function") return sharedView(entry);
300
+ const sameEntry = customEntry === entry;
301
+ if (sameEntry && Object.is(customFrom, raw)) return customValue;
302
+ const value = shareSafely(structuralSharing, sameEntry ? customValue : void 0, raw, entry.explicit);
303
+ customEntry = entry;
304
+ customFrom = raw;
305
+ customValue = value;
306
+ return value;
307
+ }
308
+ function commitBase(entry, value) {
309
+ const changed = !Object.is(value, baseValue);
310
+ baseEntry = entry;
311
+ baseValue = value;
312
+ if (!select) {
313
+ setData(value);
314
+ return;
315
+ }
316
+ if (!changed) return;
317
+ baseExplicit = entry?.explicit === true;
318
+ setBase(value);
319
+ }
320
+ const stopSelect = select ? effect(() => {
321
+ const raw = base();
322
+ const entry = baseEntry;
323
+ const explicit = baseExplicit;
324
+ baseExplicit = false;
325
+ if (entry === null && selectedEntry === null) return;
326
+ if (raw === void 0) {
327
+ selectedEntry = entry;
328
+ selectedValue = void 0;
329
+ setData(void 0);
330
+ return;
331
+ }
332
+ const selected = runSelect("query select", () => select(raw));
333
+ if (!selected.ok) return;
334
+ const prev = selectedEntry === entry ? selectedValue : void 0;
335
+ const value = shareSafely(structuralSharing, prev, selected.value, explicit);
336
+ selectedEntry = entry;
337
+ selectedValue = value;
338
+ setData(value);
339
+ }) : null;
200
340
  function detachFromEntry() {
201
341
  const entry = attachedEntry;
202
342
  const key2 = attachedKey;
@@ -255,7 +395,7 @@ function query(key, fetcher, options = {}) {
255
395
  const settledError = entry.error;
256
396
  runCallback("query onError", () => onError?.(settledError));
257
397
  } else if (entry.data !== void 0) {
258
- const settledData = entry.data;
398
+ const settledData = viewOf(entry);
259
399
  runCallback("query onSuccess", () => onSuccess?.(settledData));
260
400
  }
261
401
  runCallback("query onSettled", () => onSettled?.());
@@ -286,17 +426,14 @@ function query(key, fetcher, options = {}) {
286
426
  entry.promise = null;
287
427
  entry.controller = null;
288
428
  entry.data = result;
429
+ entry.explicit = false;
289
430
  entry.dataUpdatedAt = Date.now();
290
431
  entry.error = void 0;
291
432
  notifyListeners(entry.listeners, "query cache listener");
292
433
  if (disposed || currentKey !== key2) return;
293
- const selected = runSelect("query select", () => select ? select(result) : result);
294
- batch(() => {
295
- if (selected.ok) setData(selected.value);
296
- setIsFetching(false);
297
- setError(void 0);
298
- });
299
- runCallback("query onSuccess", () => onSuccess?.(result));
434
+ if (!entry.listeners.has(onCacheUpdate)) onCacheUpdate();
435
+ const committedResult = viewOf(entry);
436
+ runCallback("query onSuccess", () => onSuccess?.(committedResult));
300
437
  } catch (err) {
301
438
  if (entry.generation !== generation) return;
302
439
  entry.promise = null;
@@ -326,18 +463,19 @@ function query(key, fetcher, options = {}) {
326
463
  const entry = cache.get(currentKey);
327
464
  if (!entry) {
328
465
  batch(() => {
329
- setData(void 0);
466
+ commitBase(null, void 0);
330
467
  setError(void 0);
331
468
  setIsFetching(false);
469
+ setUpdatedAt(0);
332
470
  });
333
471
  return;
334
472
  }
335
- const raw = entry.data;
336
- const selected = raw !== void 0 && select ? runSelect("query select", () => select(raw)) : { ok: true, value: raw };
473
+ const view = viewOf(entry);
337
474
  batch(() => {
338
- if (selected.ok) setData(selected.value);
475
+ commitBase(entry, view);
339
476
  setError(entry.error);
340
477
  if (!entry.promise) setIsFetching(false);
478
+ setUpdatedAt(entry.dataUpdatedAt);
341
479
  });
342
480
  }
343
481
  const effectCleanup = effect(() => {
@@ -347,11 +485,11 @@ function query(key, fetcher, options = {}) {
347
485
  const entry = getOrCreateEntry(cache, key2, initialData);
348
486
  attachToEntry(entry, key2);
349
487
  if (entry.data !== void 0) {
350
- const raw = entry.data;
351
- const selected = runSelect("query select", () => select ? select(raw) : raw);
488
+ const view = viewOf(entry);
352
489
  batch(() => {
353
- if (selected.ok) setData(selected.value);
490
+ commitBase(entry, view);
354
491
  setError(entry.error);
492
+ setUpdatedAt(entry.dataUpdatedAt);
355
493
  });
356
494
  }
357
495
  if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
@@ -389,6 +527,7 @@ function query(key, fetcher, options = {}) {
389
527
  if (disposed) return;
390
528
  disposed = true;
391
529
  effectCleanup();
530
+ stopSelect?.();
392
531
  loading.dispose();
393
532
  isStale.dispose();
394
533
  if (intervalTimer) clearInterval(intervalTimer);
@@ -420,13 +559,15 @@ function invalidateQueries(keyOrPredicate) {
420
559
  }
421
560
  }
422
561
  function getQueryData(key) {
423
- return getActiveQueryCache().get(key)?.data;
562
+ const entry = getActiveQueryCache().get(key);
563
+ return entry ? sharedView(entry) : void 0;
424
564
  }
425
565
  function setQueryData(key, data) {
426
566
  const entry = getActiveQueryCache().get(key);
427
567
  if (!entry) return;
428
- const newData = typeof data === "function" ? data(entry.data) : data;
568
+ const newData = typeof data === "function" ? data(sharedView(entry)) : data;
429
569
  entry.data = newData;
570
+ entry.explicit = true;
430
571
  entry.dataUpdatedAt = Date.now();
431
572
  notifyListeners(entry.listeners, "query cache listener");
432
573
  }
@@ -476,7 +617,7 @@ function mutation(mutationFn, options = {}) {
476
617
  abortController = new AbortController();
477
618
  const signal2 = abortController.signal;
478
619
  const myRun = ++runId;
479
- let context2;
620
+ let context;
480
621
  batch(() => {
481
622
  setLoading(true);
482
623
  setError(void 0);
@@ -484,7 +625,7 @@ function mutation(mutationFn, options = {}) {
484
625
  });
485
626
  try {
486
627
  if (options.onMutate) {
487
- context2 = await options.onMutate(variables);
628
+ context = await options.onMutate(variables);
488
629
  }
489
630
  const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
490
631
  if (myRun !== runId) return result;
@@ -495,8 +636,8 @@ function mutation(mutationFn, options = {}) {
495
636
  setLoading(false);
496
637
  setStatus("success");
497
638
  });
498
- runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context2));
499
- runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context2));
639
+ runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context));
640
+ runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context));
500
641
  return result;
501
642
  } catch (err) {
502
643
  if (isAbortError(err)) {
@@ -518,8 +659,8 @@ function mutation(mutationFn, options = {}) {
518
659
  setLoading(false);
519
660
  setStatus("error");
520
661
  });
521
- runCallback("mutation onError", () => options.onError?.(errorObj, variables, context2));
522
- runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context2));
662
+ runCallback("mutation onError", () => options.onError?.(errorObj, variables, context));
663
+ runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context));
523
664
  throw errorObj;
524
665
  }
525
666
  }
@@ -657,18 +798,21 @@ function infiniteQuery(key, fetcher, options) {
657
798
  }
658
799
  });
659
800
  function fetchNextPage() {
801
+ if (disposed) return Promise.resolve();
660
802
  if (inFlight) return inFlight;
661
803
  const param = nextPageParam();
662
804
  if (param === void 0) return Promise.resolve();
663
805
  return fetchPage(param, "next");
664
806
  }
665
807
  function fetchPreviousPage() {
808
+ if (disposed) return Promise.resolve();
666
809
  if (inFlight) return inFlight;
667
810
  const param = prevPageParam();
668
811
  if (param === void 0) return Promise.resolve();
669
812
  return fetchPage(param, "prev");
670
813
  }
671
814
  async function refetch() {
815
+ if (disposed) return;
672
816
  batch(() => {
673
817
  setPages([]);
674
818
  setNextPageParam(initialPageParam);
@@ -752,25 +896,26 @@ function throttle(getter, interval) {
752
896
  let pending = null;
753
897
  let lastEmitted = getter();
754
898
  let timer = null;
899
+ function emit(value) {
900
+ setThrottled(value);
901
+ lastEmitted = value;
902
+ cooldown = true;
903
+ pending = null;
904
+ timer = setTimeout(endWindow, interval);
905
+ }
906
+ function endWindow() {
907
+ timer = null;
908
+ if (pending !== null && !Object.is(pending.value, lastEmitted)) {
909
+ emit(pending.value);
910
+ return;
911
+ }
912
+ pending = null;
913
+ cooldown = false;
914
+ }
755
915
  const stop = effect(() => {
756
916
  const value = getter();
757
917
  if (!cooldown) {
758
- if (!Object.is(value, lastEmitted)) {
759
- setThrottled(value);
760
- lastEmitted = value;
761
- cooldown = true;
762
- pending = null;
763
- timer = setTimeout(() => {
764
- timer = null;
765
- cooldown = false;
766
- if (pending !== null) {
767
- const trailingValue = pending.value;
768
- pending = null;
769
- setThrottled(trailingValue);
770
- lastEmitted = trailingValue;
771
- }
772
- }, interval);
773
- }
918
+ if (!Object.is(value, lastEmitted)) emit(value);
774
919
  } else {
775
920
  pending = { value };
776
921
  }
@@ -806,6 +951,15 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
806
951
  const [loading, setLoading] = signal(false);
807
952
  const [error, setError] = signal(void 0);
808
953
  let currentData = options.initialValue;
954
+ function commitData(next, explicit) {
955
+ const sharing = options.structuralSharing ?? true;
956
+ const shared = untracked(
957
+ () => runSelect("resource structuralSharing", () => applyStructuralSharing(sharing, currentData, next, explicit))
958
+ );
959
+ currentData = shared.ok ? shared.value : next;
960
+ setData(currentData);
961
+ return currentData;
962
+ }
809
963
  let abortController = null;
810
964
  let disposed = false;
811
965
  let effectCleanup = null;
@@ -825,12 +979,12 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
825
979
  try {
826
980
  const result = await withRetry(() => fetcher(sourceValue, { signal: signal2, prev }), options.retry, void 0, signal2);
827
981
  if (version !== fetchVersion || disposed) return;
828
- currentData = result;
982
+ let committed = result;
829
983
  batch(() => {
830
- setData(result);
984
+ committed = commitData(result, false);
831
985
  setLoading(false);
832
986
  });
833
- runCallback("resource onSuccess", () => options.onSuccess?.(result));
987
+ runCallback("resource onSuccess", () => options.onSuccess?.(committed));
834
988
  } catch (err) {
835
989
  if (version !== fetchVersion || disposed) return;
836
990
  if (isAbortError(err)) {
@@ -844,7 +998,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
844
998
  });
845
999
  runCallback("resource onError", () => options.onError?.(errorObj));
846
1000
  } finally {
847
- if (version === fetchVersion) {
1001
+ if (!disposed && version === fetchVersion) {
848
1002
  runCallback("resource onSettled", () => options.onSettled?.());
849
1003
  }
850
1004
  }
@@ -864,8 +1018,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
864
1018
  refetch: () => doFetch(source ? source() : void 0),
865
1019
  mutate: (value) => {
866
1020
  const newValue = typeof value === "function" ? value(currentData) : value;
867
- currentData = newValue;
868
- setData(newValue);
1021
+ commitData(newValue, true);
869
1022
  },
870
1023
  abort: () => abortController?.abort(),
871
1024
  dispose: () => {
@@ -1151,15 +1304,54 @@ function syncAdapter(config) {
1151
1304
  }
1152
1305
 
1153
1306
  // src/data/routeLoader.ts
1154
- var LoaderContext = context(null);
1155
- function executeLoader(loader, context2, options) {
1156
- const res = resource(({ signal: signal2 }) => loader(context2, { signal: signal2 }), options);
1157
- LoaderContext.provide(res);
1307
+ var _clientScope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.v2"), () => ({ stack: [] }));
1308
+ var _disposed = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.routeLoader.disposed.v1"), () => /* @__PURE__ */ new WeakSet());
1309
+ function currentScope() {
1310
+ const cache = getRequestScopedCache("routeLoader");
1311
+ if (!cache) return _clientScope;
1312
+ let scope = cache.get("scope");
1313
+ if (!scope) {
1314
+ scope = { stack: [] };
1315
+ cache.set("scope", scope);
1316
+ }
1317
+ return scope;
1318
+ }
1319
+ function executeLoader(loader, context, options) {
1320
+ const res = resource(({ signal: signal2 }) => loader(context, { signal: signal2 }), options);
1321
+ const disposeResource = res.dispose;
1322
+ res.dispose = () => {
1323
+ _disposed.add(res);
1324
+ disposeResource();
1325
+ };
1158
1326
  return res;
1159
1327
  }
1328
+ function renderWithLoader(loader, render) {
1329
+ if (_disposed.has(loader)) {
1330
+ throw new Error("renderWithLoader: the loader resource has been disposed");
1331
+ }
1332
+ const scope = currentScope();
1333
+ const entry = loader;
1334
+ scope.stack.push(entry);
1335
+ try {
1336
+ return render();
1337
+ } finally {
1338
+ const index = scope.stack.lastIndexOf(entry);
1339
+ if (index !== -1) scope.stack.splice(index, 1);
1340
+ }
1341
+ }
1342
+ function withLoader(loader, context, render, options) {
1343
+ const res = executeLoader(loader, context, options);
1344
+ try {
1345
+ return { view: renderWithLoader(res, render), resource: res };
1346
+ } catch (err) {
1347
+ res.dispose();
1348
+ throw err;
1349
+ }
1350
+ }
1160
1351
  function loaderData() {
1161
- const resource2 = LoaderContext.get();
1162
- if (!resource2) {
1352
+ const scope = currentScope();
1353
+ const resource2 = scope.stack[scope.stack.length - 1];
1354
+ if (!resource2 || _disposed.has(resource2)) {
1163
1355
  throw new Error("loaderData must be used inside a route with a loader");
1164
1356
  }
1165
1357
  return {
@@ -1168,7 +1360,7 @@ function loaderData() {
1168
1360
  error: resource2.error
1169
1361
  };
1170
1362
  }
1171
- async function preloadRoute(route, context2, callerSignal) {
1363
+ async function preloadRoute(route, context, callerSignal) {
1172
1364
  if (!route.loader) return void 0;
1173
1365
  const controller = new AbortController();
1174
1366
  let onAbort = null;
@@ -1181,7 +1373,7 @@ async function preloadRoute(route, context2, callerSignal) {
1181
1373
  }
1182
1374
  }
1183
1375
  try {
1184
- return await route.loader(context2, { signal: controller.signal });
1376
+ return await route.loader(context, { signal: controller.signal });
1185
1377
  } finally {
1186
1378
  if (onAbort) callerSignal?.removeEventListener("abort", onAbort);
1187
1379
  }
@@ -1209,34 +1401,73 @@ function socket(url, options) {
1209
1401
  let heartbeatTimer = null;
1210
1402
  let disposed = false;
1211
1403
  let manuallyClosed = false;
1404
+ let lifecycle = 0;
1212
1405
  function getUrl() {
1213
1406
  return typeof url === "function" ? url() : url;
1214
1407
  }
1215
1408
  function connect() {
1216
1409
  if (disposed) return;
1410
+ const generation = lifecycle;
1411
+ const invalidated = () => disposed || generation !== lifecycle;
1217
1412
  if (typeof WebSocket === "undefined") {
1218
1413
  setStatus("closed");
1219
1414
  return;
1220
1415
  }
1221
- const safeUrl = validateWsUrl(getUrl());
1416
+ let rawUrl;
1417
+ try {
1418
+ rawUrl = getUrl();
1419
+ } catch (error) {
1420
+ setStatus("closed");
1421
+ reportError(error, { phase: "async", name: "socket" });
1422
+ return;
1423
+ }
1424
+ if (invalidated()) {
1425
+ setStatus("closed");
1426
+ return;
1427
+ }
1428
+ const safeUrl = validateWsUrl(rawUrl);
1222
1429
  if (safeUrl === null) {
1223
1430
  setStatus("closed");
1224
1431
  return;
1225
1432
  }
1226
1433
  setStatus("connecting");
1227
- ws = new WebSocket(safeUrl, protocols);
1228
- ws.onopen = () => {
1434
+ if (invalidated()) {
1435
+ setStatus("closed");
1436
+ return;
1437
+ }
1438
+ let instance;
1439
+ try {
1440
+ instance = new WebSocket(safeUrl, protocols);
1441
+ } catch (error) {
1442
+ setStatus("closed");
1443
+ reportError(error, { phase: "async", name: "socket" });
1444
+ return;
1445
+ }
1446
+ if (invalidated()) {
1447
+ instance.close();
1448
+ setStatus("closed");
1449
+ return;
1450
+ }
1451
+ ws = instance;
1452
+ instance.onopen = () => {
1453
+ if (ws !== instance) return;
1454
+ const openGeneration = lifecycle;
1229
1455
  setStatus("open");
1456
+ if (disposed || openGeneration !== lifecycle || ws !== instance) return;
1230
1457
  reconnectCount = 0;
1231
1458
  startHeartbeat();
1232
1459
  };
1233
- ws.onmessage = (event) => {
1460
+ instance.onmessage = (event) => {
1461
+ if (ws !== instance) return;
1234
1462
  setData(event.data);
1235
1463
  };
1236
- ws.onclose = () => {
1464
+ instance.onclose = () => {
1465
+ if (ws !== instance) return;
1466
+ ws = null;
1467
+ const closeGeneration = lifecycle;
1237
1468
  setStatus("closed");
1238
1469
  stopHeartbeat();
1239
- const wasManual = manuallyClosed;
1470
+ const wasManual = manuallyClosed || closeGeneration !== lifecycle;
1240
1471
  manuallyClosed = false;
1241
1472
  if (autoReconnect && !disposed && !wasManual && reconnectCount < maxReconnects) {
1242
1473
  const cap = 3e4;
@@ -1250,7 +1481,7 @@ function socket(url, options) {
1250
1481
  }, jittered);
1251
1482
  }
1252
1483
  };
1253
- ws.onerror = () => {
1484
+ instance.onerror = () => {
1254
1485
  };
1255
1486
  }
1256
1487
  function startHeartbeat() {
@@ -1274,13 +1505,23 @@ function socket(url, options) {
1274
1505
  }
1275
1506
  }
1276
1507
  function close() {
1508
+ lifecycle++;
1277
1509
  manuallyClosed = true;
1278
1510
  if (reconnectTimer !== null) {
1279
1511
  clearTimeout(reconnectTimer);
1280
1512
  reconnectTimer = null;
1281
1513
  }
1282
1514
  stopHeartbeat();
1283
- if (ws) {
1515
+ if (!ws) {
1516
+ setStatus("closed");
1517
+ return;
1518
+ }
1519
+ if (ws.readyState === WebSocket.CLOSED) {
1520
+ ws = null;
1521
+ setStatus("closed");
1522
+ return;
1523
+ }
1524
+ if (ws.readyState !== WebSocket.CLOSING) {
1284
1525
  setStatus("closing");
1285
1526
  ws.close();
1286
1527
  }
@@ -1311,6 +1552,7 @@ function stream(url, options) {
1311
1552
  let disposed = false;
1312
1553
  let reconnectTimer = null;
1313
1554
  let attempts = 0;
1555
+ let closeGeneration = 0;
1314
1556
  function connect() {
1315
1557
  if (disposed) return;
1316
1558
  if (typeof EventSource === "undefined") {
@@ -1322,44 +1564,64 @@ function stream(url, options) {
1322
1564
  setStatus("closed");
1323
1565
  return;
1324
1566
  }
1567
+ const generation = closeGeneration;
1325
1568
  setStatus("connecting");
1326
- source = new EventSource(safeUrl, {
1569
+ if (disposed || generation !== closeGeneration) return;
1570
+ const instance = new EventSource(safeUrl, {
1327
1571
  withCredentials: options?.withCredentials ?? false
1328
1572
  });
1329
- source.onopen = () => {
1573
+ if (disposed || generation !== closeGeneration) {
1574
+ instance.close();
1575
+ return;
1576
+ }
1577
+ source = instance;
1578
+ const live = () => !disposed && source === instance;
1579
+ instance.onopen = () => {
1580
+ if (!live()) return;
1330
1581
  setStatus("open");
1331
1582
  attempts = 0;
1332
1583
  };
1333
- source.onmessage = (evt) => {
1584
+ instance.onmessage = (evt) => {
1585
+ if (!live()) return;
1334
1586
  setData(evt.data);
1335
1587
  setEvent(evt.type);
1336
1588
  };
1337
- source.onerror = () => {
1338
- if (source && source.readyState === EventSource.CLOSED) {
1339
- setStatus("closed");
1340
- source = null;
1341
- if (autoReconnect && !disposed && attempts < maxReconnects) {
1342
- const delay = Math.min(maxMs, baseMs * 2 ** attempts);
1343
- const jittered = delay * (0.5 + Math.random() * 0.5);
1344
- attempts++;
1345
- reconnectTimer = setTimeout(() => {
1346
- reconnectTimer = null;
1347
- connect();
1348
- }, jittered);
1349
- }
1589
+ instance.onerror = () => {
1590
+ if (!live() || instance.readyState !== EventSource.CLOSED) return;
1591
+ detach(instance);
1592
+ source = null;
1593
+ const generation2 = closeGeneration;
1594
+ setStatus("closed");
1595
+ if (disposed || generation2 !== closeGeneration) return;
1596
+ if (autoReconnect && attempts < maxReconnects) {
1597
+ const delay = Math.min(maxMs, baseMs * 2 ** attempts);
1598
+ const jittered = delay * (0.5 + Math.random() * 0.5);
1599
+ attempts++;
1600
+ reconnectTimer = setTimeout(() => {
1601
+ reconnectTimer = null;
1602
+ connect();
1603
+ }, jittered);
1350
1604
  }
1351
1605
  };
1352
1606
  }
1607
+ function detach(instance) {
1608
+ instance.onopen = null;
1609
+ instance.onmessage = null;
1610
+ instance.onerror = null;
1611
+ }
1353
1612
  function close() {
1613
+ closeGeneration++;
1354
1614
  if (reconnectTimer !== null) {
1355
1615
  clearTimeout(reconnectTimer);
1356
1616
  reconnectTimer = null;
1357
1617
  }
1358
- if (source) {
1359
- source.close();
1360
- setStatus("closed");
1618
+ const instance = source;
1619
+ if (instance) {
1361
1620
  source = null;
1621
+ detach(instance);
1622
+ instance.close();
1362
1623
  }
1624
+ setStatus("closed");
1363
1625
  }
1364
1626
  function dispose() {
1365
1627
  disposed = true;
@@ -1387,6 +1649,8 @@ export {
1387
1649
  offlineStore,
1388
1650
  syncAdapter,
1389
1651
  executeLoader,
1652
+ renderWithLoader,
1653
+ withLoader,
1390
1654
  loaderData,
1391
1655
  preloadRoute,
1392
1656
  socket,