sibujs 3.1.0 → 3.2.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 (78) hide show
  1. package/README.md +6 -0
  2. package/dist/browser.cjs +16 -8
  3. package/dist/browser.js +6 -5
  4. package/dist/build.cjs +235 -147
  5. package/dist/build.js +35 -24
  6. package/dist/cdn.global.js +7 -7
  7. package/dist/{chunk-WYU7CYJ3.js → chunk-2C4E3HBM.js} +5 -5
  8. package/dist/{chunk-3DYB5B3S.js → chunk-4JCAUOLN.js} +45 -23
  9. package/dist/{chunk-2HAGQWDV.js → chunk-5N74TKLD.js} +1 -1
  10. package/dist/{chunk-SVVAUX7J.js → chunk-7XDYVJLE.js} +19 -9
  11. package/dist/{chunk-2N2UL7O4.js → chunk-BGNLPNGV.js} +20 -12
  12. package/dist/{chunk-RK4BQG25.js → chunk-C427DVQF.js} +1 -1
  13. package/dist/{chunk-ZIBE2SAT.js → chunk-FDY42FIU.js} +3 -2
  14. package/dist/{chunk-GQ7RRFPU.js → chunk-FOI23UJL.js} +11 -1
  15. package/dist/{chunk-2RA7SHDA.js → chunk-GOJMFRBL.js} +20 -4
  16. package/dist/{chunk-IVOUCSZL.js → chunk-GOUM4JCT.js} +6 -6
  17. package/dist/chunk-H3SRKIYX.js +17 -0
  18. package/dist/{chunk-3DJH25UO.js → chunk-H6PCHJZQ.js} +2 -2
  19. package/dist/{chunk-UCS6AMJ7.js → chunk-HMJFCBRR.js} +26 -3
  20. package/dist/{chunk-JYD2PWXH.js → chunk-HXMS4SNP.js} +22 -15
  21. package/dist/{chunk-SC437AMI.js → chunk-JYXOEYI4.js} +12 -18
  22. package/dist/{chunk-KB3BA2XK.js → chunk-NFYWLRUO.js} +11 -18
  23. package/dist/{chunk-QNQY5DUS.js → chunk-NPIEEKPT.js} +20 -11
  24. package/dist/{chunk-UYX2NDOH.js → chunk-OYLPZO4N.js} +33 -15
  25. package/dist/{chunk-LYTCUZ7H.js → chunk-RDRSWYNP.js} +1 -1
  26. package/dist/{chunk-2ZJ7TSW4.js → chunk-RLUJL2MV.js} +4 -8
  27. package/dist/{chunk-CR4MXPHB.js → chunk-V2MTG5FT.js} +99 -36
  28. package/dist/{chunk-CNZ35WI2.js → chunk-VJE6DDYM.js} +2 -2
  29. package/dist/{chunk-PMSDFTK3.js → chunk-VOCE4NNK.js} +157 -75
  30. package/dist/{chunk-WKUXSE7V.js → chunk-X67UYC74.js} +12 -11
  31. package/dist/{chunk-EFOAE5NC.js → chunk-YFDGQWDA.js} +1 -1
  32. package/dist/{chunk-3U4ZVXVD.js → chunk-Z2FWAE4B.js} +6 -2
  33. package/dist/data.cjs +190 -94
  34. package/dist/data.d.cts +7 -1
  35. package/dist/data.d.ts +7 -1
  36. package/dist/data.js +8 -8
  37. package/dist/devtools.cjs +38 -10
  38. package/dist/devtools.d.cts +1 -1
  39. package/dist/devtools.d.ts +1 -1
  40. package/dist/devtools.js +6 -6
  41. package/dist/ecosystem.cjs +123 -63
  42. package/dist/ecosystem.js +9 -9
  43. package/dist/extras.cjs +380 -196
  44. package/dist/extras.d.cts +2 -2
  45. package/dist/extras.d.ts +2 -2
  46. package/dist/extras.js +27 -24
  47. package/dist/index.cjs +214 -136
  48. package/dist/index.d.cts +15 -2
  49. package/dist/index.d.ts +15 -2
  50. package/dist/index.js +15 -13
  51. package/dist/{introspect-BZWKvQUZ.d.ts → introspect-DOZfmC-4.d.ts} +1 -1
  52. package/dist/{introspect-DsJlDD2T.d.cts → introspect-RjLfIFpL.d.cts} +1 -1
  53. package/dist/motion.cjs +10 -0
  54. package/dist/motion.js +3 -3
  55. package/dist/patterns.cjs +45 -40
  56. package/dist/patterns.js +8 -7
  57. package/dist/performance.cjs +101 -25
  58. package/dist/performance.d.cts +2 -2
  59. package/dist/performance.d.ts +2 -2
  60. package/dist/performance.js +8 -7
  61. package/dist/plugins.cjs +203 -136
  62. package/dist/plugins.d.cts +1 -1
  63. package/dist/plugins.d.ts +1 -1
  64. package/dist/plugins.js +96 -45
  65. package/dist/{ssr-FXD2PPMC.js → ssr-2QDQ27EV.js} +5 -3
  66. package/dist/{ssr-CrVNy6Pa.d.cts → ssr-D62yFwuw.d.cts} +8 -1
  67. package/dist/{ssr-CrVNy6Pa.d.ts → ssr-D62yFwuw.d.ts} +8 -1
  68. package/dist/ssr.cjs +145 -66
  69. package/dist/ssr.d.cts +1 -1
  70. package/dist/ssr.d.ts +1 -1
  71. package/dist/ssr.js +12 -10
  72. package/dist/testing.cjs +9 -4
  73. package/dist/testing.js +3 -3
  74. package/dist/ui.cjs +54 -38
  75. package/dist/ui.js +10 -9
  76. package/dist/widgets.cjs +40 -24
  77. package/dist/widgets.js +8 -8
  78. package/package.json +3 -1
