sibujs 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +18 -5
  3. package/dist/browser.js +4 -4
  4. package/dist/build.cjs +1956 -1007
  5. package/dist/build.d.cts +133 -44
  6. package/dist/build.d.ts +133 -44
  7. package/dist/build.js +1368 -569
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +11 -11
  10. package/dist/cdn.full.global.js +10 -10
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-SXXVZMKZ.js → chunk-2BPG2XDA.js} +5 -5
  13. package/dist/{chunk-BAAG6ZTI.js → chunk-3QWBSL5R.js} +4 -4
  14. package/dist/{chunk-5MT6SJ3P.js → chunk-4AWA2PVD.js} +2 -2
  15. package/dist/{chunk-JSXPZCET.js → chunk-7LUJQAOJ.js} +1 -1
  16. package/dist/{chunk-3ISI6ACU.js → chunk-BC2SECJD.js} +1 -1
  17. package/dist/{chunk-DKGBBKOF.js → chunk-BSY63EM6.js} +2 -2
  18. package/dist/{chunk-7NCARGJW.js → chunk-DKXRACVN.js} +3 -3
  19. package/dist/{chunk-47M47FOM.js → chunk-FNJXNGYZ.js} +1 -1
  20. package/dist/{chunk-D33YTSX5.js → chunk-FQRUXCEE.js} +5 -4
  21. package/dist/{chunk-5Q4R7HCL.js → chunk-JWKYU5GV.js} +11 -2
  22. package/dist/{chunk-QE4TTDU3.js → chunk-RUSSKG6G.js} +12 -11
  23. package/dist/{chunk-UZQ6ALFS.js → chunk-SLM3IA34.js} +1 -1
  24. package/dist/{chunk-XRRZKZYX.js → chunk-UNWRJRKC.js} +1 -1
  25. package/dist/{chunk-VVWPJ543.js → chunk-UOL2ECCS.js} +40 -11
  26. package/dist/{chunk-ORMZXBKQ.js → chunk-VZSG24LS.js} +3 -3
  27. package/dist/{chunk-XC4MEKGA.js → chunk-WEQ3DMVL.js} +8 -2
  28. package/dist/{chunk-PK6FK2G2.js → chunk-WOLJZUFQ.js} +176 -27
  29. package/dist/{chunk-7GQCWFOE.js → chunk-XYV3EDB7.js} +143 -74
  30. package/dist/{chunk-AIF3Z2T7.js → chunk-XZR4PXRE.js} +3 -3
  31. package/dist/{chunk-HYXDKS4N.js → chunk-Z3OHK6QT.js} +4 -4
  32. package/dist/{chunk-OXUY2A6L.js → chunk-ZVL7TY4K.js} +2 -2
  33. package/dist/{contracts-DRIuclVT.d.ts → contracts-CLqzJnOV.d.ts} +28 -17
  34. package/dist/{contracts-DRIuclVT.d.cts → contracts-CTOJXu-x.d.cts} +28 -17
  35. package/dist/data.cjs +198 -39
  36. package/dist/data.d.cts +119 -3
  37. package/dist/data.d.ts +119 -3
  38. package/dist/data.js +5 -5
  39. package/dist/devtools.cjs +18 -5
  40. package/dist/devtools.js +4 -4
  41. package/dist/ecosystem.cjs +64 -23
  42. package/dist/ecosystem.d.cts +2 -1
  43. package/dist/ecosystem.d.ts +2 -1
  44. package/dist/ecosystem.js +7 -7
  45. package/dist/extras.cjs +236 -49
  46. package/dist/extras.d.cts +3 -2
  47. package/dist/extras.d.ts +3 -2
  48. package/dist/extras.js +20 -20
  49. package/dist/index.cjs +259 -108
  50. package/dist/index.d.cts +193 -168
  51. package/dist/index.d.ts +193 -168
  52. package/dist/index.js +10 -10
  53. package/dist/motion.cjs +12 -4
  54. package/dist/motion.js +3 -3
  55. package/dist/patterns.cjs +28 -15
  56. package/dist/patterns.d.cts +2 -1
  57. package/dist/patterns.d.ts +2 -1
  58. package/dist/patterns.js +5 -5
  59. package/dist/performance.cjs +18 -5
  60. package/dist/performance.js +4 -4
  61. package/dist/plugins.cjs +118 -34
  62. package/dist/plugins.d.cts +124 -11
  63. package/dist/plugins.d.ts +124 -11
  64. package/dist/plugins.js +68 -26
  65. package/dist/ssr.cjs +54 -13
  66. package/dist/ssr.js +7 -7
  67. package/dist/{tagFactory-DFstCLQV.d.cts → tagFactory-8qL9LCIx.d.cts} +58 -19
  68. package/dist/{tagFactory-DkaNVUNV.d.ts → tagFactory-BL2fymez.d.ts} +58 -19
  69. package/dist/testing.cjs +15 -2
  70. package/dist/testing.js +2 -2
  71. package/dist/types-CJFViL6Q.d.cts +26 -0
  72. package/dist/types-CJFViL6Q.d.ts +26 -0
  73. package/dist/ui.cjs +28 -15
  74. package/dist/ui.d.cts +2 -1
  75. package/dist/ui.d.ts +2 -1
  76. package/dist/ui.js +7 -7
  77. package/dist/widgets.cjs +22 -14
  78. package/dist/widgets.js +6 -6
  79. package/package.json +4 -2
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  drainNotificationQueue,
3
+ emitDevtools,
3
4
  notifySubscribers,
