sibujs 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +777 -385
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
  11. package/dist/chunk-4MFZMLUU.js +147 -0
  12. package/dist/chunk-52XFPGSN.js +123 -0
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
  15. package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
  16. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  17. package/dist/chunk-DHDZ7IVN.js +109 -0
  18. package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
  19. package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
  20. package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
  21. package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
  22. package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
  23. package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
  24. package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
  25. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  26. package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
  27. package/dist/chunk-O3QQ3XOL.js +61 -0
  28. package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
  29. package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
  30. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  31. package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
  34. package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
  35. package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
  36. package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
  37. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +419 -175
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1981 -765
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +784 -385
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1374 -576
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +613 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1104 -443
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-COY6PUD2.js +0 -34
  96. package/dist/chunk-K35BAR4P.js +0 -96
  97. package/dist/chunk-L3GAGWCC.js +0 -111
  98. package/dist/chunk-RT3IYFN2.js +0 -38
package/dist/data.cjs CHANGED
@@ -66,8 +66,95 @@ function devWarn(message) {
66
66
  }
67
67
  }
68
68
 
69
+ // src/core/errors.ts
70
+ var ERROR_STATE_KEY = /* @__PURE__ */ Symbol.for("sibujs.runtime-errors.v1");
71
+ function state() {
72
+ const g = globalThis;
73
+ const existing = g[ERROR_STATE_KEY];
74
+ if (existing) return existing;
75
+ const created = { handler: null, handlerDepth: 0 };
76
+ g[ERROR_STATE_KEY] = created;
77
+ return created;
78
+ }
79
+ function normalizeError(value) {
80
+ if (value instanceof Error) return value;
81
+ const message = typeof value === "string" ? value : `Non-Error value thrown: ${safeStringify(value)}`;
82
+ return new Error(message, { cause: value });
83
+ }
84
+ function safeStringify(value) {
85
+ if (typeof value === "symbol") return value.toString();
86
+ try {
87
+ return typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
88
+ } catch {
89
+ return Object.prototype.toString.call(value);
90
+ }
91
+ }
92
+ function describe(context2) {
93
+ return context2.name ? `${context2.phase} "${context2.name}"` : context2.phase;
94
+ }
95
+ var REACTIVE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
96
+ function untrackedRun(fn) {
97
+ const api = globalThis[REACTIVE_REGISTRY_KEY];
98
+ return api?.untracked ? api.untracked(fn) : fn();
99
+ }
100
+ var PROPAGATE_EVENT = "sibu:error-propagate";
101
+ function claimedByBoundary(error, context2) {
102
+ const raw = context2.node;
103
+ if (!raw || typeof CustomEvent === "undefined") return false;
104
+ const target = resolveEventTarget(raw);
105
+ if (!target) return false;
106
+ try {
107
+ const event = new CustomEvent(PROPAGATE_EVENT, {
108
+ bubbles: true,
109
+ cancelable: true,
110
+ detail: { error, context: context2 }
111
+ });
112
+ return target.dispatchEvent(event) === false;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+ function resolveEventTarget(node) {
118
+ const ELEMENT_NODE = 1;
119
+ let current = node;
120
+ while (current) {
121
+ if (current.nodeType === ELEMENT_NODE && typeof current.dispatchEvent === "function") {
122
+ return current;
123
+ }
124
+ current = current.parentNode;
125
+ }
126
+ const self = node;
127
+ return typeof self.dispatchEvent === "function" ? self : null;
128
+ }
129
+ function defaultReport(error, context2) {
130
+ if (typeof console === "undefined") return;
131
+ console.error(`[SibuJS] Uncaught error during ${describe(context2)}: ${error.message}`, error);
132
+ }
133
+ function reportError(error, context2) {
134
+ const err = normalizeError(error);
135
+ untrackedRun(() => {
136
+ if (claimedByBoundary(err, context2)) return;
137
+ const s = state();
138
+ const handler = s.handler;
139
+ if (handler && s.handlerDepth === 0) {
140
+ s.handlerDepth++;
141
+ try {
142
+ handler(error instanceof Error ? error : err, context2);
143
+ return;
144
+ } catch (handlerError) {
145
+ if (typeof console !== "undefined") {
146
+ const detail = handlerError instanceof Error ? handlerError.message : String(handlerError);
147
+ console.error(`[SibuJS] runtime error handler itself threw: ${detail}`, handlerError);
148
+ }
149
+ } finally {
150
+ s.handlerDepth--;
151
+ }
152
+ }
153
+ defaultReport(err, context2);
154
+ });
155
+ }
156
+
69
157
  // src/reactivity/track-core.ts
70
- var _isDev2 = isDev();
71
158
  var POOL_MAX = 4096;
72
159
  var nodePool = [];
73
160
  function createNode() {
@@ -75,6 +162,7 @@ function createNode() {
75
162
  sig: null,
76
163
  sub: null,
77
164
  epoch: 0,
165
+ depVersion: 0,
78
166
  sigPrev: null,
79
167
  sigNext: null,
80
168
  subPrev: null,
@@ -159,9 +247,28 @@ function safeInvoke(sub) {
159
247
  try {
160
248
  sub();
161
249
  } catch (err) {
162
- if (_isDev2) devWarn(`Subscriber threw during notification: ${err instanceof Error ? err.message : String(err)}`);
250
+ const s = sub;
251
+ reportError(err, { phase: s._errorPhase ?? "effect", name: s.__name, node: s._errorNode });
163
252
  }
164
253
  }
254
+ function depsChanged(sub) {
255
+ let node = sub.depsHead ?? null;
256
+ if (node === null) return true;
257
+ while (node !== null) {
258
+ const sig = node.sig;
259
+ if (sig === null) return true;
260
+ if (sig._d === true && sig._validate !== void 0) {
261
+ try {
262
+ sig._validate();
263
+ } catch {
264
+ return true;
265
+ }
266
+ }
267
+ if (node.depVersion !== sig.__v) return true;
268
+ node = node.subNext;
269
+ }
270
+ return false;
271
+ }
165
272
  var suspendDepth = 0;
166
273
  var trackingSuspended = false;
167
274
  function suspendTracking() {
@@ -242,7 +349,7 @@ function track(effectFn, subscriber) {
242
349
  const sub = subscriber;
243
350
  return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
244
351
  }
245
- function reactiveBinding(commit) {
352
+ function reactiveBinding(commit, ownerNode) {
246
353
  const run = () => {
247
354
  const s = subscriber;
248
355
  if (s._disposed || s._reentrant) return;
@@ -258,13 +365,17 @@ function reactiveBinding(commit) {
258
365
  subscriber.depsTail = null;
259
366
  subscriber._epoch = 0;
260
367
  subscriber._structDirty = false;
368
+ subscriber._hasComputedDep = false;
261
369
  subscriber._runEpoch = 0;
262
370
  subscriber._runs = 0;
263
371
  subscriber._reentrant = false;
264
372
  subscriber._disposed = false;
373
+ subscriber._errorPhase = "binding";
374
+ subscriber._errorNode = ownerNode;
265
375
  run();
266
376
  return subscriber._dispose ?? (subscriber._dispose = () => {
267
377
  subscriber._disposed = true;
378
+ subscriber._errorNode = void 0;
268
379
  cleanup(subscriber);
269
380
  });
270
381
  }
@@ -276,9 +387,12 @@ function recordDependency(signal2) {
276
387
  const active = sig.__activeNode ?? null;
277
388
  if (active !== null && active.sub === sub) {
278
389
  active.epoch = epoch;
390
+ active.depVersion = sig.__v ?? Number.NaN;
279
391
  return;
280
392
  }
393
+ if (sig._validate !== void 0) sub._hasComputedDep = true;
281
394
  const node = allocNode(signal2, sub, epoch);
395
+ node.depVersion = sig.__v ?? Number.NaN;
282
396
  node.prevActive = active;
283
397
  sig.__activeNode = node;
284
398
  linkSub(sub, node);
@@ -297,9 +411,10 @@ function cleanup(subscriber) {
297
411
  node = next;
298
412
  }
299
413
  }
300
- var maxSubscriberRepeats = 50;
414
+ var maxSubscriberRepeats = 1e3;
301
415
  var maxDrainIterations = 1e6;
302
416
  var drainEpoch = 0;
417
+ var quarantined = /* @__PURE__ */ new Set();
303
418
  function setMaxSubscriberRepeats(n) {
304
419
  const prev = maxSubscriberRepeats;
305
420
  if (Number.isFinite(n) && n > 0) maxSubscriberRepeats = Math.floor(n);
@@ -321,19 +436,21 @@ function tickRepeat(sub) {
321
436
  return s._runs > maxSubscriberRepeats;
322
437
  }
323
438
  function cycleError(sub) {
324
- if (typeof console !== "undefined") {
325
- const name = sub.__name ?? "<unnamed>";
326
- console.error(
327
- `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
328
- );
329
- }
439
+ const name = sub.__name ?? "<unnamed>";
440
+ reportError(
441
+ new Error(
442
+ `subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. This subscriber is quarantined for the rest of this update; other pending work still runs.`
443
+ ),
444
+ { phase: "scheduler", name }
445
+ );
330
446
  }
331
447
  function absoluteDrainError() {
332
- if (typeof console !== "undefined") {
333
- console.error(
334
- `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
335
- );
336
- }
448
+ reportError(
449
+ new Error(
450
+ `Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
451
+ ),
452
+ { phase: "scheduler" }
453
+ );
337
454
  }
338
455
  function drainQueue() {
339
456
  let i = 0;
@@ -343,11 +460,14 @@ function drainQueue() {
343
460
  break;
344
461
  }
345
462
  const sub = pendingQueue[i++];
463
+ pendingSet.delete(sub);
464
+ if (quarantined.size > 0 && quarantined.has(sub)) continue;
465
+ if (sub._hasComputedDep === true && !depsChanged(sub)) continue;
346
466
  if (tickRepeat(sub)) {
347
467
  cycleError(sub);
348
- break;
468
+ quarantined.add(sub);
469
+ continue;
349
470
  }
350
- pendingSet.delete(sub);
351
471
  safeInvoke(sub);
352
472
  }
353
473
  }
@@ -362,6 +482,7 @@ function drainNotificationQueue() {
362
482
  if (notifyDepth === 0) {
363
483
  pendingQueue.length = 0;
364
484
  pendingSet.clear();
485
+ if (quarantined.size > 0) quarantined.clear();
365
486
  }
366
487
  }
367
488
  }
@@ -455,6 +576,7 @@ function notifySubscribers(signal2) {
455
576
  if (notifyDepth === 0) {
456
577
  pendingQueue.length = 0;
457
578
  pendingSet.clear();
579
+ if (quarantined.size > 0) quarantined.clear();
458
580
  }
459
581
  }
460
582
  }
@@ -481,14 +603,14 @@ function forEachSubscriber(signal2, visit) {
481
603
  }
482
604
 
483
605
  // src/reactivity/track.ts
484
- var _isDev3 = isDev();
485
- var _runtimeVersion = true ? "3.4.0" : "dev";
606
+ var _isDev2 = isDev();
607
+ var _runtimeVersion = true ? "4.0.0" : "dev";
486
608
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
487
609
  function resolveReactiveApi() {
488
610
  const g = globalThis;
489
611
  const existing = g[REGISTRY_KEY];
490
612
  if (existing) {
491
- if (_isDev3 && !existing.__dupWarned) {
613
+ if (_isDev2 && !existing.__dupWarned) {
492
614
  existing.__dupWarned = true;
493
615
  devWarn(
494
616
  `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
@@ -573,6 +695,21 @@ function derived(getter, options) {
573
695
  }, markDirty);
574
696
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
575
697
  let evaluating = false;
698
+ const validate = () => {
699
+ if (!cs._d) return;
700
+ const oldValue = cs._v;
701
+ evaluating = true;
702
+ try {
703
+ retrack2(recompute, markDirty);
704
+ if (!Object.is(oldValue, cs._v)) cs.__v++;
705
+ } finally {
706
+ evaluating = false;
707
+ }
708
+ if (hook && !Object.is(oldValue, cs._v)) {
709
+ hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
710
+ }
711
+ };
712
+ cs._validate = validate;
576
713
  function computedGetter() {
577
714
  if (evaluating) {
578
715
  throw new Error(
@@ -580,32 +717,11 @@ function derived(getter, options) {
580
717
  );
581
718
  }
582
719
  if (isTrackingSuspended2()) {
583
- if (cs._d) {
584
- const prev = cs._v;
585
- evaluating = true;
586
- try {
587
- retrack2(recompute, markDirty);
588
- if (!Object.is(prev, cs._v)) cs.__v++;
589
- } finally {
590
- evaluating = false;
591
- }
592
- }
720
+ if (cs._d) validate();
593
721
  return cs._v;
594
722
  }
723
+ if (cs._d) validate();
595
724
  recordDependency2(cs);
596
- if (cs._d) {
597
- const oldValue = cs._v;
598
- evaluating = true;
599
- try {
600
- retrack2(recompute, markDirty);
601
- if (!Object.is(oldValue, cs._v)) cs.__v++;
602
- } finally {
603
- evaluating = false;
604
- }
605
- if (hook && oldValue !== cs._v) {
606
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
607
- }
608
- }
609
725
  return cs._v;
610
726
  }
611
727
  if (debugName) {
@@ -627,20 +743,24 @@ function detectSSRShared() {
627
743
  const getBuiltin = process.getBuiltinModule;
628
744
  if (typeof getBuiltin === "function") {
629
745
  mod = getBuiltin("node:async_hooks");
630
- } else {
631
- const req = Function("return typeof require==='function'?require:null")();
632
- if (req) mod = req("node:async_hooks");
746
+ } else if (typeof require === "function") {
747
+ mod = require("async_hooks");
633
748
  }
634
749
  if (mod) detected = new mod.AsyncLocalStorage();
635
750
  }
636
751
  } catch {
637
752
  detected = null;
638
753
  }
639
- return { als: detected, fallbackStore: { ssr: false, suspenseIdCounter: 0 } };
754
+ return {
755
+ als: detected,
756
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
757
+ fallbackDepth: 0
758
+ };
640
759
  }
641
760
  var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
642
761
  var als = _shared.als;
643
762
  var fallbackStore = _shared.fallbackStore;
763
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
644
764
  function getSSRStore() {
645
765
  if (als) {
646
766
  const s = als.getStore();
@@ -674,7 +794,7 @@ function flushUserCleanups(ctx) {
674
794
  try {
675
795
  list[i]();
676
796
  } catch (err) {
677
- if (typeof console !== "undefined") console.warn("[SibuJS effect] onCleanup threw:", err);
797
+ reportError(err, { phase: "cleanup" });
678
798
  }
679
799
  }
680
800
  }
@@ -687,11 +807,12 @@ function drainReruns(ctx) {
687
807
  } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
688
808
  if (ctx.rerunPending) {
689
809
  ctx.rerunPending = false;
690
- if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
691
- console.error(
692
- `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
693
- );
694
- }
810
+ reportError(
811
+ new Error(
812
+ `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
813
+ ),
814
+ { phase: "effect" }
815
+ );
695
816
  }
696
817
  }
697
818
  function disposeEffect(ctx) {
@@ -768,6 +889,8 @@ function effect(effectFn, options) {
768
889
  sub.depsTail = null;
769
890
  sub._epoch = 0;
770
891
  sub._structDirty = false;
892
+ sub._hasComputedDep = false;
893
+ sub._errorPhase = "effect";
771
894
  sub._runEpoch = 0;
772
895
  sub._runs = 0;
773
896
  ctx.subscriber = sub;
@@ -836,70 +959,72 @@ var isBatching = API2.isBatching;
836
959
 
837
960
  // src/core/signals/signal.ts
838
961
  var _g2 = globalThis;
839
- var _isDev4 = isDev();
962
+ var _isDev3 = isDev();
840
963
  function signal(initial, options) {
841
- const state = {
964
+ const state2 = {
842
965
  value: initial,
843
966
  __v: 0,
844
967
  __sc: 0,
845
968
  subsHead: null,
846
969
  subsTail: null,
847
970
  __activeNode: null,
848
- __name: void 0
971
+ __name: void 0,
972
+ _d: false,
973
+ _validate: void 0
849
974
  };
850
- const debugName = _isDev4 ? options?.name : void 0;
975
+ const debugName = _isDev3 ? options?.name : void 0;
851
976
  const equalsFn = options?.equals;
852
- if (debugName) state.__name = debugName;
977
+ if (debugName) state2.__name = debugName;
853
978
  function get() {
854
- recordDependency2(state);
855
- return state.value;
979
+ recordDependency2(state2);
980
+ return state2.value;
856
981
  }
857
- get.__signal = state;
982
+ get.__signal = state2;
858
983
  if (debugName) get.__name = debugName;
859
984
  let set;
860
985
  if (equalsFn) {
861
986
  set = (next) => {
862
- const prev = state.value;
987
+ const prev = state2.value;
863
988
  const newValue = typeof next === "function" ? next(prev) : next;
864
989
  if (equalsFn(prev, newValue)) return;
865
- state.value = newValue;
866
- state.__v++;
867
- if (_isDev4) {
990
+ state2.value = newValue;
991
+ state2.__v++;
992
+ if (_isDev3) {
868
993
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
869
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
994
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
870
995
  }
871
- if (!enqueueBatchedSignal(state)) {
872
- notifySubscribers2(state);
996
+ if (!enqueueBatchedSignal(state2)) {
997
+ notifySubscribers2(state2);
873
998
  }
874
999
  };
875
- } else if (_isDev4) {
1000
+ } else if (_isDev3) {
876
1001
  set = (next) => {
877
- const prev = state.value;
1002
+ const prev = state2.value;
878
1003
  const newValue = typeof next === "function" ? next(prev) : next;
879
1004
  if (Object.is(newValue, prev)) return;
880
- state.value = newValue;
881
- state.__v++;
1005
+ state2.value = newValue;
1006
+ state2.__v++;
882
1007
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
883
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
884
- if (!enqueueBatchedSignal(state)) {
885
- notifySubscribers2(state);
1008
+ if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1009
+ if (!enqueueBatchedSignal(state2)) {
1010
+ notifySubscribers2(state2);
886
1011
  }
887
1012
  };
888
1013
  } else {
889
1014
  set = (next) => {
890
- const prev = state.value;
1015
+ const prev = state2.value;
891
1016
  const newValue = typeof next === "function" ? next(prev) : next;
892
1017
  if (Object.is(newValue, prev)) return;
893
- state.value = newValue;
894
- state.__v++;
895
- if (!enqueueBatchedSignal(state)) {
896
- notifySubscribers2(state);
1018
+ state2.value = newValue;
1019
+ state2.__v++;
1020
+ if (!enqueueBatchedSignal(state2)) {
1021
+ notifySubscribers2(state2);
897
1022
  }
898
1023
  };
899
1024
  }
900
- if (_isDev4) {
1025
+ if (_isDev3) {
901
1026
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
902
- if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
1027
+ if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
903
1028
  }
904
1029
  return [get, set];
905
1030
  }
@@ -910,6 +1035,50 @@ function globalSingleton(key, create) {
910
1035
  return g[key] ?? (g[key] = create());
911
1036
  }
912
1037
 
1038
+ // src/data/abort.ts
1039
+ function isAbortError(error) {
1040
+ return typeof error === "object" && error !== null && error.name === "AbortError";
1041
+ }
1042
+ function createAbortError(message = "Aborted") {
1043
+ if (typeof DOMException !== "undefined") {
1044
+ return new DOMException(message, "AbortError");
1045
+ }
1046
+ const error = new Error(message);
1047
+ error.name = "AbortError";
1048
+ return error;
1049
+ }
1050
+
1051
+ // src/data/callbacks.ts
1052
+ function reportCallbackError(label, error) {
1053
+ if (typeof console !== "undefined") {
1054
+ console.error(`[SibuJS data] ${label} threw. The operation's own status is unaffected.`, error);
1055
+ }
1056
+ }
1057
+ function runCallback(label, fn) {
1058
+ try {
1059
+ fn();
1060
+ } catch (error) {
1061
+ reportCallbackError(label, error);
1062
+ }
1063
+ }
1064
+ function runSelect(label, fn) {
1065
+ try {
1066
+ return { ok: true, value: fn() };
1067
+ } catch (error) {
1068
+ reportCallbackError(label, error);
1069
+ return { ok: false };
1070
+ }
1071
+ }
1072
+ function notifyListeners(listeners, label) {
1073
+ for (const listener of Array.from(listeners)) {
1074
+ try {
1075
+ listener();
1076
+ } catch (error) {
1077
+ reportCallbackError(label, error);
1078
+ }
1079
+ }
1080
+ }
1081
+
913
1082
  // src/data/retry.ts
914
1083
  function calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter) {
915
1084
  let delay;
@@ -942,39 +1111,54 @@ async function withRetry(fn, options, onRetry, signal2) {
942
1111
  const shouldRetry = options?.shouldRetry ?? (() => true);
943
1112
  let lastError;
944
1113
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
945
- if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
1114
+ if (signal2?.aborted) throw createAbortError();
946
1115
  try {
947
- if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
948
1116
  return await fn();
949
1117
  } catch (error) {
950
1118
  lastError = error;
1119
+ if (signal2?.aborted) throw createAbortError();
1120
+ if (isAbortError(error)) throw error;
951
1121
  if (attempt >= maxRetries || !shouldRetry(error, attempt)) throw error;
952
1122
  const delay = calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter);
953
1123
  onRetry?.(error, attempt, delay);
954
- await new Promise((resolve, reject) => {
955
- let onAbort = null;
956
- const timer = setTimeout(() => {
957
- if (onAbort && signal2) signal2.removeEventListener("abort", onAbort);
958
- resolve();
959
- }, delay);
960
- if (signal2) {
961
- onAbort = () => {
962
- clearTimeout(timer);
963
- reject(new DOMException("Aborted", "AbortError"));
964
- };
965
- signal2.addEventListener("abort", onAbort, { once: true });
966
- }
967
- });
1124
+ await waitForRetryDelay(delay, signal2);
968
1125
  }
969
1126
  }
970
1127
  throw lastError;
971
1128
  }
1129
+ function waitForRetryDelay(delay, signal2) {
1130
+ if (signal2?.aborted) return Promise.reject(createAbortError());
1131
+ return new Promise((resolve, reject) => {
1132
+ let settled = false;
1133
+ const finish = (action) => {
1134
+ if (settled) return;
1135
+ settled = true;
1136
+ clearTimeout(timer);
1137
+ if (signal2) signal2.removeEventListener("abort", onAbort);
1138
+ action();
1139
+ };
1140
+ const onAbort = () => finish(() => reject(createAbortError()));
1141
+ const timer = setTimeout(() => finish(resolve), delay);
1142
+ if (signal2) {
1143
+ signal2.addEventListener("abort", onAbort, { once: true });
1144
+ if (signal2.aborted) onAbort();
1145
+ }
1146
+ });
1147
+ }
972
1148
 
973
1149
  // src/data/query.ts
974
1150
  var globalQueryCache = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.query.cache.v1"), () => /* @__PURE__ */ new Map());
975
1151
  function getActiveQueryCache() {
976
1152
  return getRequestScopedCache("query") ?? globalQueryCache;
977
1153
  }
1154
+ function abandonEntry(entry) {
1155
+ if (entry.gcTimer) clearTimeout(entry.gcTimer);
1156
+ entry.gcTimer = null;
1157
+ entry.controller?.abort();
1158
+ entry.controller = null;
1159
+ entry.promise = null;
1160
+ entry.generation++;
1161
+ }
978
1162
  function getOrCreateEntry(cache, key, initialData) {
979
1163
  let entry = cache.get(key);
980
1164
  if (!entry) {
@@ -986,7 +1170,9 @@ function getOrCreateEntry(cache, key, initialData) {
986
1170
  gcTimer: null,
987
1171
  promise: null,
988
1172
  listeners: /* @__PURE__ */ new Set(),
989
- refetchers: /* @__PURE__ */ new Set()
1173
+ refetchers: /* @__PURE__ */ new Set(),
1174
+ controller: null,
1175
+ generation: 0
990
1176
  };
991
1177
  cache.set(key, entry);
992
1178
  }
@@ -1012,9 +1198,10 @@ function query(key, fetcher, options = {}) {
1012
1198
  const [data, setData] = signal(initialData);
1013
1199
  const [isFetching, setIsFetching] = signal(false);
1014
1200
  const [error, setError] = signal(void 0);
1015
- let abortController = null;
1016
1201
  let disposed = false;
1017
1202
  let currentKey = null;
1203
+ let attachedEntry = null;
1204
+ let attachedKey = null;
1018
1205
  let intervalTimer = null;
1019
1206
  const loading = derived(() => isFetching() && data() === void 0);
1020
1207
  const isStale = derived(() => {
@@ -1024,82 +1211,128 @@ function query(key, fetcher, options = {}) {
1024
1211
  if (!entry || entry.dataUpdatedAt === 0) return true;
1025
1212
  return Date.now() - entry.dataUpdatedAt >= staleTime;
1026
1213
  });
1214
+ function detachFromEntry() {
1215
+ const entry = attachedEntry;
1216
+ const key2 = attachedKey;
1217
+ if (!entry) return;
1218
+ attachedEntry = null;
1219
+ attachedKey = null;
1220
+ entry.listeners.delete(onCacheUpdate);
1221
+ entry.refetchers.delete(doFetch);
1222
+ entry.subscribers = Math.max(0, entry.subscribers - 1);
1223
+ if (entry.subscribers > 0 || cacheTime < 0 || key2 === null) return;
1224
+ if (cache.get(key2) !== entry) return;
1225
+ if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
1226
+ entry.gcTimer = setTimeout(() => {
1227
+ const current = cache.get(key2);
1228
+ if (current === entry && current.subscribers <= 0) {
1229
+ abandonEntry(current);
1230
+ cache.delete(key2);
1231
+ }
1232
+ }, cacheTime);
1233
+ entry.gcTimer.unref?.();
1234
+ }
1235
+ function attachToEntry(entry, key2) {
1236
+ if (attachedEntry === entry) {
1237
+ if (entry.gcTimer !== null) {
1238
+ clearTimeout(entry.gcTimer);
1239
+ entry.gcTimer = null;
1240
+ }
1241
+ return;
1242
+ }
1243
+ detachFromEntry();
1244
+ attachedEntry = entry;
1245
+ attachedKey = key2;
1246
+ entry.subscribers++;
1247
+ entry.listeners.add(onCacheUpdate);
1248
+ entry.refetchers.add(doFetch);
1249
+ if (entry.gcTimer !== null) {
1250
+ clearTimeout(entry.gcTimer);
1251
+ entry.gcTimer = null;
1252
+ }
1253
+ }
1027
1254
  async function doFetch() {
1028
1255
  if (disposed || !currentKey || !enabled) return;
1029
1256
  const key2 = currentKey;
1030
- let entry = cache.get(key2);
1031
- if (!entry) {
1032
- entry = getOrCreateEntry(cache, key2);
1033
- entry.listeners.add(onCacheUpdate);
1034
- entry.refetchers.add(doFetch);
1035
- }
1257
+ const entry = getOrCreateEntry(cache, key2);
1258
+ attachToEntry(entry, key2);
1036
1259
  if (entry.promise) {
1037
1260
  setIsFetching(true);
1038
1261
  const captured = entry.promise;
1039
1262
  try {
1040
1263
  await captured;
1041
- if (disposed || currentKey !== key2) return;
1042
- if (entry.promise === captured) {
1043
- onCacheUpdate();
1044
- if (entry.error) onError?.(entry.error);
1045
- else if (entry.data !== void 0) onSuccess?.(entry.data);
1046
- }
1047
1264
  } catch {
1048
- if (disposed || currentKey !== key2) return;
1049
- if (entry.promise === captured) {
1265
+ } finally {
1266
+ if (!disposed && currentKey === key2) {
1050
1267
  onCacheUpdate();
1051
- if (entry.error) onError?.(entry.error);
1268
+ if (entry.error) {
1269
+ const settledError = entry.error;
1270
+ runCallback("query onError", () => onError?.(settledError));
1271
+ } else if (entry.data !== void 0) {
1272
+ const settledData = entry.data;
1273
+ runCallback("query onSuccess", () => onSuccess?.(settledData));
1274
+ }
1275
+ runCallback("query onSettled", () => onSettled?.());
1052
1276
  }
1053
- } finally {
1054
- if (!disposed && currentKey === key2) onSettled?.();
1055
1277
  }
1056
1278
  return;
1057
1279
  }
1058
- abortController?.abort();
1059
- abortController = new AbortController();
1060
- const signal2 = abortController.signal;
1280
+ entry.controller = new AbortController();
1281
+ const signal2 = entry.controller.signal;
1282
+ const generation = ++entry.generation;
1061
1283
  setIsFetching(true);
1062
1284
  let promise;
1063
1285
  try {
1064
1286
  promise = withRetry(() => fetcher({ signal: signal2, key: key2 }), retryOptions, void 0, signal2);
1065
1287
  } catch (err) {
1066
1288
  setIsFetching(false);
1289
+ if (isAbortError(err)) return;
1067
1290
  const errorObj = err instanceof Error ? err : new Error(String(err));
1068
1291
  entry.error = errorObj;
1069
- onError?.(errorObj);
1070
- onSettled?.();
1292
+ runCallback("query onError", () => onError?.(errorObj));
1293
+ runCallback("query onSettled", () => onSettled?.());
1071
1294
  return;
1072
1295
  }
1073
1296
  entry.promise = promise;
1074
1297
  try {
1075
1298
  const result = await promise;
1299
+ if (entry.generation !== generation) return;
1076
1300
  entry.promise = null;
1077
- if (disposed || currentKey !== key2) return;
1301
+ entry.controller = null;
1078
1302
  entry.data = result;
1079
1303
  entry.dataUpdatedAt = Date.now();
1080
1304
  entry.error = void 0;
1081
- const selected = select ? select(result) : result;
1305
+ notifyListeners(entry.listeners, "query cache listener");
1306
+ if (disposed || currentKey !== key2) return;
1307
+ const selected = runSelect("query select", () => select ? select(result) : result);
1082
1308
  batch(() => {
1083
- setData(selected);
1309
+ if (selected.ok) setData(selected.value);
1084
1310
  setIsFetching(false);
1085
1311
  setError(void 0);
1086
1312
  });
1087
- for (const listener of entry.listeners) listener();
1088
- onSuccess?.(result);
1313
+ runCallback("query onSuccess", () => onSuccess?.(result));
1089
1314
  } catch (err) {
1315
+ if (entry.generation !== generation) return;
1090
1316
  entry.promise = null;
1091
- if (disposed || currentKey !== key2) return;
1092
- if (err instanceof DOMException && err.name === "AbortError") return;
1317
+ entry.controller = null;
1318
+ if (isAbortError(err)) {
1319
+ notifyListeners(entry.listeners, "query cache listener");
1320
+ if (!disposed && currentKey === key2) setIsFetching(false);
1321
+ return;
1322
+ }
1093
1323
  const errorObj = err instanceof Error ? err : new Error(String(err));
1094
1324
  entry.error = errorObj;
1325
+ notifyListeners(entry.listeners, "query cache listener");
1326
+ if (disposed || currentKey !== key2) return;
1095
1327
  batch(() => {
1096
1328
  setError(errorObj);
1097
1329
  setIsFetching(false);
1098
1330
  });
1099
- for (const listener of entry.listeners) listener();
1100
- onError?.(errorObj);
1331
+ runCallback("query onError", () => onError?.(errorObj));
1101
1332
  } finally {
1102
- if (!disposed && currentKey === key2) onSettled?.();
1333
+ if (!disposed && currentKey === key2 && entry.generation === generation) {
1334
+ runCallback("query onSettled", () => onSettled?.());
1335
+ }
1103
1336
  }
1104
1337
  }
1105
1338
  function onCacheUpdate() {
@@ -1114,43 +1347,24 @@ function query(key, fetcher, options = {}) {
1114
1347
  return;
1115
1348
  }
1116
1349
  const raw = entry.data;
1117
- const selected = raw !== void 0 && select ? select(raw) : raw;
1350
+ const selected = raw !== void 0 && select ? runSelect("query select", () => select(raw)) : { ok: true, value: raw };
1118
1351
  batch(() => {
1119
- setData(selected);
1352
+ if (selected.ok) setData(selected.value);
1120
1353
  setError(entry.error);
1121
1354
  if (!entry.promise) setIsFetching(false);
1122
1355
  });
1123
1356
  }
1124
1357
  const effectCleanup = effect(() => {
1125
1358
  const key2 = resolveKey();
1126
- if (currentKey !== null && currentKey !== key2) {
1127
- const oldEntry = cache.get(currentKey);
1128
- if (oldEntry) {
1129
- oldEntry.listeners.delete(onCacheUpdate);
1130
- oldEntry.refetchers.delete(doFetch);
1131
- oldEntry.subscribers--;
1132
- if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
1133
- const oldKey = currentKey;
1134
- if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
1135
- oldEntry.gcTimer = setTimeout(() => cache.delete(oldKey), cacheTime);
1136
- }
1137
- }
1138
- }
1139
1359
  const keyChanged = currentKey !== key2;
1140
1360
  currentKey = key2;
1141
1361
  const entry = getOrCreateEntry(cache, key2, initialData);
1142
- if (keyChanged) entry.subscribers++;
1143
- if (entry.gcTimer !== null) {
1144
- clearTimeout(entry.gcTimer);
1145
- entry.gcTimer = null;
1146
- }
1147
- entry.listeners.add(onCacheUpdate);
1148
- entry.refetchers.add(doFetch);
1362
+ attachToEntry(entry, key2);
1149
1363
  if (entry.data !== void 0) {
1150
1364
  const raw = entry.data;
1151
- const selected = select ? select(raw) : raw;
1365
+ const selected = runSelect("query select", () => select ? select(raw) : raw);
1152
1366
  batch(() => {
1153
- setData(selected);
1367
+ if (selected.ok) setData(selected.value);
1154
1368
  setError(entry.error);
1155
1369
  });
1156
1370
  }
@@ -1188,22 +1402,9 @@ function query(key, fetcher, options = {}) {
1188
1402
  function dispose() {
1189
1403
  if (disposed) return;
1190
1404
  disposed = true;
1191
- abortController?.abort();
1192
1405
  effectCleanup();
1193
1406
  if (intervalTimer) clearInterval(intervalTimer);
1194
- if (currentKey) {
1195
- const entry = cache.get(currentKey);
1196
- if (entry) {
1197
- entry.listeners.delete(onCacheUpdate);
1198
- entry.refetchers.delete(doFetch);
1199
- entry.subscribers--;
1200
- if (entry.subscribers <= 0 && cacheTime >= 0) {
1201
- const key2 = currentKey;
1202
- if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
1203
- entry.gcTimer = setTimeout(() => cache.delete(key2), cacheTime);
1204
- }
1205
- }
1206
- }
1407
+ detachFromEntry();
1207
1408
  if (focusHandler && typeof globalThis.removeEventListener === "function") {
1208
1409
  globalThis.removeEventListener("focus", focusHandler);
1209
1410
  }
@@ -1239,21 +1440,21 @@ function setQueryData(key, data) {
1239
1440
  const newData = typeof data === "function" ? data(entry.data) : data;
1240
1441
  entry.data = newData;
1241
1442
  entry.dataUpdatedAt = Date.now();
1242
- for (const listener of entry.listeners) listener();
1443
+ notifyListeners(entry.listeners, "query cache listener");
1243
1444
  }
1244
1445
  function clearQueryCache() {
1245
1446
  const activeListeners = [];
1246
1447
  const activeRefetchers = [];
1247
1448
  const activeCache = getActiveQueryCache();
1248
1449
  for (const entry of activeCache.values()) {
1249
- if (entry.gcTimer) clearTimeout(entry.gcTimer);
1250
1450
  if (entry.subscribers > 0) {
1251
1451
  for (const listener of entry.listeners) activeListeners.push(listener);
1252
1452
  for (const refetcher of entry.refetchers) activeRefetchers.push(refetcher);
1253
1453
  }
1454
+ abandonEntry(entry);
1254
1455
  }
1255
1456
  activeCache.clear();
1256
- for (const listener of activeListeners) listener();
1457
+ notifyListeners(activeListeners, "query cache listener");
1257
1458
  for (const refetcher of activeRefetchers) {
1258
1459
  refetcher().catch((err) => {
1259
1460
  if (typeof console !== "undefined") {
@@ -1265,7 +1466,7 @@ function clearQueryCache() {
1265
1466
  function __resetQueryCache() {
1266
1467
  const activeCache = getActiveQueryCache();
1267
1468
  for (const entry of activeCache.values()) {
1268
- if (entry.gcTimer) clearTimeout(entry.gcTimer);
1469
+ abandonEntry(entry);
1269
1470
  }
1270
1471
  activeCache.clear();
1271
1472
  }
@@ -1280,6 +1481,8 @@ function mutation(mutationFn, options = {}) {
1280
1481
  const isIdle = derived(() => status() === "idle");
1281
1482
  let runId = 0;
1282
1483
  let abortController = null;
1484
+ let terminalStatus = "idle";
1485
+ let terminalError;
1283
1486
  async function execute(variables) {
1284
1487
  abortController?.abort();
1285
1488
  abortController = new AbortController();
@@ -1297,25 +1500,38 @@ function mutation(mutationFn, options = {}) {
1297
1500
  }
1298
1501
  const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
1299
1502
  if (myRun !== runId) return result;
1503
+ terminalStatus = "success";
1504
+ terminalError = void 0;
1300
1505
  batch(() => {
1301
1506
  setData(result);
1302
1507
  setLoading(false);
1303
1508
  setStatus("success");
1304
1509
  });
1305
- options.onSuccess?.(result, variables, context2);
1306
- options.onSettled?.(result, void 0, variables, context2);
1510
+ runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context2));
1511
+ runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context2));
1307
1512
  return result;
1308
1513
  } catch (err) {
1514
+ if (isAbortError(err)) {
1515
+ if (myRun === runId) {
1516
+ batch(() => {
1517
+ setLoading(false);
1518
+ setError(terminalError);
1519
+ setStatus(terminalStatus);
1520
+ });
1521
+ }
1522
+ throw err;
1523
+ }
1309
1524
  const errorObj = err instanceof Error ? err : new Error(String(err));
1310
- if (errorObj instanceof DOMException && errorObj.name === "AbortError") throw errorObj;
1311
1525
  if (myRun !== runId) throw errorObj;
1526
+ terminalStatus = "error";
1527
+ terminalError = errorObj;
1312
1528
  batch(() => {
1313
1529
  setError(errorObj);
1314
1530
  setLoading(false);
1315
1531
  setStatus("error");
1316
1532
  });
1317
- options.onError?.(errorObj, variables, context2);
1318
- options.onSettled?.(void 0, errorObj, variables, context2);
1533
+ runCallback("mutation onError", () => options.onError?.(errorObj, variables, context2));
1534
+ runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context2));
1319
1535
  throw errorObj;
1320
1536
  }
1321
1537
  }
@@ -1323,6 +1539,8 @@ function mutation(mutationFn, options = {}) {
1323
1539
  runId++;
1324
1540
  abortController?.abort();
1325
1541
  abortController = null;
1542
+ terminalStatus = "idle";
1543
+ terminalError = void 0;
1326
1544
  batch(() => {
1327
1545
  setData(void 0);
1328
1546
  setError(void 0);
@@ -1338,7 +1556,7 @@ function mutation(mutationFn, options = {}) {
1338
1556
  isIdle,
1339
1557
  mutate: (variables) => {
1340
1558
  execute(variables).catch((err) => {
1341
- if (err instanceof DOMException && err.name === "AbortError") return;
1559
+ if (isAbortError(err)) return;
1342
1560
  if (typeof console !== "undefined") {
1343
1561
  console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
1344
1562
  }
@@ -1411,10 +1629,17 @@ function infiniteQuery(key, fetcher, options) {
1411
1629
  setIsFetchingNext(false);
1412
1630
  setIsFetchingPrev(false);
1413
1631
  });
1414
- onSuccess?.(newPages);
1632
+ runCallback("infiniteQuery onSuccess", () => onSuccess?.(newPages));
1415
1633
  } catch (err) {
1416
1634
  if (disposed || myRun !== runId) return;
1417
- if (err instanceof DOMException && err.name === "AbortError") return;
1635
+ if (isAbortError(err)) {
1636
+ batch(() => {
1637
+ setIsFetching(false);
1638
+ setIsFetchingNext(false);
1639
+ setIsFetchingPrev(false);
1640
+ });
1641
+ return;
1642
+ }
1418
1643
  const errorObj = err instanceof Error ? err : new Error(String(err));
1419
1644
  batch(() => {
1420
1645
  setError(errorObj);
@@ -1422,7 +1647,7 @@ function infiniteQuery(key, fetcher, options) {
1422
1647
  setIsFetchingNext(false);
1423
1648
  setIsFetchingPrev(false);
1424
1649
  });
1425
- onError?.(errorObj);
1650
+ runCallback("infiniteQuery onError", () => onError?.(errorObj));
1426
1651
  }
1427
1652
  })();
1428
1653
  inFlight = promise;
@@ -1604,7 +1829,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1604
1829
  setLoading(true);
1605
1830
  setError(void 0);
1606
1831
  });
1607
- options.onStart?.();
1832
+ runCallback("resource onStart", () => options.onStart?.());
1608
1833
  try {
1609
1834
  const result = await withRetry(() => fetcher(sourceValue, { signal: signal2, prev }), options.retry, void 0, signal2);
1610
1835
  if (version !== fetchVersion || disposed) return;
@@ -1613,10 +1838,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1613
1838
  setData(result);
1614
1839
  setLoading(false);
1615
1840
  });
1616
- options.onSuccess?.(result);
1841
+ runCallback("resource onSuccess", () => options.onSuccess?.(result));
1617
1842
  } catch (err) {
1618
1843
  if (version !== fetchVersion || disposed) return;
1619
- if (err instanceof DOMException && err.name === "AbortError") {
1844
+ if (isAbortError(err)) {
1620
1845
  if (version === fetchVersion) setLoading(false);
1621
1846
  return;
1622
1847
  }
@@ -1625,10 +1850,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
1625
1850
  setError(errorObj);
1626
1851
  setLoading(false);
1627
1852
  });
1628
- options.onError?.(errorObj);
1853
+ runCallback("resource onError", () => options.onError?.(errorObj));
1629
1854
  } finally {
1630
1855
  if (version === fetchVersion) {
1631
- options.onSettled?.();
1856
+ runCallback("resource onSettled", () => options.onSettled?.());
1632
1857
  }
1633
1858
  }
1634
1859
  }
@@ -1816,13 +2041,14 @@ async function offlineStore(options) {
1816
2041
  return data().filter(filter);
1817
2042
  }
1818
2043
  async function sync() {
1819
- if (!adapter || isSyncing() || closed) return;
2044
+ const syncAdapter2 = adapter;
2045
+ if (!syncAdapter2 || isSyncing() || closed) return;
1820
2046
  setIsSyncing(true);
1821
2047
  try {
1822
2048
  const snapshot = await idbGetAllWithKeys(db, "_changes");
1823
2049
  if (closed) return;
1824
2050
  if (snapshot.length > 0) {
1825
- const result = await adapter.push(snapshot.map((e) => e.value));
2051
+ const result = await syncAdapter2.push(snapshot.map((e) => e.value));
1826
2052
  if (closed) return;
1827
2053
  if (result.ok) {
1828
2054
  await idbDeleteKeys(
@@ -1835,20 +2061,18 @@ async function offlineStore(options) {
1835
2061
  console.warn(`[offlineStore] push rejected by adapter${result.error ? `: ${result.error}` : ""}`);
1836
2062
  }
1837
2063
  }
1838
- const remoteItems = await adapter.pull(lastSynced());
2064
+ const remoteItems = await syncAdapter2.pull(lastSynced());
1839
2065
  if (closed) return;
1840
2066
  const pendingChanges = await idbGetAll(db, "_changes");
1841
2067
  if (closed) return;
1842
- const pendingKeys = /* @__PURE__ */ new Set();
2068
+ const pendingByKey = /* @__PURE__ */ new Map();
1843
2069
  for (const c of pendingChanges) {
1844
2070
  const k = c.item[keyPath];
1845
- if (k != null) pendingKeys.add(k);
2071
+ if (k != null) pendingByKey.set(k, c);
1846
2072
  }
1847
- const safeRemote = remoteItems.filter((item) => {
1848
- const k = item[keyPath];
1849
- return k == null || !pendingKeys.has(k);
1850
- });
1851
- await idbPutMany(db, "items", safeRemote);
2073
+ const resolved = await resolveRemoteItems(syncAdapter2, remoteItems, pendingByKey);
2074
+ if (closed) return;
2075
+ await idbPutMany(db, "items", resolved);
1852
2076
  if (closed) return;
1853
2077
  const now = Date.now();
1854
2078
  await idbPut(db, "_meta", now, "lastSynced");
@@ -1861,6 +2085,42 @@ async function offlineStore(options) {
1861
2085
  setIsSyncing(false);
1862
2086
  }
1863
2087
  }
2088
+ async function resolveRemoteItems(activeAdapter, remoteItems, pendingByKey) {
2089
+ const strategy = activeAdapter.conflictStrategy ?? "client-wins";
2090
+ const out = [];
2091
+ for (const remote of remoteItems) {
2092
+ const key = remote[keyPath];
2093
+ const pending = key == null ? void 0 : pendingByKey.get(key);
2094
+ if (!pending) {
2095
+ out.push(remote);
2096
+ continue;
2097
+ }
2098
+ if (strategy === "server-wins") {
2099
+ out.push(remote);
2100
+ continue;
2101
+ }
2102
+ if (strategy === "manual") {
2103
+ if (!activeAdapter.resolveConflict) {
2104
+ if (typeof console !== "undefined") {
2105
+ console.warn(
2106
+ '[offlineStore] conflictStrategy is "manual" but the adapter has no resolveConflict(); keeping the local record for this key (client-wins).'
2107
+ );
2108
+ }
2109
+ continue;
2110
+ }
2111
+ try {
2112
+ const local = pending.type === "delete" ? void 0 : await idbGet(db, "items", key);
2113
+ const choice = await activeAdapter.resolveConflict({ key, local, remote, pending });
2114
+ if (choice !== void 0) out.push(choice);
2115
+ } catch (err) {
2116
+ if (typeof console !== "undefined") {
2117
+ console.warn("[offlineStore] resolveConflict threw; keeping the local record", err);
2118
+ }
2119
+ }
2120
+ }
2121
+ }
2122
+ return out;
2123
+ }
1864
2124
  function attach(newAdapter) {
1865
2125
  adapter = newAdapter;
1866
2126
  }
@@ -1899,10 +2159,18 @@ function syncAdapter(config) {
1899
2159
  }
1900
2160
 
1901
2161
  // src/core/rendering/context.ts
2162
+ var _isDev4 = isDev();
1902
2163
  function context(defaultValue) {
1903
2164
  const [getValue, setValue] = signal(defaultValue);
2165
+ const warnIfSSR = (method) => {
2166
+ if (!_isDev4 || !isSSR()) return;
2167
+ devWarn(
2168
+ `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.`
2169
+ );
2170
+ };
1904
2171
  const ctx = {
1905
2172
  provide(value) {
2173
+ warnIfSSR("provide");
1906
2174
  const previous2 = getValue();
1907
2175
  setValue(value);
1908
2176
  return () => setValue(previous2);
@@ -1914,13 +2182,21 @@ function context(defaultValue) {
1914
2182
  return getValue();
1915
2183
  },
1916
2184
  set(value) {
2185
+ warnIfSSR("set");
1917
2186
  setValue(value);
1918
2187
  },
1919
2188
  withContext(value, fn) {
2189
+ warnIfSSR("withContext");
1920
2190
  const previous2 = getValue();
1921
2191
  setValue(value);
1922
2192
  try {
1923
- return fn();
2193
+ const result = fn();
2194
+ if (_isDev4 && result !== null && typeof result?.then === "function") {
2195
+ devWarn(
2196
+ "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."
2197
+ );
2198
+ }
2199
+ return result;
1924
2200
  } finally {
1925
2201
  setValue(previous2);
1926
2202
  }
@@ -2077,10 +2353,12 @@ function stripControlChars(value) {
2077
2353
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
2078
2354
  }
2079
2355
  var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
2356
+ var OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
2080
2357
  function sanitizeUrl(url) {
2081
- const trimmed = stripControlChars(url).trim();
2082
- if (!trimmed) return "";
2083
- const lower = trimmed.toLowerCase();
2358
+ const probe = stripControlChars(url).trim();
2359
+ if (!probe) return "";
2360
+ const output = url.replace(OUTER_TRIM, "");
2361
+ const lower = probe.toLowerCase();
2084
2362
  let schemeEnd = -1;
2085
2363
  for (let i = 0; i < lower.length; i++) {
2086
2364
  const ch = lower.charCodeAt(i);
@@ -2090,11 +2368,11 @@ function sanitizeUrl(url) {
2090
2368
  }
2091
2369
  if (ch === 47 || ch === 63 || ch === 35) break;
2092
2370
  }
2093
- if (schemeEnd === -1) return trimmed;
2371
+ if (schemeEnd === -1) return output;
2094
2372
  const scheme = lower.slice(0, schemeEnd + 1);
2095
- if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
2373
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return output;
2096
2374
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
2097
- return trimmed;
2375
+ return output;
2098
2376
  }
2099
2377
 
2100
2378
  // src/ui/stream.ts