@@ -1,19 +1,22 @@
1
1
  import {
2
2
  context
3
- } from "./chunk-EFOAE5NC.js";
3
+ } from "./chunk-YFDGQWDA.js";
4
4
  import {
5
5
  derived
6
- } from "./chunk-SC437AMI.js";
6
+ } from "./chunk-JYXOEYI4.js";
7
7
  import {
8
8
  sanitizeUrl
9
- } from "./chunk-UCS6AMJ7.js";
9
+ } from "./chunk-HMJFCBRR.js";
10
10
  import {
11
11
  effect
12
- } from "./chunk-ZIBE2SAT.js";
12
+ } from "./chunk-FDY42FIU.js";
13
+ import {
14
+ getRequestScopedCache
15
+ } from "./chunk-GOJMFRBL.js";
13
16
  import {
14
17
  batch,
15
18
  signal
16
- } from "./chunk-RK4BQG25.js";
19
+ } from "./chunk-C427DVQF.js";
17
20
 
18
21
  // src/data/retry.ts
19
22
  function calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter) {
@@ -76,9 +79,12 @@ async function withRetry(fn, options, onRetry, signal2) {
76
79
  }
77
80
 
78
81
  // src/data/query.ts
79
- var queryCache = /* @__PURE__ */ new Map();
80
- function getOrCreateEntry(key, initialData) {
81
- let entry = queryCache.get(key);
82
+ var globalQueryCache = /* @__PURE__ */ new Map();
83
+ function getActiveQueryCache() {
84
+ return getRequestScopedCache("query") ?? globalQueryCache;
85
+ }
86
+ function getOrCreateEntry(cache, key, initialData) {
87
+ let entry = cache.get(key);
82
88
  if (!entry) {
83
89
  entry = {
84
90
  data: initialData,
@@ -90,7 +96,7 @@ function getOrCreateEntry(key, initialData) {
90
96
  listeners: /* @__PURE__ */ new Set(),
91
97
  refetchers: /* @__PURE__ */ new Set()
92
98
  };
93
- queryCache.set(key, entry);
99
+ cache.set(key, entry);
94
100
  }
95
101
  return entry;
96
102
  }
@@ -110,6 +116,7 @@ function query(key, fetcher, options = {}) {
110
116
  select
111
117
  } = options;
112
118
  const resolveKey = typeof key === "function" ? key : () => key;
119
+ const cache = getActiveQueryCache();
113
120
  const [data, setData] = signal(initialData);
114
121
  const [isFetching, setIsFetching] = signal(false);
115
122
  const [error, setError] = signal(void 0);
@@ -121,16 +128,16 @@ function query(key, fetcher, options = {}) {
121
128
  const isStale = derived(() => {
122
129
  data();
123
130
  if (!currentKey) return true;
124
- const entry = queryCache.get(currentKey);
131
+ const entry = cache.get(currentKey);
125
132
  if (!entry || entry.dataUpdatedAt === 0) return true;
126
133
  return Date.now() - entry.dataUpdatedAt >= staleTime;
127
134
  });
128
135
  async function doFetch() {
129
136
  if (disposed || !currentKey || !enabled) return;
130
137
  const key2 = currentKey;
131
- let entry = queryCache.get(key2);
138
+ let entry = cache.get(key2);
132
139
  if (!entry) {
133
- entry = getOrCreateEntry(key2);
140
+ entry = getOrCreateEntry(cache, key2);
134
141
  entry.listeners.add(onCacheUpdate);
135
142
  entry.refetchers.add(doFetch);
136
143
  }
@@ -205,7 +212,7 @@ function query(key, fetcher, options = {}) {
205
212
  }
206
213
  function onCacheUpdate() {
207
214
  if (disposed || !currentKey) return;
208
- const entry = queryCache.get(currentKey);
215
+ const entry = cache.get(currentKey);
209
216
  if (!entry) {
210
217
  batch(() => {
211
218
  setData(void 0);
@@ -225,7 +232,7 @@ function query(key, fetcher, options = {}) {
225
232
  const effectCleanup = effect(() => {
226
233
  const key2 = resolveKey();
227
234
  if (currentKey !== null && currentKey !== key2) {
228
- const oldEntry = queryCache.get(currentKey);
235
+ const oldEntry = cache.get(currentKey);
229
236
  if (oldEntry) {
230
237
  oldEntry.listeners.delete(onCacheUpdate);
231
238
  oldEntry.refetchers.delete(doFetch);
@@ -233,13 +240,13 @@ function query(key, fetcher, options = {}) {
233
240
  if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
234
241
  const oldKey = currentKey;
235
242
  if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
236
- oldEntry.gcTimer = setTimeout(() => queryCache.delete(oldKey), cacheTime);
243
+ oldEntry.gcTimer = setTimeout(() => cache.delete(oldKey), cacheTime);
237
244
  }
238
245
  }
239
246
  }
240
247
  const keyChanged = currentKey !== key2;
241
248
  currentKey = key2;
242
- const entry = getOrCreateEntry(key2, initialData);
249
+ const entry = getOrCreateEntry(cache, key2, initialData);
243
250
  if (keyChanged) entry.subscribers++;
244
251
  if (entry.gcTimer !== null) {
245
252
  clearTimeout(entry.gcTimer);
@@ -293,7 +300,7 @@ function query(key, fetcher, options = {}) {
293
300
  effectCleanup();
294
301
  if (intervalTimer) clearInterval(intervalTimer);
295
302
  if (currentKey) {
296
- const entry = queryCache.get(currentKey);
303
+ const entry = cache.get(currentKey);
297
304
  if (entry) {
298
305
  entry.listeners.delete(onCacheUpdate);
299
306
  entry.refetchers.delete(doFetch);
@@ -301,7 +308,7 @@ function query(key, fetcher, options = {}) {
301
308
  if (entry.subscribers <= 0 && cacheTime >= 0) {
302
309
  const key2 = currentKey;
303
310
  if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
304
- entry.gcTimer = setTimeout(() => queryCache.delete(key2), cacheTime);
311
+ entry.gcTimer = setTimeout(() => cache.delete(key2), cacheTime);
305
312
  }
306
313
  }
307
314
  }
@@ -324,7 +331,7 @@ function query(key, fetcher, options = {}) {
324
331
  }
325
332
  function invalidateQueries(keyOrPredicate) {
326
333
  const predicate = typeof keyOrPredicate === "function" ? keyOrPredicate : (k) => k === keyOrPredicate;
327
- for (const [key, entry] of queryCache.entries()) {
334
+ for (const [key, entry] of getActiveQueryCache().entries()) {
328
335
  if (predicate(key)) {
329
336
  entry.dataUpdatedAt = 0;
330
337
  for (const refetcher of entry.refetchers) refetcher();
@@ -332,10 +339,10 @@ function invalidateQueries(keyOrPredicate) {
332
339
  }
333
340
  }
334
341
  function getQueryData(key) {
335
- return queryCache.get(key)?.data;
342
+ return getActiveQueryCache().get(key)?.data;
336
343
  }
337
344
  function setQueryData(key, data) {
338
- const entry = queryCache.get(key);
345
+ const entry = getActiveQueryCache().get(key);
339
346
  if (!entry) return;
340
347
  const newData = typeof data === "function" ? data(entry.data) : data;
341
348
  entry.data = newData;
@@ -345,14 +352,15 @@ function setQueryData(key, data) {
345
352
  function clearQueryCache() {
346
353
  const activeListeners = [];
347
354
  const activeRefetchers = [];
348
- for (const entry of queryCache.values()) {
355
+ const activeCache = getActiveQueryCache();
356
+ for (const entry of activeCache.values()) {
349
357
  if (entry.gcTimer) clearTimeout(entry.gcTimer);
350
358
  if (entry.subscribers > 0) {
351
359
  for (const listener of entry.listeners) activeListeners.push(listener);
352
360
  for (const refetcher of entry.refetchers) activeRefetchers.push(refetcher);
353
361
  }
354
362
  }
355
- queryCache.clear();
363
+ activeCache.clear();
356
364
  for (const listener of activeListeners) listener();
357
365
  for (const refetcher of activeRefetchers) {
358
366
  refetcher().catch((err) => {
@@ -363,10 +371,11 @@ function clearQueryCache() {
363
371
  }
364
372
  }
365
373
  function __resetQueryCache() {
366
- for (const entry of queryCache.values()) {
374
+ const activeCache = getActiveQueryCache();
375
+ for (const entry of activeCache.values()) {
367
376
  if (entry.gcTimer) clearTimeout(entry.gcTimer);
368
377
  }
369
- queryCache.clear();
378
+ activeCache.clear();
370
379
  }
371
380
 
372
381
  // src/data/mutation.ts
@@ -378,7 +387,11 @@ function mutation(mutationFn, options = {}) {
378
387
  const isSuccess = derived(() => status() === "success");
379
388
  const isIdle = derived(() => status() === "idle");
380
389
  let runId = 0;
390
+ let abortController = null;
381
391
  async function execute(variables) {
392
+ abortController?.abort();
393
+ abortController = new AbortController();
394
+ const signal2 = abortController.signal;
382
395
  const myRun = ++runId;
383
396
  let context2;
384
397
  batch(() => {
@@ -390,7 +403,7 @@ function mutation(mutationFn, options = {}) {
390
403
  if (options.onMutate) {
391
404
  context2 = await options.onMutate(variables);
392
405
  }
393
- const result = await withRetry(() => mutationFn(variables), options.retry);
406
+ const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
394
407
  if (myRun !== runId) return result;
395
408
  batch(() => {
396
409
  setData(result);
@@ -402,6 +415,7 @@ function mutation(mutationFn, options = {}) {
402
415
  return result;
403
416
  } catch (err) {
404
417
  const errorObj = err instanceof Error ? err : new Error(String(err));
418
+ if (errorObj instanceof DOMException && errorObj.name === "AbortError") throw errorObj;
405
419
  if (myRun !== runId) throw errorObj;
406
420
  batch(() => {
407
421
  setError(errorObj);
@@ -415,6 +429,8 @@ function mutation(mutationFn, options = {}) {
415
429
  }
416
430
  function reset() {
417
431
  runId++;
432
+ abortController?.abort();
433
+ abortController = null;
418
434
  batch(() => {
419
435
  setData(void 0);
420
436
  setError(void 0);
@@ -430,6 +446,7 @@ function mutation(mutationFn, options = {}) {
430
446
  isIdle,
431
447
  mutate: (variables) => {
432
448
  execute(variables).catch((err) => {
449
+ if (err instanceof DOMException && err.name === "AbortError") return;
433
450
  if (typeof console !== "undefined") {
434
451
  console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
435
452
  }
@@ -446,6 +463,7 @@ function infiniteQuery(key, fetcher, options) {
446
463
  getNextPageParam,
447
464
  getPreviousPageParam,
448
465
  initialPageParam = 0,
466
+ maxPages,
449
467
  enabled = true,
450
468
  retry: retryOptions,
451
469
  onSuccess,
@@ -469,8 +487,9 @@ function infiniteQuery(key, fetcher, options) {
469
487
  let abortController = null;
470
488
  let disposed = false;
471
489
  let runId = 0;
472
- async function fetchPage(pageParam, direction) {
473
- if (disposed) return;
490
+ let inFlight = null;
491
+ function fetchPage(pageParam, direction) {
492
+ if (disposed) return Promise.resolve();
474
493
  abortController?.abort();
475
494
  abortController = new AbortController();
476
495
  const signal2 = abortController.signal;
@@ -481,39 +500,44 @@ function infiniteQuery(key, fetcher, options) {
481
500
  if (direction === "prev") setIsFetchingPrev(true);
482
501
  setError(void 0);
483
502
  });
484
- try {
485
- const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
486
- if (disposed || myRun !== runId) return;
487
- const currentPages = pages();
488
- let newPages;
489
- if (direction === "prev") {
490
- newPages = [page, ...currentPages];
491
- } else {
492
- newPages = [...currentPages, page];
503
+ const promise = (async () => {
504
+ try {
505
+ const page = await withRetry(() => fetcher({ signal: signal2, pageParam }), retryOptions, void 0, signal2);
506
+ if (disposed || myRun !== runId) return;
507
+ const currentPages = pages();
508
+ let newPages = direction === "prev" ? [page, ...currentPages] : [...currentPages, page];
509
+ if (maxPages != null && maxPages > 0 && newPages.length > maxPages) {
510
+ newPages = direction === "prev" ? newPages.slice(0, maxPages) : newPages.slice(newPages.length - maxPages);
511
+ }
512
+ const nextParam = getNextPageParam(newPages[newPages.length - 1], newPages);
513
+ const prevParam = getPreviousPageParam?.(newPages[0], newPages);
514
+ batch(() => {
515
+ setPages(newPages);
516
+ setNextPageParam(nextParam);
517
+ setPrevPageParam(prevParam);
518
+ setIsFetching(false);
519
+ setIsFetchingNext(false);
520
+ setIsFetchingPrev(false);
521
+ });
522
+ onSuccess?.(newPages);
523
+ } catch (err) {
524
+ if (disposed || myRun !== runId) return;
525
+ if (err instanceof DOMException && err.name === "AbortError") return;
526
+ const errorObj = err instanceof Error ? err : new Error(String(err));
527
+ batch(() => {
528
+ setError(errorObj);
529
+ setIsFetching(false);
530
+ setIsFetchingNext(false);
531
+ setIsFetchingPrev(false);
532
+ });
533
+ onError?.(errorObj);
493
534
  }
494
- const nextParam = getNextPageParam(page, newPages);
495
- const prevParam = getPreviousPageParam?.(newPages[0], newPages);
496
- batch(() => {
497
- setPages(newPages);
498
- setNextPageParam(nextParam);
499
- setPrevPageParam(prevParam);
500
- setIsFetching(false);
501
- setIsFetchingNext(false);
502
- setIsFetchingPrev(false);
503
- });
504
- onSuccess?.(newPages);
505
- } catch (err) {
506
- if (disposed || myRun !== runId) return;
507
- if (err instanceof DOMException && err.name === "AbortError") return;
508
- const errorObj = err instanceof Error ? err : new Error(String(err));
509
- batch(() => {
510
- setError(errorObj);
511
- setIsFetching(false);
512
- setIsFetchingNext(false);
513
- setIsFetchingPrev(false);
514
- });
515
- onError?.(errorObj);
516
- }
535
+ })();
536
+ inFlight = promise;
537
+ void promise.finally(() => {
538
+ if (inFlight === promise) inFlight = null;
539
+ });
540
+ return promise;
517
541
  }
518
542
  const effectCleanup = effect(() => {
519
543
  resolveKey();
@@ -528,11 +552,13 @@ function infiniteQuery(key, fetcher, options) {
528
552
  }
529
553
  });
530
554
  function fetchNextPage() {
555
+ if (inFlight) return inFlight;
531
556
  const param = nextPageParam();
532
557
  if (param === void 0) return Promise.resolve();
533
558
  return fetchPage(param, "next");
534
559
  }
535
560
  function fetchPreviousPage() {
561
+ if (inFlight) return inFlight;
536
562
  const param = prevPageParam();
537
563
  if (param === void 0) return Promise.resolve();
538
564
  return fetchPage(param, "prev");
@@ -571,13 +597,17 @@ function infiniteQuery(key, fetcher, options) {
571
597
  function previous(getter) {
572
598
  const [previous2, setPrevious] = signal(void 0);
573
599
  let current = getter();
574
- effect(() => {
600
+ const stop = effect(() => {
575
601
  const next = getter();
576
602
  if (!Object.is(next, current)) {
577
603
  setPrevious(current);
578
604
  current = next;
579
605
  }
580
606
  });
607
+ Object.defineProperty(previous2, "dispose", {
608
+ value: stop,
609
+ enumerable: false
610
+ });
581
611
  return previous2;
582
612
  }
583
613
 
@@ -585,7 +615,7 @@ function previous(getter) {
585
615
  function debounce(getter, delay) {
586
616
  const [debounced, setDebounced] = signal(getter());
587
617
  let timer = null;
588
- effect(() => {
618
+ const stop = effect(() => {
589
619
  const value = getter();
590
620
  if (timer !== null) clearTimeout(timer);
591
621
  timer = setTimeout(() => {
@@ -593,6 +623,16 @@ function debounce(getter, delay) {
593
623
  timer = null;
594
624
  }, delay);
595
625
  });
626
+ Object.defineProperty(debounced, "dispose", {
627
+ value: () => {
628
+ stop();
629
+ if (timer !== null) {
630
+ clearTimeout(timer);
631
+ timer = null;
632
+ }
633
+ },
634
+ enumerable: false
635
+ });
596
636
  return debounced;
597
637
  }
598
638
 
@@ -602,7 +642,8 @@ function throttle(getter, interval) {
602
642
  let cooldown = false;
603
643
  let pending = null;
604
644
  let lastEmitted = getter();
605
- effect(() => {
645
+ let timer = null;
646
+ const stop = effect(() => {
606
647
  const value = getter();
607
648
  if (!cooldown) {
608
649
  if (!Object.is(value, lastEmitted)) {
@@ -610,7 +651,8 @@ function throttle(getter, interval) {
610
651
  lastEmitted = value;
611
652
  cooldown = true;
612
653
  pending = null;
613
- setTimeout(() => {
654
+ timer = setTimeout(() => {
655
+ timer = null;
614
656
  cooldown = false;
615
657
  if (pending !== null) {
616
658
  const trailingValue = pending.value;
@@ -624,6 +666,16 @@ function throttle(getter, interval) {
624
666
  pending = { value };
625
667
  }
626
668
  });
669
+ Object.defineProperty(throttled, "dispose", {
670
+ value: () => {
671
+ stop();
672
+ if (timer !== null) {
673
+ clearTimeout(timer);
674
+ timer = null;
675
+ }
676
+ },
677
+ enumerable: false
678
+ });
627
679
  return throttled;
628
680
  }
629
681
 
@@ -752,28 +804,45 @@ function idbGet(db, store, key) {
752
804
  req.onerror = () => reject(req.error);
753
805
  });
754
806
  }
755
- function idbPut(db, store, item) {
807
+ function idbPut(db, store, item, key) {
756
808
  return new Promise((resolve, reject) => {
757
809
  const tx = db.transaction(store, "readwrite");
758
- tx.objectStore(store).put(item);
810
+ if (key !== void 0) tx.objectStore(store).put(item, key);
811
+ else tx.objectStore(store).put(item);
759
812
  tx.oncomplete = () => resolve();
760
813
  tx.onerror = () => reject(tx.error);
761
814
  });
762
815
  }
763
- function idbPutWithChange(db, item, change) {
816
+ function coalesceAndAddChange(tx, change, keyPath) {
817
+ const store = tx.objectStore("_changes");
818
+ const targetKey = change.item[keyPath];
819
+ const cursorReq = store.openCursor();
820
+ cursorReq.onsuccess = () => {
821
+ const cursor = cursorReq.result;
822
+ if (cursor) {
823
+ const existing = cursor.value;
824
+ const k = existing.item[keyPath];
825
+ if (targetKey != null && k === targetKey) cursor.delete();
826
+ cursor.continue();
827
+ } else {
828
+ store.put(change);
829
+ }
830
+ };
831
+ }
832
+ function idbPutWithChange(db, item, change, keyPath) {
764
833
  return new Promise((resolve, reject) => {
765
834
  const tx = db.transaction(["items", "_changes"], "readwrite");
766
835
  tx.objectStore("items").put(item);
767
- tx.objectStore("_changes").put(change);
836
+ coalesceAndAddChange(tx, change, keyPath);
768
837
  tx.oncomplete = () => resolve();
769
838
  tx.onerror = () => reject(tx.error);
770
839
  });
771
840
  }
772
- function idbDeleteWithChange(db, key, change) {
841
+ function idbDeleteWithChange(db, key, change, keyPath) {
773
842
  return new Promise((resolve, reject) => {
774
843
  const tx = db.transaction(["items", "_changes"], "readwrite");
775
844
  tx.objectStore("items").delete(key);
776
- tx.objectStore("_changes").put(change);
845
+ coalesceAndAddChange(tx, change, keyPath);
777
846
  tx.oncomplete = () => resolve();
778
847
  tx.onerror = () => reject(tx.error);
779
848
  });
@@ -833,13 +902,18 @@ async function offlineStore(options) {
833
902
  setPendingCount(changes.length);
834
903
  }
835
904
  async function put(item) {
836
- await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() });
905
+ await idbPutWithChange(db, item, { type: "put", item, timestamp: Date.now() }, keyPath);
837
906
  await refreshData();
838
907
  }
839
908
  async function remove(key) {
840
909
  const existing = await idbGet(db, "items", key);
841
910
  if (existing) {
842
- await idbDeleteWithChange(db, key, { type: "delete", item: existing, timestamp: Date.now() });
911
+ await idbDeleteWithChange(
912
+ db,
913
+ key,
914
+ { type: "delete", item: existing, timestamp: Date.now() },
915
+ keyPath
916
+ );
843
917
  await refreshData();
844
918
  }
845
919
  }
@@ -865,6 +939,8 @@ async function offlineStore(options) {
865
939
  snapshot.map((e) => e.key)
866
940
  );
867
941
  if (closed) return;
942
+ } else if (typeof console !== "undefined") {
943
+ console.warn(`[offlineStore] push rejected by adapter${result.error ? `: ${result.error}` : ""}`);
868
944
  }
869
945
  }
870
946
  const remoteItems = await adapter.pull(lastSynced());
@@ -883,7 +959,7 @@ async function offlineStore(options) {
883
959
  await idbPutMany(db, "items", safeRemote);
884
960
  if (closed) return;
885
961
  const now = Date.now();
886
- await idbPut(db, "_meta", now);
962
+ await idbPut(db, "_meta", now, "lastSynced");
887
963
  if (closed) return;
888
964
  setLastSynced(now);
889
965
  await refreshData();
@@ -951,14 +1027,20 @@ function loaderData() {
951
1027
  async function preloadRoute(route, context2, callerSignal) {
952
1028
  if (!route.loader) return void 0;
953
1029
  const controller = new AbortController();
1030
+ let onAbort = null;
954
1031
  if (callerSignal) {
955
1032
  if (callerSignal.aborted) {
956
1033
  controller.abort();
957
1034
  } else {
958
- callerSignal.addEventListener("abort", () => controller.abort(), { once: true });
1035
+ onAbort = () => controller.abort();
1036
+ callerSignal.addEventListener("abort", onAbort, { once: true });
959
1037
  }
960
1038
  }
961
- return route.loader(context2, { signal: controller.signal });
1039
+ try {
1040
+ return await route.loader(context2, { signal: controller.signal });
1041
+ } finally {
1042
+ if (onAbort) callerSignal?.removeEventListener("abort", onAbort);
1043
+ }
962
1044
  }
963
1045
 
964
1046
  // src/ui/socket.ts
@@ -1,19 +1,21 @@
1
1
  import {
2
2
  bindAttribute
3
- } from "./chunk-2ZJ7TSW4.js";
3
+ } from "./chunk-RLUJL2MV.js";
4
4
  import {
5
+ dispose,
5
6
  registerDisposer
6
7
  } from "./chunk-2UPRY23K.js";
7
8
  import {
9
+ isEventHandlerAttr,
8
10
  isUrlAttribute,
9
11
  sanitizeCSSValue,
10
12
  sanitizeSrcset,
11
13
  sanitizeUrl
12
- } from "./chunk-UCS6AMJ7.js";
14
+ } from "./chunk-HMJFCBRR.js";
13
15
  import {
14
16
  reactiveBinding,
15
17
  track
16
- } from "./chunk-3U4ZVXVD.js";
18
+ } from "./chunk-Z2FWAE4B.js";
17
19
  import {
18
20
  devWarn,
19
21
  isDev
@@ -34,6 +36,7 @@ function bindChildNode(placeholder, getter) {
34
36
  if (result == null || typeof result === "boolean") {
35
37
  for (let i = 0; i < lastNodes.length; i++) {
36
38
  const node = lastNodes[i];
39
+ dispose(node);
37
40
  if (node.parentNode) node.parentNode.removeChild(node);
38
41
  }
39
42
  lastNodes.length = 0;
@@ -75,18 +78,16 @@ function bindChildNode(placeholder, getter) {
75
78
  for (let i = 0; i < lastNodes.length; i++) {
76
79
  const node = lastNodes[i];
77
80
  if (reused?.has(node)) continue;
81
+ dispose(node);
78
82
  if (node.parentNode) node.parentNode.removeChild(node);
79
83
  }
80
- const anchor = placeholder.nextSibling;
84
+ let prev = placeholder;
81
85
  for (let i = 0; i < newNodes.length; i++) {
82
86
  const node = newNodes[i];
83
- if (reused?.has(node) && node.parentNode === parent) {
84
- if (node.nextSibling !== anchor) {
85
- parent.insertBefore(node, anchor);
86
- }
87
- } else {
88
- parent.insertBefore(node, anchor);
87
+ if (prev.nextSibling !== node) {
88
+ parent.insertBefore(node, prev.nextSibling);
89
89
  }
90
+ prev = node;
90
91
  }
91
92
  lastNodes = newNodes;
92
93
  }
@@ -328,7 +329,7 @@ var tagFactory = (tag, ns) => {
328
329
  const value = props[key];
329
330
  if (value == null) continue;
330
331
  const lkey = key.toLowerCase();
331
- if (lkey[0] === "o" && lkey[1] === "n") continue;
332
+ if (isEventHandlerAttr(key)) continue;
332
333
  if (typeof value === "function") {
333
334
  registerDisposer(el, bindAttribute(el, key, value));
334
335
  } else if (typeof value === "boolean") {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  signal
3
- } from "./chunk-RK4BQG25.js";
3
+ } from "./chunk-C427DVQF.js";
4
4
 
5
5
  // src/core/rendering/context.ts
6
6
  function context(defaultValue) {
@@ -179,7 +179,7 @@ function track(effectFn, subscriber) {
179
179
  function reactiveBinding(commit) {
180
180
  const run = () => {
181
181
  const s = subscriber;
182
- if (s._reentrant) return;
182
+ if (s._disposed || s._reentrant) return;
183
183
  s._reentrant = true;
184
184
  try {
185
185
  retrack(commit, subscriber);
@@ -195,8 +195,12 @@ function reactiveBinding(commit) {
195
195
  subscriber._runEpoch = 0;
196
196
  subscriber._runs = 0;
197
197
  subscriber._reentrant = false;
198
+ subscriber._disposed = false;
198
199
  run();
199
- return subscriber._dispose ?? (subscriber._dispose = () => cleanup(subscriber));
200
+ return subscriber._dispose ?? (subscriber._dispose = () => {
201
+ subscriber._disposed = true;
202
+ cleanup(subscriber);
203
+ });
200
204
  }
201
205
  function recordDependency(signal) {
202
206
  if (!currentSubscriber) return;