4
5
  queueSignalNotification,
5
6
  recordDependency
6
- } from "./chunk-5Q4R7HCL.js";
7
+ } from "./chunk-JWKYU5GV.js";
7
8
  import {
8
9
  DEV
9
10
  } from "./chunk-NYNYSPK7.js";
@@ -91,7 +92,7 @@ function signal(initial, options) {
91
92
  state.__v++;
92
93
  if (DEV) {
93
94
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
94
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
95
+ if (hook) emitDevtools(hook, "signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
95
96
  }
96
97
  if (!enqueueBatchedSignal(state)) {
97
98
  notifySubscribers(state);
@@ -105,7 +106,7 @@ function signal(initial, options) {
105
106
  state.value = newValue;
106
107
  state.__v++;
107
108
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
108
- if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
109
+ if (hook) emitDevtools(hook, "signal:update", { signal: state, name: debugName, oldValue: prev, newValue });
109
110
  if (!enqueueBatchedSignal(state)) {
110
111
  notifySubscribers(state);
111
112
  }
@@ -124,7 +125,7 @@ function signal(initial, options) {
124
125
  }
125
126
  if (DEV) {
126
127
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
127
- if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
128
+ if (hook) emitDevtools(hook, "signal:create", { signal: state, name: debugName, getter: get, initial });
128
129
  }
129
130
  return [get, set];
130
131
  }
@@ -498,7 +498,7 @@ function forEachSubscriber(signal, visit) {
498
498
  }
499
499
 
500
500
  // src/reactivity/track.ts
501
- var _runtimeVersion = true ? "4.6.0" : "dev";
501
+ var _runtimeVersion = true ? "4.7.0" : "dev";
502
502
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
503
503
  function resolveReactiveApi() {
504
504
  const g = globalThis;
@@ -554,6 +554,14 @@ var getSubscriberCount2 = API.getSubscriberCount;
554
554
  var getSubscriberDeps2 = API.getSubscriberDeps;
555
555
  var forEachSubscriber2 = API.forEachSubscriber;
556
556
 
557
+ // src/core/devtoolsHook.ts
558
+ function emitDevtools(hook, event, payload) {
559
+ try {
560
+ hook.emit(event, payload);
561
+ } catch {
562
+ }
563
+ }
564
+
557
565
  export {
558
566
  suspendTracking2 as suspendTracking,
559
567
  resumeTracking2 as resumeTracking,
@@ -570,5 +578,6 @@ export {
570
578
  notifySubscribers2 as notifySubscribers,
571
579
  getSubscriberCount2 as getSubscriberCount,
572
580
  getSubscriberDeps2 as getSubscriberDeps,
573
- forEachSubscriber2 as forEachSubscriber
581
+ forEachSubscriber2 as forEachSubscriber,
582
+ emitDevtools
574
583
  };
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  cleanup,
3
+ emitDevtools,
3
4
  isTrackingSuspended,
4
5
  recordDependency,
5
6
  retrack,
6
7
  track
7
- } from "./chunk-5Q4R7HCL.js";
8
+ } from "./chunk-JWKYU5GV.js";
8
9
  import {
9
10
  devAssert
10
11
  } from "./chunk-NYNYSPK7.js";
@@ -35,17 +36,17 @@ function derived(getter, options) {
35
36
  cs._d = false;
36
37
  cs._init = true;
37
38
  };
38
- track(() => {
39
- let threw = true;
40
- try {
39
+ try {
40
+ track(() => {
41
41
  cs._v = getter();
42
42
  cs._d = false;
43
43
  cs._init = true;
44
- threw = false;
45
- } finally {
46
- if (threw) cs._d = true;
47
- }
48
- }, markDirty);
44
+ }, markDirty);
45
+ } catch (err) {
46
+ disposed = true;
47
+ cleanup(markDirty);
48
+ throw err;
49
+ }
49
50
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
50
51
  const validate = () => {
51
52
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -64,7 +65,7 @@ function derived(getter, options) {
64
65
  if (disposed) cleanup(markDirty);
65
66
  }
66
67
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
67
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
68
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
68
69
  }
69
70
  };
70
71
  cs._validate = validate;
@@ -115,7 +116,7 @@ function derived(getter, options) {
115
116
  }
116
117
  }
117
118
  };
