sibujs 3.4.1 → 4.0.1

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 +784 -392
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-PBZWJQNY.js → chunk-52ERIVFD.js} +8 -8
  11. package/dist/chunk-52XFPGSN.js +123 -0
  12. package/dist/{chunk-Y22QAKJX.js → chunk-5BVKQX2F.js} +4 -4
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-4I3M326N.js → chunk-AD5YSZAN.js} +19 -25
  15. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  16. package/dist/chunk-DHDZ7IVN.js +109 -0
  17. package/dist/{chunk-VQZDHAEK.js → chunk-E72G5JSK.js} +1 -1
  18. package/dist/{chunk-MPE75KOA.js → chunk-HPXTUPJD.js} +259 -124
  19. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  20. package/dist/{chunk-FWFEBYQ6.js → chunk-IKVUUE7D.js} +115 -41
  21. package/dist/{chunk-5MFF5EPT.js → chunk-J4WZMCIW.js} +5 -5
  22. package/dist/{chunk-BQLVTAUZ.js → chunk-JYXQCELN.js} +57 -22
  23. package/dist/{chunk-WEZ5SP7L.js → chunk-PHB3MCK4.js} +73 -34
  24. package/dist/chunk-PHRH66Y7.js +147 -0
  25. package/dist/{chunk-S2VW4NBA.js → chunk-PJDSI2K7.js} +295 -198
  26. package/dist/{chunk-QHPUFNHZ.js → chunk-PTLJ7NQX.js} +3 -3
  27. package/dist/{chunk-NXR2YFZB.js → chunk-QD4I7ZGZ.js} +24 -34
  28. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  29. package/dist/{chunk-7PNKW3FJ.js → chunk-TNEHT3NG.js} +232 -67
  30. package/dist/{chunk-335KJEN2.js → chunk-TVURNWDK.js} +5 -3
  31. package/dist/{chunk-CV7FAQ2R.js → chunk-UNAYBND2.js} +9 -3
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-X4JS367G.js → chunk-XDOMTQHM.js} +14 -10
  34. package/dist/chunk-XET2RUJF.js +61 -0
  35. package/dist/{chunk-3FFCUIGV.js → chunk-XZMCVZW6.js} +309 -188
  36. package/dist/{chunk-YDWP3ZOU.js → chunk-YLV26GAJ.js} +228 -86
  37. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.ts → 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 +426 -182
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1988 -772
  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 +791 -392
  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 +1378 -583
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +610 -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 +1111 -450
  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-6CAJU5TP.js +0 -38
  96. package/dist/chunk-COY6PUD2.js +0 -34
  97. package/dist/chunk-DLBFEAIS.js +0 -96
  98. package/dist/chunk-L3GAGWCC.js +0 -111
@@ -1,25 +1,69 @@
1
1
  import {
2
2
  context
3
- } from "./chunk-6CAJU5TP.js";
3
+ } from "./chunk-XET2RUJF.js";
4
4
  import {
5
5
  derived
6
- } from "./chunk-4I3M326N.js";
6
+ } from "./chunk-AD5YSZAN.js";
7
+ import {
8
+ sanitizeUrl
9
+ } from "./chunk-5INI7D2L.js";
7
10
  import {
8
11
  globalSingleton
9
12
  } from "./chunk-7ZHH77QA.js";