118
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
119
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
119
120
  return computedGetter;
120
121
  }
121
122
 
@@ -5,7 +5,7 @@ import {
5
5
  resumeTracking,
6
6
  suspendTracking,
7
7
  track
8
- } from "./chunk-5Q4R7HCL.js";
8
+ } from "./chunk-JWKYU5GV.js";
9
9
  import {
10
10
  devAssert
11
11
  } from "./chunk-NYNYSPK7.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5HZXGZ6T.js";
4
4
  import {
5
5
  signal
6
- } from "./chunk-D33YTSX5.js";
6
+ } from "./chunk-FQRUXCEE.js";
7
7
  import {
8
8
  registerDisposer,
9
9
  reportError
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bindAttribute,
3
3
  setSafeAttribute
4
- } from "./chunk-47M47FOM.js";
4
+ } from "./chunk-FNJXNGYZ.js";
5
5
  import {
6
6
  isEventHandlerAttr,
7
7
  sanitizeCSSValue,
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-2INLLLMZ.js";
10
10
  import {
11
11
  reactiveBinding
12
- } from "./chunk-5Q4R7HCL.js";
12
+ } from "./chunk-JWKYU5GV.js";
13
13
  import {
14
14
  DEV,
15
15
  devWarn,
@@ -219,8 +219,34 @@ function toKebab(prop) {
219
219
  }
220
220
  function applyStyle(el, style) {
221
221
  if (typeof style === "function") {
222
+ const getter = style;
223
+ let written = [];
224
+ let wroteString = false;
222
225
  const teardown = reactiveBinding(() => {
223
- el.setAttribute("style", sanitizeStyleAttribute(String(style()), { element: el }));
226
+ const value = getter();
227
+ if (value === null || typeof value !== "object") {
228
+ written = [];
229
+ wroteString = true;
230
+ el.setAttribute("style", sanitizeStyleAttribute(String(value), { element: el }));
231
+ return;
232
+ }
233
+ const decl = el.style;
234
+ if (wroteString) {
235
+ el.removeAttribute("style");
236
+ wroteString = false;
237
+ }
238
+ const next = [];
239
+ for (const prop in value) {
240
+ const val = value[prop];
241
+ if (val == null) continue;
242
+ const name = toKebab(prop);
243
+ next.push(name);
244
+ decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
245
+ }
246
+ for (let i = 0; i < written.length; i++) {
247
+ if (next.indexOf(written[i]) === -1) decl.removeProperty(written[i]);
248
+ }
249
+ written = next;
224
250
  }, el);
225
251
  registerDisposer(el, teardown);
226
252
  return;
@@ -246,7 +272,7 @@ function applyStyle(el, style) {
246
272
  }
247
273
  function resolveClassValue(cls) {
248
274
  if (typeof cls === "string") return cls;
249
- if (typeof cls === "function") return cls();
275
+ if (typeof cls === "function") return cls() || "";
250
276
  if (!cls) return "";
251
277
  let out = "";
252
278
  for (const name in cls) {
@@ -263,7 +289,9 @@ function applyClass(el, cls) {
263
289
  }
264
290
  if (typeof cls === "function") {
265
291
  const teardown = reactiveBinding(() => {
266
- el.setAttribute("class", cls());
292
+ const value = cls();
293
+ if (value == null || value === false) el.removeAttribute("class");
294
+ else el.setAttribute("class", value);
267
295
  }, el);
268
296
  registerDisposer(el, teardown);
269
297
  return;
@@ -345,7 +373,7 @@ function appendChildren(el, nodes) {
345
373
  }
346
374
  }
347
375
  }
348
- var tagFactory = (tag, ns) => {
376
+ function tagFactory(tag, ns) {
349
377
  const blocked = isBlockedTag(tag);
350
378
  return (first, second) => {
351
379
  if (blocked) {
@@ -394,7 +422,9 @@ var tagFactory = (tag, ns) => {
394
422
  const pClass = props.class;
395
423
  if (pClass != null) applyClass(el, pClass);
396
424
  const pId = props.id;
397
- if (pId != null) {
425
+ if (typeof pId === "function") {
426
+ registerDisposer(el, bindAttribute(el, "id", pId));
427
+ } else if (pId != null) {
398
428
  if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
399
429
  devWarn(
400
430
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
@@ -444,12 +474,10 @@ var tagFactory = (tag, ns) => {
444
474
  }
445
475
  }
446
476
  }
447
- if (props.onElement && typeof props.onElement === "function") {
448
- props.onElement(el);
449
- }
477
+ if (typeof props.onElement === "function") props.onElement(el);
450
478
  return el;
451
479
  };
452
- };
480
+ }
453
481
 
454
482
  export {
455
483
  captureFocusWithin,
@@ -457,5 +485,6 @@ export {
457
485
  bindChildNode,
458
486
  SVG_NS,
459
487
  resolveClassValue,
488
+ appendChildren,
460
489
  tagFactory
461
490
  };
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  div,
8
8
  span
9
- } from "./chunk-JSXPZCET.js";
9
+ } from "./chunk-7LUJQAOJ.js";
10
10
  import {
11
11
  STATIC_VALUE_RESOLVER,
12
12
  planHeadElementEntry,
@@ -20,11 +20,11 @@ import {
20
20
  } from "./chunk-7ZHH77QA.js";
21
21
  import {
22
22
  effect
23
- } from "./chunk-XC4MEKGA.js";
23
+ } from "./chunk-WEQ3DMVL.js";
24
24
  import {
25
25
  batch,
26
26
  signal
27
- } from "./chunk-D33YTSX5.js";
27
+ } from "./chunk-FQRUXCEE.js";
28
28
  import {
29
29
  registerDisposer,
30
30
  replaceChildrenSafely,
@@ -3,9 +3,10 @@ import {
3
3
  } from "./chunk-7XHATCIH.js";
4
4
  import {
5
5
  cleanup,
6
+ emitDevtools,
6
7
  retrack,
7
8
  untracked
8
- } from "./chunk-5Q4R7HCL.js";
9
+ } from "./chunk-JWKYU5GV.js";
9
10
  import {
10
11
  devAssert,
11
12
  reportError
@@ -142,12 +143,17 @@ function effect(effectFn, options) {
142
143
  try {
143
144
  retrack(ctx.bodyFn, ctx.subscriber);
144
145
  if (ctx.rerunPending) drainReruns(ctx);
146
+ } catch (err) {
147
+ ctx.disposed = true;
148
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
149
+ cleanup(ctx.subscriber);
150
+ throw err;
145
151
  } finally {
146
152
  ctx.running = false;
147
153
  ctx.rerunPending = false;
148
154
  }
149
155
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
150
- if (hook) hook.emit("effect:create", { effectFn });
156
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
151
157
  return () => disposeEffect(ctx);
152
158
  }
153
159
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  derived
3
- } from "./chunk-QE4TTDU3.js";
3
+ } from "./chunk-RUSSKG6G.js";
4
4
  import {
5
5
  sanitizeUrl
6
6
  } from "./chunk-2INLLLMZ.js";
@@ -9,14 +9,17 @@ import {
9
9
  } from "./chunk-7ZHH77QA.js";
10
10
  import {
11
11
  effect
12
- } from "./chunk-XC4MEKGA.js";
12
+ } from "./chunk-WEQ3DMVL.js";
13
13
  import {
14
14
  getRequestScopedCache
15
15
  } from "./chunk-7XHATCIH.js";
16
16
  import {
17
17
  batch,
18
18
  signal
19
- } from "./chunk-D33YTSX5.js";
19
+ } from "./chunk-FQRUXCEE.js";
20
+ import {
21
+ untracked
22
+ } from "./chunk-JWKYU5GV.js";
20
23
  import {
21
24
  reportError
22
25
  } from "./chunk-NYNYSPK7.js";
@@ -132,7 +135,83 @@ function waitForRetryDelay(delay, signal2) {
132
135
  });
133
136
  }
134
137
 
138
+ // src/data/structuralSharing.ts
139
+ function isPlainObject(value) {
140
+ if (value === null || typeof value !== "object") return false;
141
+ const proto = Object.getPrototypeOf(value);
142
+ return proto === Object.prototype || proto === null;
143
+ }
144
+ function isPlainArray(value) {
145
+ return Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype;
146
+ }
147
+ function reconcile(prev, next, depth, walk, newRoot) {
148
+ if (Object.is(prev, next)) return prev;
149
+ const arrays = isPlainArray(prev) && isPlainArray(next);
150
+ if (!arrays && !(isPlainObject(prev) && isPlainObject(next))) return next;
151
+ if (Object.getPrototypeOf(prev) !== Object.getPrototypeOf(next)) return next;
152
+ const nextObject = next;
153
+ const seenAt = walk.path.get(nextObject);
154
+ if (seenAt !== void 0) {
155
+ walk.cycleTo = Math.min(walk.cycleTo, seenAt);
156
+ return next;
157
+ }
158
+ const prevRecord = prev;
159
+ const nextRecord = next;
160
+ const nextKeys = arrays ? Array.from({ length: next.length }, (_, i) => i) : Object.keys(nextRecord);
161
+ const prevSize = arrays ? prev.length : Object.keys(prevRecord).length;
162
+ const copy = arrays ? new Array(nextKeys.length) : Object.create(Object.getPrototypeOf(next));
163
+ let allFromPrev = prevSize === nextKeys.length;
164
+ let allFromNext = true;
165
+ const outerCycleTo = walk.cycleTo;
166
+ walk.cycleTo = Number.POSITIVE_INFINITY;
167
+ walk.path.set(nextObject, depth);
168
+ for (const key of nextKeys) {
169
+ if (arrays && !Object.hasOwn(nextRecord, key)) {
170
+ if (Object.hasOwn(prevRecord, key)) allFromPrev = false;
171
+ continue;
172
+ }
173
+ const nextChild = nextRecord[key];
174
+ const hasPrev = Object.hasOwn(prevRecord, key);
175
+ const shared = hasPrev ? reconcile(prevRecord[key], nextChild, depth + 1, walk, false) : nextChild;
176
+ copy[key] = shared;
177
+ if (!hasPrev || !Object.is(shared, prevRecord[key])) allFromPrev = false;
178
+ if (!Object.is(shared, nextChild)) allFromNext = false;
179
+ }
180
+ walk.path.delete(nextObject);
181
+ const reached = walk.cycleTo;
182
+ walk.cycleTo = Math.min(outerCycleTo, reached < depth ? reached : Number.POSITIVE_INFINITY);
183
+ if (reached <= depth) return next;
184
+ if (allFromPrev && !newRoot) return prev;
185
+ if (allFromNext) return next;
186
+ return copy;
187
+ }
188
+ function applyStructuralSharing(option, prev, next, explicit = false) {
189
+ if (option === false || prev === void 0) return next;
190
+ const forceNew = explicit && !Object.is(prev, next);
191
+ if (typeof option === "function") {
192
+ const shared = option(prev, next);
193
+ return forceNew && Object.is(shared, prev) ? next : shared;
194
+ }
195
+ return reconcile(prev, next, 0, { path: /* @__PURE__ */ new Map(), cycleTo: Number.POSITIVE_INFINITY }, forceNew);
196
+ }
197
+
135
198
  // src/data/query.ts