10
- import {
11
- sanitizeUrl
12
- } from "./chunk-L3GAGWCC.js";
13
13
  import {
14
14
  effect
15
- } from "./chunk-X4JS367G.js";
15
+ } from "./chunk-XDOMTQHM.js";
16
16
  import {
17
17
  getRequestScopedCache
18
- } from "./chunk-S3NFJO6L.js";
18
+ } from "./chunk-TIRZCERI.js";
19
19
  import {
20
20
  batch,
21
21
  signal
22
- } from "./chunk-335KJEN2.js";
22
+ } from "./chunk-TVURNWDK.js";
23
+
24
+ // src/data/abort.ts
25
+ function isAbortError(error) {
26
+ return typeof error === "object" && error !== null && error.name === "AbortError";
27
+ }
28
+ function createAbortError(message = "Aborted") {
29
+ if (typeof DOMException !== "undefined") {
30
+ return new DOMException(message, "AbortError");
31
+ }
32
+ const error = new Error(message);
33
+ error.name = "AbortError";
34
+ return error;
35
+ }
36
+
37
+ // src/data/callbacks.ts
38
+ function reportCallbackError(label, error) {
39
+ if (typeof console !== "undefined") {
40
+ console.error(`[SibuJS data] ${label} threw. The operation's own status is unaffected.`, error);
41
+ }
42
+ }
43
+ function runCallback(label, fn) {
44
+ try {
45
+ fn();
46
+ } catch (error) {
47
+ reportCallbackError(label, error);
48
+ }
49
+ }
50
+ function runSelect(label, fn) {
51
+ try {
52
+ return { ok: true, value: fn() };
53
+ } catch (error) {
54
+ reportCallbackError(label, error);
55
+ return { ok: false };
56
+ }
57
+ }
58
+ function notifyListeners(listeners, label) {
59
+ for (const listener of Array.from(listeners)) {
60
+ try {
61
+ listener();
62
+ } catch (error) {
63
+ reportCallbackError(label, error);
64
+ }
65
+ }
66
+ }
23
67
 
24
68
  // src/data/retry.ts
25
69
  function calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter) {
@@ -53,39 +97,54 @@ async function withRetry(fn, options, onRetry, signal2) {
53
97
  const shouldRetry = options?.shouldRetry ?? (() => true);
54
98
  let lastError;
55
99
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
56
- if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
100
+ if (signal2?.aborted) throw createAbortError();
57
101
  try {
58
- if (signal2?.aborted) throw new DOMException("Aborted", "AbortError");
59
102
  return await fn();
60
103
  } catch (error) {
61
104
  lastError = error;
105
+ if (signal2?.aborted) throw createAbortError();
106
+ if (isAbortError(error)) throw error;
62
107
  if (attempt >= maxRetries || !shouldRetry(error, attempt)) throw error;
63
108
  const delay = calculateDelay(attempt, strategy, baseDelay, maxDelay, jitter);
64
109
  onRetry?.(error, attempt, delay);
65
- await new Promise((resolve, reject) => {
66
- let onAbort = null;
67
- const timer = setTimeout(() => {
68
- if (onAbort && signal2) signal2.removeEventListener("abort", onAbort);
69
- resolve();
70
- }, delay);
71
- if (signal2) {
72
- onAbort = () => {
73
- clearTimeout(timer);
74
- reject(new DOMException("Aborted", "AbortError"));
75
- };
76
- signal2.addEventListener("abort", onAbort, { once: true });
77
- }
78
- });
110
+ await waitForRetryDelay(delay, signal2);
79
111
  }
80
112
  }
81
113
  throw lastError;
82
114
  }
115
+ function waitForRetryDelay(delay, signal2) {
116
+ if (signal2?.aborted) return Promise.reject(createAbortError());
117
+ return new Promise((resolve, reject) => {
118
+ let settled = false;
119
+ const finish = (action) => {
120
+ if (settled) return;
121
+ settled = true;
122
+ clearTimeout(timer);
123
+ if (signal2) signal2.removeEventListener("abort", onAbort);
124
+ action();
125
+ };
126
+ const onAbort = () => finish(() => reject(createAbortError()));
127
+ const timer = setTimeout(() => finish(resolve), delay);
128
+ if (signal2) {
129
+ signal2.addEventListener("abort", onAbort, { once: true });
130
+ if (signal2.aborted) onAbort();
131
+ }
132
+ });
133
+ }
83
134
 
84
135
  // src/data/query.ts
85
136
  var globalQueryCache = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.query.cache.v1"), () => /* @__PURE__ */ new Map());