199
+ function shareSafely(option, prev, next, explicit) {
200
+ const shared = untracked(
201
+ () => runSelect("query structuralSharing", () => applyStructuralSharing(option, prev, next, explicit))
202
+ );
203
+ return shared.ok ? shared.value : next;
204
+ }
205
+ function sharedView(entry) {
206
+ const raw = entry.data;
207
+ if (raw === void 0) return void 0;
208
+ if (entry.hasShared && Object.is(entry.sharedFrom, raw)) return entry.shared;
209
+ const value = shareSafely(true, entry.hasShared ? entry.shared : void 0, raw, entry.explicit);
210
+ entry.shared = value;
211
+ entry.sharedFrom = raw;
212
+ entry.hasShared = true;
213
+ return value;
214
+ }
136
215
  var globalQueryCache = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.query.cache.v1"), () => /* @__PURE__ */ new Map());
137
216
  function getActiveQueryCache() {
138
217
  return getRequestScopedCache("query") ?? globalQueryCache;
@@ -158,7 +237,11 @@ function getOrCreateEntry(cache, key, initialData) {
158
237
  listeners: /* @__PURE__ */ new Set(),
159
238
  refetchers: /* @__PURE__ */ new Set(),
160
239
  controller: null,
161
- generation: 0
240
+ generation: 0,
241
+ explicit: false,
242
+ shared: void 0,
243
+ sharedFrom: void 0,
244
+ hasShared: false
162
245
  };
163
246
  cache.set(key, entry);
164
247
  }
@@ -177,13 +260,24 @@ function query(key, fetcher, options = {}) {
177
260
  onSuccess,
178
261
  onError,
179
262
  onSettled,
180
- select
263
+ select,
264
+ structuralSharing = true
181
265
  } = options;
182
266
  const resolveKey = typeof key === "function" ? key : () => key;
183
267
  const cache = getActiveQueryCache();
184
268
  const [data, setData] = signal(initialData);
185
269
  const [isFetching, setIsFetching] = signal(false);
186
270
  const [error, setError] = signal(void 0);
271
+ const [updatedAt, setUpdatedAt] = signal(0);
272
+ const [base, setBase] = signal(void 0);
273
+ let baseEntry = null;
274
+ let baseValue;
275
+ let baseExplicit = false;
276
+ let customEntry = null;
277
+ let customFrom;
278
+ let customValue;
279
+ let selectedEntry = null;
280
+ let selectedValue;
187
281
  let disposed = false;
188
282
  let currentKey = null;
189
283
  let attachedEntry = null;
@@ -192,11 +286,57 @@ function query(key, fetcher, options = {}) {
192
286
  const loading = derived(() => isFetching() && data() === void 0);
193
287
  const isStale = derived(() => {
194
288
  data();
289
+ updatedAt();
195
290
  if (!currentKey) return true;
196
291
  const entry = cache.get(currentKey);
197
292
  if (!entry || entry.dataUpdatedAt === 0) return true;
198
293
  return Date.now() - entry.dataUpdatedAt >= staleTime;
199
294
  });
295
+ function viewOf(entry) {
296
+ const raw = entry.data;
297
+ if (raw === void 0) return void 0;
298
+ if (structuralSharing === false) return raw;
299
+ if (typeof structuralSharing !== "function") return sharedView(entry);
300
+ const sameEntry = customEntry === entry;
301
+ if (sameEntry && Object.is(customFrom, raw)) return customValue;
302
+ const value = shareSafely(structuralSharing, sameEntry ? customValue : void 0, raw, entry.explicit);
303
+ customEntry = entry;
304
+ customFrom = raw;
305
+ customValue = value;
306
+ return value;
307
+ }
308
+ function commitBase(entry, value) {
309
+ const changed = !Object.is(value, baseValue);
310
+ baseEntry = entry;
311
+ baseValue = value;
312
+ if (!select) {
313
+ setData(value);
314
+ return;
315
+ }
316
+ if (!changed) return;
317
+ baseExplicit = entry?.explicit === true;
318
+ setBase(value);
319
+ }
320
+ const stopSelect = select ? effect(() => {
321
+ const raw = base();
322
+ const entry = baseEntry;
323
+ const explicit = baseExplicit;
324
+ baseExplicit = false;
325
+ if (entry === null && selectedEntry === null) return;
326
+ if (raw === void 0) {
327
+ selectedEntry = entry;
328
+ selectedValue = void 0;
329
+ setData(void 0);
330
+ return;
331
+ }
332
+ const selected = runSelect("query select", () => select(raw));
333
+ if (!selected.ok) return;
334
+ const prev = selectedEntry === entry ? selectedValue : void 0;
335
+ const value = shareSafely(structuralSharing, prev, selected.value, explicit);
336
+ selectedEntry = entry;
337
+ selectedValue = value;
338
+ setData(value);
339
+ }) : null;
200
340
  function detachFromEntry() {
201
341
  const entry = attachedEntry;
202
342
  const key2 = attachedKey;
@@ -255,7 +395,7 @@ function query(key, fetcher, options = {}) {
255
395
  const settledError = entry.error;
256
396
  runCallback("query onError", () => onError?.(settledError));
257
397
  } else if (entry.data !== void 0) {
258
- const settledData = entry.data;
398
+ const settledData = viewOf(entry);
259
399
  runCallback("query onSuccess", () => onSuccess?.(settledData));
260
400
  }
261
401
  runCallback("query onSettled", () => onSettled?.());
@@ -286,17 +426,14 @@ function query(key, fetcher, options = {}) {
286
426
  entry.promise = null;
287
427
  entry.controller = null;
288
428
  entry.data = result;
429
+ entry.explicit = false;
289
430
  entry.dataUpdatedAt = Date.now();
290
431
  entry.error = void 0;
291
432
  notifyListeners(entry.listeners, "query cache listener");
292
433
  if (disposed || currentKey !== key2) return;
293
- const selected = runSelect("query select", () => select ? select(result) : result);
294
- batch(() => {
295
- if (selected.ok) setData(selected.value);
296
- setIsFetching(false);
297
- setError(void 0);
298
- });
299
- runCallback("query onSuccess", () => onSuccess?.(result));
434
+ if (!entry.listeners.has(onCacheUpdate)) onCacheUpdate();
435
+ const committedResult = viewOf(entry);
436
+ runCallback("query onSuccess", () => onSuccess?.(committedResult));
300
437
  } catch (err) {
301
438
  if (entry.generation !== generation) return;
302
439
  entry.promise = null;
@@ -326,18 +463,19 @@ function query(key, fetcher, options = {}) {
326
463
  const entry = cache.get(currentKey);
327
464
  if (!entry) {
328
465
  batch(() => {
329
- setData(void 0);
466
+ commitBase(null, void 0);
330
467
  setError(void 0);
331
468
  setIsFetching(false);
469
+ setUpdatedAt(0);
332
470
  });
333
471
  return;
334
472
  }
335
- const raw = entry.data;
336
- const selected = raw !== void 0 && select ? runSelect("query select", () => select(raw)) : { ok: true, value: raw };
473
+ const view = viewOf(entry);
337
474
  batch(() => {
338
- if (selected.ok) setData(selected.value);
475
+ commitBase(entry, view);
339
476
  setError(entry.error);
340
477
  if (!entry.promise) setIsFetching(false);
478
+ setUpdatedAt(entry.dataUpdatedAt);
341
479
  });
342
480
  }
343
481
  const effectCleanup = effect(() => {
@@ -347,11 +485,11 @@ function query(key, fetcher, options = {}) {
347
485
  const entry = getOrCreateEntry(cache, key2, initialData);
348
486
  attachToEntry(entry, key2);
349
487
  if (entry.data !== void 0) {
350
- const raw = entry.data;
351
- const selected = runSelect("query select", () => select ? select(raw) : raw);
488
+ const view = viewOf(entry);
352
489
  batch(() => {
353
- if (selected.ok) setData(selected.value);
490
+ commitBase(entry, view);
354
491
  setError(entry.error);
492
+ setUpdatedAt(entry.dataUpdatedAt);
355
493
  });
356
494
  }
357
495
  if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
@@ -389,6 +527,7 @@ function query(key, fetcher, options = {}) {
389
527
  if (disposed) return;
390
528
  disposed = true;
391
529
  effectCleanup();
530
+ stopSelect?.();
392
531
  loading.dispose();
393
532
  isStale.dispose();
394
533
  if (intervalTimer) clearInterval(intervalTimer);
@@ -420,13 +559,15 @@ function invalidateQueries(keyOrPredicate) {
420
559
  }
421
560
  }
422
561
  function getQueryData(key) {
423
- return getActiveQueryCache().get(key)?.data;
562
+ const entry = getActiveQueryCache().get(key);
563
+ return entry ? sharedView(entry) : void 0;
424
564
  }
425
565
  function setQueryData(key, data) {
426
566
  const entry = getActiveQueryCache().get(key);
427
567
  if (!entry) return;
428
- const newData = typeof data === "function" ? data(entry.data) : data;
568
+ const newData = typeof data === "function" ? data(sharedView(entry)) : data;
429
569
  entry.data = newData;
570
+ entry.explicit = true;
430
571
  entry.dataUpdatedAt = Date.now();
431
572
  notifyListeners(entry.listeners, "query cache listener");
432
573
  }
@@ -810,6 +951,15 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
810
951
  const [loading, setLoading] = signal(false);
811
952
  const [error, setError] = signal(void 0);
812
953
  let currentData = options.initialValue;
954
+ function commitData(next, explicit) {
955
+ const sharing = options.structuralSharing ?? true;
956
+ const shared = untracked(
957
+ () => runSelect("resource structuralSharing", () => applyStructuralSharing(sharing, currentData, next, explicit))
958
+ );
959
+ currentData = shared.ok ? shared.value : next;
960
+ setData(currentData);
961
+ return currentData;
962
+ }
813
963
  let abortController = null;
814
964
  let disposed = false;
815
965
  let effectCleanup = null;
@@ -829,12 +979,12 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
829
979
  try {
830
980
  const result = await withRetry(() => fetcher(sourceValue, { signal: signal2, prev }), options.retry, void 0, signal2);
831
981
  if (version !== fetchVersion || disposed) return;
832
- currentData = result;
982
+ let committed = result;
833
983
  batch(() => {
834
- setData(result);
984
+ committed = commitData(result, false);
835
985
  setLoading(false);
836
986
  });
837
- runCallback("resource onSuccess", () => options.onSuccess?.(result));
987
+ runCallback("resource onSuccess", () => options.onSuccess?.(committed));
838
988
  } catch (err) {
839
989
  if (version !== fetchVersion || disposed) return;
840
990
  if (isAbortError(err)) {
@@ -868,8 +1018,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
868
1018
  refetch: () => doFetch(source ? source() : void 0),
869
1019
  mutate: (value) => {
870
1020
  const newValue = typeof value === "function" ? value(currentData) : value;
871
- currentData = newValue;
872
- setData(newValue);
1021
+ commitData(newValue, true);
873
1022
  },
874
1023
  abort: () => abortController?.abort(),
875
1024
  dispose: () => {