86
137
  function getActiveQueryCache() {
87
138
  return getRequestScopedCache("query") ?? globalQueryCache;
88
139
  }
140
+ function abandonEntry(entry) {
141
+ if (entry.gcTimer) clearTimeout(entry.gcTimer);
142
+ entry.gcTimer = null;
143
+ entry.controller?.abort();
144
+ entry.controller = null;
145
+ entry.promise = null;
146
+ entry.generation++;
147
+ }
89
148
  function getOrCreateEntry(cache, key, initialData) {
90
149
  let entry = cache.get(key);
91
150
  if (!entry) {
@@ -97,7 +156,9 @@ function getOrCreateEntry(cache, key, initialData) {
97
156
  gcTimer: null,
98
157
  promise: null,
99
158
  listeners: /* @__PURE__ */ new Set(),
100
- refetchers: /* @__PURE__ */ new Set()
159
+ refetchers: /* @__PURE__ */ new Set(),
160
+ controller: null,
161
+ generation: 0
101
162
  };
102
163
  cache.set(key, entry);
103
164
  }
@@ -123,9 +184,10 @@ function query(key, fetcher, options = {}) {
123
184
  const [data, setData] = signal(initialData);
124
185
  const [isFetching, setIsFetching] = signal(false);
125
186
  const [error, setError] = signal(void 0);
126
- let abortController = null;
127
187
  let disposed = false;
128
188
  let currentKey = null;
189
+ let attachedEntry = null;
190
+ let attachedKey = null;
129
191
  let intervalTimer = null;
130
192
  const loading = derived(() => isFetching() && data() === void 0);
131
193
  const isStale = derived(() => {
@@ -135,82 +197,128 @@ function query(key, fetcher, options = {}) {
135
197
  if (!entry || entry.dataUpdatedAt === 0) return true;
136
198
  return Date.now() - entry.dataUpdatedAt >= staleTime;
137
199
  });
200
+ function detachFromEntry() {
201
+ const entry = attachedEntry;
202
+ const key2 = attachedKey;
203
+ if (!entry) return;
204
+ attachedEntry = null;
205
+ attachedKey = null;
206
+ entry.listeners.delete(onCacheUpdate);
207
+ entry.refetchers.delete(doFetch);
208
+ entry.subscribers = Math.max(0, entry.subscribers - 1);
209
+ if (entry.subscribers > 0 || cacheTime < 0 || key2 === null) return;
210
+ if (cache.get(key2) !== entry) return;
211
+ if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
212
+ entry.gcTimer = setTimeout(() => {
213
+ const current = cache.get(key2);
214
+ if (current === entry && current.subscribers <= 0) {
215
+ abandonEntry(current);
216
+ cache.delete(key2);
217
+ }
218
+ }, cacheTime);
219
+ entry.gcTimer.unref?.();
220
+ }
221
+ function attachToEntry(entry, key2) {
222
+ if (attachedEntry === entry) {
223
+ if (entry.gcTimer !== null) {
224
+ clearTimeout(entry.gcTimer);
225
+ entry.gcTimer = null;
226
+ }
227
+ return;
228
+ }
229
+ detachFromEntry();
230
+ attachedEntry = entry;
231
+ attachedKey = key2;
232
+ entry.subscribers++;
233
+ entry.listeners.add(onCacheUpdate);
234
+ entry.refetchers.add(doFetch);
235
+ if (entry.gcTimer !== null) {
236
+ clearTimeout(entry.gcTimer);
237
+ entry.gcTimer = null;
238
+ }
239
+ }
138
240
  async function doFetch() {
139
241
  if (disposed || !currentKey || !enabled) return;
140
242
  const key2 = currentKey;
141
- let entry = cache.get(key2);
142
- if (!entry) {
143
- entry = getOrCreateEntry(cache, key2);
144
- entry.listeners.add(onCacheUpdate);
145
- entry.refetchers.add(doFetch);
146
- }
243
+ const entry = getOrCreateEntry(cache, key2);
244
+ attachToEntry(entry, key2);
147
245
  if (entry.promise) {
148
246
  setIsFetching(true);
149
247
  const captured = entry.promise;
150
248
  try {
151
249
  await captured;
152
- if (disposed || currentKey !== key2) return;
153
- if (entry.promise === captured) {
154
- onCacheUpdate();
155
- if (entry.error) onError?.(entry.error);
156
- else if (entry.data !== void 0) onSuccess?.(entry.data);
157
- }
158
250
  } catch {
159
- if (disposed || currentKey !== key2) return;
160
- if (entry.promise === captured) {
251
+ } finally {
252
+ if (!disposed && currentKey === key2) {
161
253
  onCacheUpdate();
162
- if (entry.error) onError?.(entry.error);
254
+ if (entry.error) {
255
+ const settledError = entry.error;
256
+ runCallback("query onError", () => onError?.(settledError));
257
+ } else if (entry.data !== void 0) {
258
+ const settledData = entry.data;
259
+ runCallback("query onSuccess", () => onSuccess?.(settledData));
260
+ }
261
+ runCallback("query onSettled", () => onSettled?.());
163
262
  }
164
- } finally {
165
- if (!disposed && currentKey === key2) onSettled?.();
166
263
  }
167
264
  return;
168
265
  }
169
- abortController?.abort();
170
- abortController = new AbortController();
171
- const signal2 = abortController.signal;
266
+ entry.controller = new AbortController();
267
+ const signal2 = entry.controller.signal;
268
+ const generation = ++entry.generation;
172
269
  setIsFetching(true);
173
270
  let promise;
174
271
  try {
175
272
  promise = withRetry(() => fetcher({ signal: signal2, key: key2 }), retryOptions, void 0, signal2);
176
273
  } catch (err) {
177
274
  setIsFetching(false);
275
+ if (isAbortError(err)) return;
178
276
  const errorObj = err instanceof Error ? err : new Error(String(err));
179
277
  entry.error = errorObj;
180
- onError?.(errorObj);
181
- onSettled?.();
278
+ runCallback("query onError", () => onError?.(errorObj));
279
+ runCallback("query onSettled", () => onSettled?.());
182
280
  return;
183
281
  }
184
282
  entry.promise = promise;
185
283
  try {
186
284
  const result = await promise;
285
+ if (entry.generation !== generation) return;
187
286
  entry.promise = null;
188
- if (disposed || currentKey !== key2) return;
287
+ entry.controller = null;
189
288
  entry.data = result;
190
289
  entry.dataUpdatedAt = Date.now();
191
290
  entry.error = void 0;
192
- const selected = select ? select(result) : result;
291
+ notifyListeners(entry.listeners, "query cache listener");
292
+ if (disposed || currentKey !== key2) return;
293
+ const selected = runSelect("query select", () => select ? select(result) : result);
193
294
  batch(() => {
194
- setData(selected);
295
+ if (selected.ok) setData(selected.value);
195
296
  setIsFetching(false);
196
297
  setError(void 0);
197
298
  });
198
- for (const listener of entry.listeners) listener();
199
- onSuccess?.(result);
299
+ runCallback("query onSuccess", () => onSuccess?.(result));
200
300
  } catch (err) {
301
+ if (entry.generation !== generation) return;
201
302
  entry.promise = null;
202
- if (disposed || currentKey !== key2) return;
203
- if (err instanceof DOMException && err.name === "AbortError") return;
303
+ entry.controller = null;
304
+ if (isAbortError(err)) {
305
+ notifyListeners(entry.listeners, "query cache listener");
306
+ if (!disposed && currentKey === key2) setIsFetching(false);
307
+ return;
308
+ }
204
309
  const errorObj = err instanceof Error ? err : new Error(String(err));
205
310
  entry.error = errorObj;
311
+ notifyListeners(entry.listeners, "query cache listener");
312
+ if (disposed || currentKey !== key2) return;
206
313
  batch(() => {
207
314
  setError(errorObj);
208
315
  setIsFetching(false);
209
316
  });
210
- for (const listener of entry.listeners) listener();
211
- onError?.(errorObj);
317
+ runCallback("query onError", () => onError?.(errorObj));
212
318
  } finally {
213
- if (!disposed && currentKey === key2) onSettled?.();
319
+ if (!disposed && currentKey === key2 && entry.generation === generation) {
320
+ runCallback("query onSettled", () => onSettled?.());
321
+ }
214
322
  }
215
323
  }
216
324
  function onCacheUpdate() {
@@ -225,43 +333,24 @@ function query(key, fetcher, options = {}) {
225
333
  return;
226
334
  }
227
335
  const raw = entry.data;
228
- const selected = raw !== void 0 && select ? select(raw) : raw;
336
+ const selected = raw !== void 0 && select ? runSelect("query select", () => select(raw)) : { ok: true, value: raw };
229
337
  batch(() => {
230
- setData(selected);
338
+ if (selected.ok) setData(selected.value);
231
339
  setError(entry.error);
232
340
  if (!entry.promise) setIsFetching(false);
233
341
  });
234
342
  }
235
343
  const effectCleanup = effect(() => {
236
344
  const key2 = resolveKey();
237
- if (currentKey !== null && currentKey !== key2) {
238
- const oldEntry = cache.get(currentKey);
239
- if (oldEntry) {
240
- oldEntry.listeners.delete(onCacheUpdate);
241
- oldEntry.refetchers.delete(doFetch);
242
- oldEntry.subscribers--;
243
- if (oldEntry.subscribers <= 0 && cacheTime >= 0) {
244
- const oldKey = currentKey;
245
- if (oldEntry.gcTimer !== null) clearTimeout(oldEntry.gcTimer);
246
- oldEntry.gcTimer = setTimeout(() => cache.delete(oldKey), cacheTime);
247
- }
248
- }
249
- }
250
345
  const keyChanged = currentKey !== key2;
251
346
  currentKey = key2;
252
347
  const entry = getOrCreateEntry(cache, key2, initialData);
253
- if (keyChanged) entry.subscribers++;
254
- if (entry.gcTimer !== null) {
255
- clearTimeout(entry.gcTimer);
256
- entry.gcTimer = null;
257
- }
258
- entry.listeners.add(onCacheUpdate);
259
- entry.refetchers.add(doFetch);
348
+ attachToEntry(entry, key2);
260
349
  if (entry.data !== void 0) {
261
350
  const raw = entry.data;
262
- const selected = select ? select(raw) : raw;
351
+ const selected = runSelect("query select", () => select ? select(raw) : raw);
263
352
  batch(() => {
264
- setData(selected);
353
+ if (selected.ok) setData(selected.value);
265
354
  setError(entry.error);
266
355
  });
267
356
  }
@@ -299,22 +388,9 @@ function query(key, fetcher, options = {}) {
299
388
  function dispose() {
300
389
  if (disposed) return;
301
390
  disposed = true;
302
- abortController?.abort();
303
391
  effectCleanup();
304
392
  if (intervalTimer) clearInterval(intervalTimer);
305
- if (currentKey) {
306
- const entry = cache.get(currentKey);
307
- if (entry) {
308
- entry.listeners.delete(onCacheUpdate);
309
- entry.refetchers.delete(doFetch);
310
- entry.subscribers--;
311
- if (entry.subscribers <= 0 && cacheTime >= 0) {
312
- const key2 = currentKey;
313
- if (entry.gcTimer !== null) clearTimeout(entry.gcTimer);
314
- entry.gcTimer = setTimeout(() => cache.delete(key2), cacheTime);
315
- }
316
- }
317
- }
393
+ detachFromEntry();
318
394
  if (focusHandler && typeof globalThis.removeEventListener === "function") {
319
395
  globalThis.removeEventListener("focus", focusHandler);
320
396
  }
@@ -350,21 +426,21 @@ function setQueryData(key, data) {
350
426
  const newData = typeof data === "function" ? data(entry.data) : data;
351
427
  entry.data = newData;
352
428
  entry.dataUpdatedAt = Date.now();
353
- for (const listener of entry.listeners) listener();
429
+ notifyListeners(entry.listeners, "query cache listener");
354
430
  }
355
431
  function clearQueryCache() {
356
432
  const activeListeners = [];
357
433
  const activeRefetchers = [];
358
434
  const activeCache = getActiveQueryCache();
359
435
  for (const entry of activeCache.values()) {
360
- if (entry.gcTimer) clearTimeout(entry.gcTimer);
361
436
  if (entry.subscribers > 0) {
362
437
  for (const listener of entry.listeners) activeListeners.push(listener);
363
438
  for (const refetcher of entry.refetchers) activeRefetchers.push(refetcher);
364
439
  }
440
+ abandonEntry(entry);
365
441
  }
366
442
  activeCache.clear();
367
- for (const listener of activeListeners) listener();
443
+ notifyListeners(activeListeners, "query cache listener");
368
444
  for (const refetcher of activeRefetchers) {
369
445
  refetcher().catch((err) => {
370
446
  if (typeof console !== "undefined") {
@@ -376,7 +452,7 @@ function clearQueryCache() {
376
452
  function __resetQueryCache() {
377
453
  const activeCache = getActiveQueryCache();
378
454
  for (const entry of activeCache.values()) {
379
- if (entry.gcTimer) clearTimeout(entry.gcTimer);
455
+ abandonEntry(entry);
380
456
  }
381
457
  activeCache.clear();
382
458
  }
@@ -391,6 +467,8 @@ function mutation(mutationFn, options = {}) {
391
467
  const isIdle = derived(() => status() === "idle");
392
468
  let runId = 0;
393
469
  let abortController = null;
470
+ let terminalStatus = "idle";
471
+ let terminalError;
394
472
  async function execute(variables) {
395
473
  abortController?.abort();
396
474
  abortController = new AbortController();
@@ -408,25 +486,38 @@ function mutation(mutationFn, options = {}) {
408
486
  }
409
487
  const result = await withRetry(() => mutationFn(variables, signal2), options.retry, void 0, signal2);
410
488
  if (myRun !== runId) return result;
489
+ terminalStatus = "success";
490
+ terminalError = void 0;
411
491
  batch(() => {
412
492
  setData(result);
413
493
  setLoading(false);
414
494
  setStatus("success");
415
495
  });
416
- options.onSuccess?.(result, variables, context2);
417
- options.onSettled?.(result, void 0, variables, context2);
496
+ runCallback("mutation onSuccess", () => options.onSuccess?.(result, variables, context2));
497
+ runCallback("mutation onSettled", () => options.onSettled?.(result, void 0, variables, context2));
418
498
  return result;
419
499
  } catch (err) {
500
+ if (isAbortError(err)) {
501
+ if (myRun === runId) {
502
+ batch(() => {
503
+ setLoading(false);
504
+ setError(terminalError);
505
+ setStatus(terminalStatus);
506
+ });
507
+ }
508
+ throw err;
509
+ }
420
510
  const errorObj = err instanceof Error ? err : new Error(String(err));
421
- if (errorObj instanceof DOMException && errorObj.name === "AbortError") throw errorObj;
422
511
  if (myRun !== runId) throw errorObj;
512
+ terminalStatus = "error";
513
+ terminalError = errorObj;
423
514
  batch(() => {
424
515
  setError(errorObj);
425
516
  setLoading(false);
426
517
  setStatus("error");
427
518
  });
428
- options.onError?.(errorObj, variables, context2);
429
- options.onSettled?.(void 0, errorObj, variables, context2);
519
+ runCallback("mutation onError", () => options.onError?.(errorObj, variables, context2));
520
+ runCallback("mutation onSettled", () => options.onSettled?.(void 0, errorObj, variables, context2));
430
521
  throw errorObj;
431
522
  }
432
523
  }
@@ -434,6 +525,8 @@ function mutation(mutationFn, options = {}) {
434
525
  runId++;
435
526
  abortController?.abort();
436
527
  abortController = null;
528
+ terminalStatus = "idle";
529
+ terminalError = void 0;
437
530
  batch(() => {
438
531
  setData(void 0);
439
532
  setError(void 0);
@@ -449,7 +542,7 @@ function mutation(mutationFn, options = {}) {
449
542
  isIdle,
450
543
  mutate: (variables) => {
451
544
  execute(variables).catch((err) => {
452
- if (err instanceof DOMException && err.name === "AbortError") return;
545
+ if (isAbortError(err)) return;
453
546
  if (typeof console !== "undefined") {
454
547
  console.warn("[SibuJS mutation] mutate() failed; check `.error()` signal or onError option.", err);
455
548
  }
@@ -522,10 +615,17 @@ function infiniteQuery(key, fetcher, options) {
522
615
  setIsFetchingNext(false);
523
616
  setIsFetchingPrev(false);
524
617
  });
525
- onSuccess?.(newPages);
618
+ runCallback("infiniteQuery onSuccess", () => onSuccess?.(newPages));
526
619
  } catch (err) {
527
620
  if (disposed || myRun !== runId) return;
528
- if (err instanceof DOMException && err.name === "AbortError") return;
621
+ if (isAbortError(err)) {
622
+ batch(() => {
623
+ setIsFetching(false);
624
+ setIsFetchingNext(false);
625
+ setIsFetchingPrev(false);
626
+ });
627
+ return;
628
+ }
529
629
  const errorObj = err instanceof Error ? err : new Error(String(err));
530
630
  batch(() => {
531
631
  setError(errorObj);
@@ -533,7 +633,7 @@ function infiniteQuery(key, fetcher, options) {
533
633
  setIsFetchingNext(false);
534
634
  setIsFetchingPrev(false);
535
635
  });
536
- onError?.(errorObj);
636
+ runCallback("infiniteQuery onError", () => onError?.(errorObj));
537
637
  }
538
638
  })();
539
639
  inFlight = promise;
@@ -715,7 +815,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
715
815
  setLoading(true);
716
816
  setError(void 0);
717
817
  });
718
- options.onStart?.();
818
+ runCallback("resource onStart", () => options.onStart?.());
719
819
  try {
720
820
  const result = await withRetry(() => fetcher(sourceValue, { signal: signal2, prev }), options.retry, void 0, signal2);
721
821
  if (version !== fetchVersion || disposed) return;
@@ -724,10 +824,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
724
824
  setData(result);
725
825
  setLoading(false);
726
826
  });
727
- options.onSuccess?.(result);
827
+ runCallback("resource onSuccess", () => options.onSuccess?.(result));
728
828
  } catch (err) {
729
829
  if (version !== fetchVersion || disposed) return;
730
- if (err instanceof DOMException && err.name === "AbortError") {
830
+ if (isAbortError(err)) {
731
831
  if (version === fetchVersion) setLoading(false);
732
832
  return;
733
833
  }
@@ -736,10 +836,10 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
736
836
  setError(errorObj);
737
837
  setLoading(false);
738
838
  });
739
- options.onError?.(errorObj);
839
+ runCallback("resource onError", () => options.onError?.(errorObj));
740
840
  } finally {
741
841
  if (version === fetchVersion) {
742
- options.onSettled?.();
842
+ runCallback("resource onSettled", () => options.onSettled?.());
743
843
  }
744
844
  }
745
845
  }
@@ -927,13 +1027,14 @@ async function offlineStore(options) {
927
1027
  return data().filter(filter);
928
1028
  }
929
1029
  async function sync() {
930
- if (!adapter || isSyncing() || closed) return;
1030
+ const syncAdapter2 = adapter;
1031
+ if (!syncAdapter2 || isSyncing() || closed) return;
931
1032
  setIsSyncing(true);
932
1033
  try {
933
1034
  const snapshot = await idbGetAllWithKeys(db, "_changes");
934
1035
  if (closed) return;
935
1036
  if (snapshot.length > 0) {
936
- const result = await adapter.push(snapshot.map((e) => e.value));
1037
+ const result = await syncAdapter2.push(snapshot.map((e) => e.value));
937
1038
  if (closed) return;
938
1039
  if (result.ok) {
939
1040
  await idbDeleteKeys(
@@ -946,20 +1047,18 @@ async function offlineStore(options) {
946
1047
  console.warn(`[offlineStore] push rejected by adapter${result.error ? `: ${result.error}` : ""}`);
947
1048
  }
948
1049
  }
949
- const remoteItems = await adapter.pull(lastSynced());
1050
+ const remoteItems = await syncAdapter2.pull(lastSynced());
950
1051
  if (closed) return;
951
1052
  const pendingChanges = await idbGetAll(db, "_changes");
952
1053
  if (closed) return;
953
- const pendingKeys = /* @__PURE__ */ new Set();
1054
+ const pendingByKey = /* @__PURE__ */ new Map();
954
1055
  for (const c of pendingChanges) {
955
1056
  const k = c.item[keyPath];
956
- if (k != null) pendingKeys.add(k);
1057
+ if (k != null) pendingByKey.set(k, c);
957
1058
  }
958
- const safeRemote = remoteItems.filter((item) => {
959
- const k = item[keyPath];
960
- return k == null || !pendingKeys.has(k);
961
- });
962
- await idbPutMany(db, "items", safeRemote);
1059
+ const resolved = await resolveRemoteItems(syncAdapter2, remoteItems, pendingByKey);
1060
+ if (closed) return;
1061
+ await idbPutMany(db, "items", resolved);
963
1062
  if (closed) return;
964
1063
  const now = Date.now();
965
1064
  await idbPut(db, "_meta", now, "lastSynced");
@@ -972,6 +1071,42 @@ async function offlineStore(options) {
972
1071
  setIsSyncing(false);
973
1072
  }
974
1073
  }
1074
+ async function resolveRemoteItems(activeAdapter, remoteItems, pendingByKey) {
1075
+ const strategy = activeAdapter.conflictStrategy ?? "client-wins";
1076
+ const out = [];
1077
+ for (const remote of remoteItems) {
1078
+ const key = remote[keyPath];
1079
+ const pending = key == null ? void 0 : pendingByKey.get(key);
1080
+ if (!pending) {
1081
+ out.push(remote);
1082
+ continue;
1083
+ }
1084
+ if (strategy === "server-wins") {
1085
+ out.push(remote);
1086
+ continue;
1087
+ }
1088
+ if (strategy === "manual") {
1089
+ if (!activeAdapter.resolveConflict) {
1090
+ if (typeof console !== "undefined") {
1091
+ console.warn(
1092
+ '[offlineStore] conflictStrategy is "manual" but the adapter has no resolveConflict(); keeping the local record for this key (client-wins).'
1093
+ );
1094
+ }
1095
+ continue;
1096
+ }
1097
+ try {
1098
+ const local = pending.type === "delete" ? void 0 : await idbGet(db, "items", key);
1099
+ const choice = await activeAdapter.resolveConflict({ key, local, remote, pending });
1100
+ if (choice !== void 0) out.push(choice);
1101
+ } catch (err) {
1102
+ if (typeof console !== "undefined") {
1103
+ console.warn("[offlineStore] resolveConflict threw; keeping the local record", err);
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ return out;
1109
+ }
975
1110
  function attach(newAdapter) {
976
1111
  adapter = newAdapter;
977
1112
  }