@kontsedal/olas-core 0.0.1-rc.0 → 0.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 (40) hide show
  1. package/dist/index.cjs +2 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +13 -2
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +13 -2
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +2 -2
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{root-BImHnGj1.mjs → root-BCZDC5Fv.mjs} +442 -139
  10. package/dist/root-BCZDC5Fv.mjs.map +1 -0
  11. package/dist/{root-Bazp5_Ik.cjs → root-DXV1gVbQ.cjs} +447 -138
  12. package/dist/root-DXV1gVbQ.cjs.map +1 -0
  13. package/dist/testing.cjs +1 -1
  14. package/dist/testing.d.cts +1 -1
  15. package/dist/testing.d.mts +1 -1
  16. package/dist/testing.mjs +1 -1
  17. package/dist/{types-CAMgqCMz.d.mts → types-CffZ1QXt.d.cts} +82 -10
  18. package/dist/types-CffZ1QXt.d.cts.map +1 -0
  19. package/dist/{types-emq_lZd7.d.cts → types-DSlDowpE.d.mts} +82 -10
  20. package/dist/types-DSlDowpE.d.mts.map +1 -0
  21. package/package.json +28 -2
  22. package/src/controller/instance.ts +115 -15
  23. package/src/controller/root.ts +9 -1
  24. package/src/controller/types.ts +17 -7
  25. package/src/forms/field.ts +73 -8
  26. package/src/forms/form-types.ts +16 -0
  27. package/src/forms/form.ts +171 -21
  28. package/src/index.ts +5 -0
  29. package/src/query/client.ts +161 -6
  30. package/src/query/define.ts +14 -0
  31. package/src/query/entry.ts +64 -42
  32. package/src/query/infinite.ts +77 -55
  33. package/src/query/mutation.ts +11 -21
  34. package/src/query/plugin.ts +50 -0
  35. package/src/query/use.ts +80 -3
  36. package/src/utils.ts +24 -0
  37. package/dist/root-BImHnGj1.mjs.map +0 -1
  38. package/dist/root-Bazp5_Ik.cjs.map +0 -1
  39. package/dist/types-CAMgqCMz.d.mts.map +0 -1
  40. package/dist/types-emq_lZd7.d.cts.map +0 -1
@@ -222,6 +222,29 @@ function isAbortError(err) {
222
222
  if (typeof DOMException !== "undefined" && err instanceof DOMException) return err.name === "AbortError";
223
223
  return false;
224
224
  }
225
+ /**
226
+ * `setTimeout` wrapped in a promise that rejects with `AbortError` if the
227
+ * passed signal fires. Internal — used by the retry loops in `Entry`,
228
+ * `InfiniteEntry`, and `Mutation` so a slow backoff never blocks a supersede.
229
+ */
230
+ function abortableSleep(ms, signal) {
231
+ return new Promise((resolve, reject) => {
232
+ if (signal.aborted) {
233
+ reject(new DOMException("Aborted", "AbortError"));
234
+ return;
235
+ }
236
+ const timer = setTimeout(() => {
237
+ signal.removeEventListener("abort", onAbort);
238
+ resolve();
239
+ }, ms);
240
+ const onAbort = () => {
241
+ clearTimeout(timer);
242
+ signal.removeEventListener("abort", onAbort);
243
+ reject(new DOMException("Aborted", "AbortError"));
244
+ };
245
+ signal.addEventListener("abort", onAbort, { once: true });
246
+ });
247
+ }
225
248
  //#endregion
226
249
  //#region src/query/entry.ts
227
250
  /**
@@ -250,18 +273,37 @@ var Entry = class {
250
273
  nextSnapshotId = 0;
251
274
  disposed = false;
252
275
  events;
276
+ onSuccessData;
253
277
  fetchStartTime = 0;
278
+ /**
279
+ * Promises returned by `firstValue()` that haven't settled. Rejected on
280
+ * `dispose()` so awaiters (most notably `prefetch` and `subscription.firstValue`)
281
+ * don't hang when the controller tree is torn down mid-fetch.
282
+ */
283
+ pendingFirstValueRejects = [];
254
284
  constructor(options) {
255
285
  this.fetcherProvider = options.fetcher;
256
286
  this.staleTime = options.staleTime ?? 0;
257
287
  this.retry = options.retry ?? 0;
258
288
  this.retryDelay = options.retryDelay ?? 1e3;
259
289
  this.events = options.events ?? {};
290
+ this.onSuccessData = options.onSuccessData;
260
291
  this.data = signal$1(options.initialData);
261
292
  if (options.initialData !== void 0) {
262
293
  this.status = signal$1("success");
263
- this.scheduleStaleness();
264
- this.isStale.set(this.staleTime === 0);
294
+ if (this.staleTime === 0) this.isStale.set(true);
295
+ else {
296
+ const last = options.initialUpdatedAt;
297
+ const alreadyStale = last === void 0 || Date.now() - last >= this.staleTime;
298
+ this.isStale.set(alreadyStale);
299
+ if (!alreadyStale) {
300
+ const remaining = this.staleTime - (Date.now() - last);
301
+ this.staleTimer = setTimeout(() => {
302
+ this.staleTimer = null;
303
+ if (!this.disposed) this.isStale.set(true);
304
+ }, remaining);
305
+ }
306
+ }
265
307
  } else this.status = signal$1("idle");
266
308
  this.lastUpdatedAt = signal$1(options.initialUpdatedAt);
267
309
  }
@@ -294,7 +336,7 @@ var Entry = class {
294
336
  } catch (err) {
295
337
  if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
296
338
  if (!this.shouldRetry(attempt, err)) return this.applyFailure(err);
297
- await abortableSleep$2(this.computeDelay(attempt), abort.signal);
339
+ await abortableSleep(this.computeDelay(attempt), abort.signal);
298
340
  attempt += 1;
299
341
  }
300
342
  }
@@ -323,6 +365,7 @@ var Entry = class {
323
365
  try {
324
366
  this.events.onFetchSuccess?.(Date.now() - this.fetchStartTime);
325
367
  } catch {}
368
+ this.onSuccessData?.(result);
326
369
  return result;
327
370
  }
328
371
  applyFailure(err) {
@@ -401,26 +444,24 @@ var Entry = class {
401
444
  }
402
445
  };
403
446
  }
404
- finalizeSnapshot(snapshot) {
405
- const id = snapshotIds.get(snapshot);
406
- if (id === void 0) return;
407
- const record = this.snapshots.find((s) => s.live && s.id === id);
408
- if (!record) return;
409
- record.live = false;
410
- this.snapshots = this.snapshots.filter((s) => s !== record);
411
- if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
412
- }
413
447
  firstValue() {
448
+ if (this.disposed) return Promise.reject(new DOMException("Entry disposed", "AbortError"));
414
449
  if (this.status.peek() === "success") return Promise.resolve(this.data.peek());
415
450
  if (this.status.peek() === "error") return Promise.reject(this.error.peek());
416
451
  return new Promise((resolve, reject) => {
452
+ const tracked = (err) => {
453
+ this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
454
+ reject(err);
455
+ };
456
+ this.pendingFirstValueRejects.push(tracked);
417
457
  const unsub = this.status.subscribe((s) => {
418
458
  if (s === "success") {
419
459
  unsub();
460
+ this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
420
461
  resolve(this.data.peek());
421
462
  } else if (s === "error") {
422
463
  unsub();
423
- reject(this.error.peek());
464
+ tracked(this.error.peek());
424
465
  }
425
466
  });
426
467
  });
@@ -443,27 +484,14 @@ var Entry = class {
443
484
  }
444
485
  this.currentAbort?.abort();
445
486
  this.currentAbort = null;
487
+ if (this.pendingFirstValueRejects.length > 0) {
488
+ const disposed = new DOMException("Entry disposed", "AbortError");
489
+ const rejects = this.pendingFirstValueRejects;
490
+ this.pendingFirstValueRejects = [];
491
+ for (const fn of rejects) fn(disposed);
492
+ }
446
493
  }
447
494
  };
448
- const snapshotIds = /* @__PURE__ */ new WeakMap();
449
- function abortableSleep$2(ms, signal) {
450
- return new Promise((resolve, reject) => {
451
- if (signal.aborted) {
452
- reject(new DOMException("Aborted", "AbortError"));
453
- return;
454
- }
455
- const timer = setTimeout(() => {
456
- signal.removeEventListener("abort", onAbort);
457
- resolve();
458
- }, ms);
459
- const onAbort = () => {
460
- clearTimeout(timer);
461
- signal.removeEventListener("abort", onAbort);
462
- reject(new DOMException("Aborted", "AbortError"));
463
- };
464
- signal.addEventListener("abort", onAbort, { once: true });
465
- });
466
- }
467
495
  //#endregion
468
496
  //#region src/query/focus-online.ts
469
497
  const focusSubs = /* @__PURE__ */ new Set();
@@ -539,6 +567,8 @@ var InfiniteEntry = class {
539
567
  snapshots = [];
540
568
  nextSnapshotId = 0;
541
569
  disposed = false;
570
+ /** Mirrors `Entry.pendingFirstValueRejects` — see that field for context. */
571
+ pendingFirstValueRejects = [];
542
572
  fetcher;
543
573
  initialPageParam;
544
574
  getNextPageParam;
@@ -547,6 +577,13 @@ var InfiniteEntry = class {
547
577
  retry;
548
578
  retryDelay;
549
579
  itemsOf;
580
+ /**
581
+ * Mirrors `Entry.onSuccessData`. Fires from `applyFetchSuccess`-equivalent
582
+ * branches AFTER `pages.set(...)` settles. Used by `InfiniteClientEntry`
583
+ * to emit `SetDataEvent { kind: 'infinite', source: 'fetch' }` for
584
+ * `QueryClientPlugin`s (e.g. entity normalization).
585
+ */
586
+ onSuccessData;
550
587
  constructor(opts) {
551
588
  this.fetcher = opts.fetcher;
552
589
  this.initialPageParam = opts.initialPageParam;
@@ -556,6 +593,7 @@ var InfiniteEntry = class {
556
593
  this.staleTime = opts.staleTime ?? 0;
557
594
  this.retry = opts.retry ?? 0;
558
595
  this.retryDelay = opts.retryDelay ?? 1e3;
596
+ this.onSuccessData = opts.onSuccessData;
559
597
  this.pageParams = signal$1([]);
560
598
  this.data = computed$1(() => {
561
599
  const ps = this.pages.value;
@@ -607,6 +645,7 @@ var InfiniteEntry = class {
607
645
  this.isStale.set(this.staleTime === 0);
608
646
  });
609
647
  if (this.staleTime > 0) this.scheduleStaleness();
648
+ this.onSuccessData?.(this.pages.peek());
610
649
  }, "initial");
611
650
  }
612
651
  fetchNextPage() {
@@ -633,6 +672,7 @@ var InfiniteEntry = class {
633
672
  this.isFetching.set(false);
634
673
  this.lastUpdatedAt.set(Date.now());
635
674
  });
675
+ this.onSuccessData?.(this.pages.peek());
636
676
  }, "next").then(() => {});
637
677
  }
638
678
  fetchPreviousPage() {
@@ -660,36 +700,46 @@ var InfiniteEntry = class {
660
700
  this.isFetching.set(false);
661
701
  this.lastUpdatedAt.set(Date.now());
662
702
  });
703
+ this.onSuccessData?.(this.pages.peek());
663
704
  }, "prev").then(() => {});
664
705
  }
665
706
  async runFetch(myId, signal, pageParam, onSuccess, direction) {
666
707
  let attempt = 0;
667
- while (true) {
668
- if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
669
- try {
670
- const page = await this.fetcher({
671
- pageParam,
672
- signal
673
- });
708
+ let succeeded = false;
709
+ try {
710
+ while (true) {
674
711
  if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
675
- onSuccess(page, pageParam);
676
- return page;
677
- } catch (err) {
678
- if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
679
- if (!(typeof this.retry === "number" ? attempt < this.retry : this.retry(attempt, err))) {
680
- batch$1(() => {
681
- this.error.set(err);
682
- this.status.set("error");
683
- this.isLoading.set(false);
684
- this.isFetching.set(false);
685
- if (direction === "next") this.isFetchingNextPage.set(false);
686
- if (direction === "prev") this.isFetchingPreviousPage.set(false);
712
+ try {
713
+ const page = await this.fetcher({
714
+ pageParam,
715
+ signal
687
716
  });
688
- throw err;
717
+ if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
718
+ onSuccess(page, pageParam);
719
+ succeeded = true;
720
+ return page;
721
+ } catch (err) {
722
+ if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
723
+ if (!(typeof this.retry === "number" ? attempt < this.retry : this.retry(attempt, err))) {
724
+ batch$1(() => {
725
+ this.error.set(err);
726
+ this.status.set("error");
727
+ this.isLoading.set(false);
728
+ this.isFetching.set(false);
729
+ if (direction === "next") this.isFetchingNextPage.set(false);
730
+ if (direction === "prev") this.isFetchingPreviousPage.set(false);
731
+ });
732
+ throw err;
733
+ }
734
+ await abortableSleep(typeof this.retryDelay === "function" ? this.retryDelay(attempt) : this.retryDelay, signal);
735
+ attempt += 1;
689
736
  }
690
- await abortableSleep$1(typeof this.retryDelay === "function" ? this.retryDelay(attempt) : this.retryDelay, signal);
691
- attempt += 1;
692
737
  }
738
+ } finally {
739
+ if (!succeeded) batch$1(() => {
740
+ if (direction === "next") this.isFetchingNextPage.set(false);
741
+ if (direction === "prev") this.isFetchingPreviousPage.set(false);
742
+ });
693
743
  }
694
744
  }
695
745
  refetch() {
@@ -749,16 +799,23 @@ var InfiniteEntry = class {
749
799
  };
750
800
  }
751
801
  firstValue() {
802
+ if (this.disposed) return Promise.reject(new DOMException("Entry disposed", "AbortError"));
752
803
  if (this.status.peek() === "success") return Promise.resolve(this.pages.peek());
753
804
  if (this.status.peek() === "error") return Promise.reject(this.error.peek());
754
805
  return new Promise((resolve, reject) => {
806
+ const tracked = (err) => {
807
+ this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
808
+ reject(err);
809
+ };
810
+ this.pendingFirstValueRejects.push(tracked);
755
811
  const unsub = this.status.subscribe((s) => {
756
812
  if (s === "success") {
757
813
  unsub();
814
+ this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
758
815
  resolve(this.pages.peek());
759
816
  } else if (s === "error") {
760
817
  unsub();
761
- reject(this.error.peek());
818
+ tracked(this.error.peek());
762
819
  }
763
820
  });
764
821
  });
@@ -784,26 +841,14 @@ var InfiniteEntry = class {
784
841
  }
785
842
  this.currentAbort?.abort();
786
843
  this.currentAbort = null;
844
+ if (this.pendingFirstValueRejects.length > 0) {
845
+ const disposed = new DOMException("Entry disposed", "AbortError");
846
+ const rejects = this.pendingFirstValueRejects;
847
+ this.pendingFirstValueRejects = [];
848
+ for (const fn of rejects) fn(disposed);
849
+ }
787
850
  }
788
851
  };
789
- function abortableSleep$1(ms, signal) {
790
- return new Promise((resolve, reject) => {
791
- if (signal.aborted) {
792
- reject(new DOMException("Aborted", "AbortError"));
793
- return;
794
- }
795
- const timer = setTimeout(() => {
796
- signal.removeEventListener("abort", onAbort);
797
- resolve();
798
- }, ms);
799
- const onAbort = () => {
800
- clearTimeout(timer);
801
- signal.removeEventListener("abort", onAbort);
802
- reject(new DOMException("Aborted", "AbortError"));
803
- };
804
- signal.addEventListener("abort", onAbort, { once: true });
805
- });
806
- }
807
852
  //#endregion
808
853
  //#region src/query/keys.ts
809
854
  /**
@@ -870,7 +915,7 @@ var ClientEntry = class {
870
915
  refetchInterval;
871
916
  refetchOnWindowFocus;
872
917
  refetchOnReconnect;
873
- constructor(client, query, callArgs, keyArgs, spec, hydrated) {
918
+ constructor(client, query, callArgs, keyArgs, spec, hydrated, onFetchSuccess) {
874
919
  this.client = client;
875
920
  this.query = query;
876
921
  this.callArgs = callArgs;
@@ -893,7 +938,8 @@ var ClientEntry = class {
893
938
  retryDelay: spec.retryDelay,
894
939
  initialData: hydrated?.data,
895
940
  initialUpdatedAt: hydrated?.lastUpdatedAt,
896
- events: void 0
941
+ events: void 0,
942
+ onSuccessData: onFetchSuccess
897
943
  });
898
944
  }
899
945
  acquire() {
@@ -993,7 +1039,7 @@ var InfiniteClientEntry = class {
993
1039
  intervalTimer = null;
994
1040
  gcTime;
995
1041
  refetchInterval;
996
- constructor(client, query, callArgs, keyArgs, spec) {
1042
+ constructor(client, query, callArgs, keyArgs, spec, onFetchSuccess) {
997
1043
  this.client = client;
998
1044
  this.query = query;
999
1045
  this.callArgs = callArgs;
@@ -1014,7 +1060,8 @@ var InfiniteClientEntry = class {
1014
1060
  itemsOf: spec.itemsOf,
1015
1061
  staleTime: spec.staleTime,
1016
1062
  retry: spec.retry,
1017
- retryDelay: spec.retryDelay
1063
+ retryDelay: spec.retryDelay,
1064
+ onSuccessData: onFetchSuccess
1018
1065
  });
1019
1066
  }
1020
1067
  acquire() {
@@ -1130,6 +1177,9 @@ var QueryClient = class {
1130
1177
  applyRemoteInvalidate(queryId, keyArgs) {
1131
1178
  self.applyRemoteInvalidate(queryId, keyArgs);
1132
1179
  },
1180
+ setEntryData(queryId, keyArgs, updater) {
1181
+ self.setEntryData(queryId, keyArgs, updater);
1182
+ },
1133
1183
  subscribedKeys(queryId) {
1134
1184
  return self.subscribedKeysFor(queryId);
1135
1185
  }
@@ -1146,7 +1196,24 @@ var QueryClient = class {
1146
1196
  });
1147
1197
  }
1148
1198
  }
1149
- emitSetData(query, keyArgs, data, kind) {
1199
+ /**
1200
+ * Emit a `SetDataEvent` to every installed plugin. The `source` field
1201
+ * tells layered plugins where the write originated:
1202
+ * - `'set'`: explicit `client.setData`, including mutations and plugin-
1203
+ * initiated `setEntryData` calls (e.g. entity backpropagation).
1204
+ * - `'fetch'`: a query fetcher resolved successfully (`Entry.applySuccess`
1205
+ * reaches this through `onSuccessData`), or hydrated data was first
1206
+ * bound (a per-tab arrival of pre-fetched data; cross-tab skips
1207
+ * `'fetch'` so this stays a per-tab concern).
1208
+ * - `'remote'`: `applyRemoteSetData` — cross-tab / server-push. Mirrors
1209
+ * `isRemote === true`.
1210
+ *
1211
+ * Private — fetcher-success emission goes through the `onFetchSuccess`
1212
+ * closure that `bindEntry` builds and hands to each new `ClientEntry`.
1213
+ * Hydrated emission goes through this method directly from `bindEntry`.
1214
+ * Mutation / remote paths call it from within QueryClient methods.
1215
+ */
1216
+ emitSetData(query, keyArgs, data, kind, source) {
1150
1217
  if (this.plugins.length === 0) return;
1151
1218
  const queryId = query.__spec.queryId;
1152
1219
  if (queryId == null) return;
@@ -1155,7 +1222,8 @@ var QueryClient = class {
1155
1222
  keyArgs,
1156
1223
  data,
1157
1224
  kind,
1158
- isRemote: this.applyingRemote
1225
+ isRemote: this.applyingRemote,
1226
+ source
1159
1227
  };
1160
1228
  for (const plugin of this.plugins) if (plugin.onSetData) {
1161
1229
  const cb = plugin.onSetData;
@@ -1233,11 +1301,44 @@ var QueryClient = class {
1233
1301
  this.applyingRemote = true;
1234
1302
  try {
1235
1303
  entry.entry.setData(() => data);
1236
- this.emitSetData(internal, entry.keyArgs, data, "data");
1304
+ this.emitSetData(internal, entry.keyArgs, data, "data", "remote");
1237
1305
  } finally {
1238
1306
  this.applyingRemote = false;
1239
1307
  }
1240
1308
  }
1309
+ /**
1310
+ * Local-originated `setData` keyed by `queryId + keyArgs`. Plugin-facing
1311
+ * (exposed via `QueryClientPluginApi.setEntryData`); used by the
1312
+ * `@kontsedal/olas-entities` plugin to backpropagate entity patches into
1313
+ * every query holding the entity, without forcing the plugin to recover
1314
+ * the original `callArgs`.
1315
+ *
1316
+ * Drops silently in the same cases as `applyRemoteSetData` (unknown
1317
+ * queryId / infinite query / no local entry). Emits `SetDataEvent` with
1318
+ * `isRemote: false`, `source: 'set'` — cross-tab WILL rebroadcast.
1319
+ */
1320
+ setEntryData(queryId, keyArgs, updater) {
1321
+ const query = lookupRegisteredQuery(queryId);
1322
+ if (!query) return;
1323
+ const hash = stableHash(keyArgs);
1324
+ if (query.__olas === "query") {
1325
+ const internal = query;
1326
+ const map = this.maps.get(internal);
1327
+ if (!map) return;
1328
+ const entry = map.get(hash);
1329
+ if (!entry) return;
1330
+ entry.entry.setData(updater);
1331
+ this.emitSetData(internal, entry.keyArgs, entry.entry.data.peek(), "data", "set");
1332
+ return;
1333
+ }
1334
+ const internal = query;
1335
+ const map = this.infiniteMaps.get(internal);
1336
+ if (!map) return;
1337
+ const entry = map.get(hash);
1338
+ if (!entry) return;
1339
+ entry.entry.setData(updater);
1340
+ this.emitSetData(internal, entry.keyArgs, entry.entry.pages.peek(), "infinite", "set");
1341
+ }
1241
1342
  applyRemoteInvalidate(queryId, keyArgs) {
1242
1343
  const query = lookupRegisteredQuery(queryId);
1243
1344
  if (!query) return;
@@ -1251,6 +1352,7 @@ var QueryClient = class {
1251
1352
  this.applyingRemote = true;
1252
1353
  try {
1253
1354
  entry.entry.invalidate().catch((err) => {
1355
+ if (isAbortError(err)) return;
1254
1356
  dispatchError(this.onError, err, {
1255
1357
  kind: "cache",
1256
1358
  controllerPath: [],
@@ -1346,9 +1448,11 @@ var QueryClient = class {
1346
1448
  if (!entry) {
1347
1449
  const hydrated = this.hydratedData.get(hash);
1348
1450
  if (hydrated) this.hydratedData.delete(hash);
1349
- entry = new ClientEntry(this, internal, args, keyArgs, internal.__spec, hydrated);
1451
+ const onFetchSuccess = internal.__spec.queryId != null ? (data) => this.emitSetData(internal, keyArgs, data, "data", "fetch") : void 0;
1452
+ entry = new ClientEntry(this, internal, args, keyArgs, internal.__spec, hydrated, onFetchSuccess);
1350
1453
  map.set(hash, entry);
1351
1454
  entry.scheduleGcIfOrphan();
1455
+ if (hydrated !== void 0) this.emitSetData(internal, keyArgs, hydrated.data, "data", "fetch");
1352
1456
  }
1353
1457
  return entry;
1354
1458
  }
@@ -1371,6 +1475,7 @@ var QueryClient = class {
1371
1475
  const entry = map.get(hash);
1372
1476
  if (!entry) return;
1373
1477
  entry.entry.invalidate().catch((err) => {
1478
+ if (isAbortError(err)) return;
1374
1479
  dispatchError(this.onError, err, {
1375
1480
  kind: "cache",
1376
1481
  controllerPath: [],
@@ -1385,6 +1490,7 @@ var QueryClient = class {
1385
1490
  if (!map) return;
1386
1491
  for (const [hash, entry] of map) {
1387
1492
  entry.entry.invalidate().catch((err) => {
1493
+ if (isAbortError(err)) return;
1388
1494
  dispatchError(this.onError, err, {
1389
1495
  kind: "cache",
1390
1496
  controllerPath: [],
@@ -1397,7 +1503,7 @@ var QueryClient = class {
1397
1503
  setData(query, args, updater) {
1398
1504
  const entry = this.bindEntry(query, args);
1399
1505
  const snapshot = entry.entry.setData(updater);
1400
- this.emitSetData(entry.query, entry.keyArgs, entry.entry.data.peek(), "data");
1506
+ this.emitSetData(entry.query, entry.keyArgs, entry.entry.data.peek(), "data", "set");
1401
1507
  return snapshot;
1402
1508
  }
1403
1509
  bindInfiniteEntry(query, args) {
@@ -1413,7 +1519,8 @@ var QueryClient = class {
1413
1519
  const hash = stableHash(keyArgs);
1414
1520
  let entry = map.get(hash);
1415
1521
  if (!entry) {
1416
- entry = new InfiniteClientEntry(this, internal, args, keyArgs, internal.__spec);
1522
+ const onFetchSuccess = internal.__spec.queryId != null ? (pages) => this.emitSetData(internal, keyArgs, pages, "infinite", "fetch") : void 0;
1523
+ entry = new InfiniteClientEntry(this, internal, args, keyArgs, internal.__spec, onFetchSuccess);
1417
1524
  map.set(hash, entry);
1418
1525
  entry.scheduleGcIfOrphan();
1419
1526
  }
@@ -1438,6 +1545,7 @@ var QueryClient = class {
1438
1545
  const entry = map.get(hash);
1439
1546
  if (!entry) return;
1440
1547
  entry.entry.invalidate().catch((err) => {
1548
+ if (isAbortError(err)) return;
1441
1549
  dispatchError(this.onError, err, {
1442
1550
  kind: "cache",
1443
1551
  controllerPath: [],
@@ -1452,6 +1560,7 @@ var QueryClient = class {
1452
1560
  if (!map) return;
1453
1561
  for (const entry of map.values()) {
1454
1562
  entry.entry.invalidate().catch((err) => {
1563
+ if (isAbortError(err)) return;
1455
1564
  dispatchError(this.onError, err, {
1456
1565
  kind: "cache",
1457
1566
  controllerPath: [],
@@ -1464,7 +1573,7 @@ var QueryClient = class {
1464
1573
  setInfiniteData(query, args, updater) {
1465
1574
  const entry = this.bindInfiniteEntry(query, args);
1466
1575
  const snapshot = entry.entry.setData(updater);
1467
- this.emitSetData(entry.query, entry.keyArgs, entry.entry.pages.peek(), "infinite");
1576
+ this.emitSetData(entry.query, entry.keyArgs, entry.entry.pages.peek(), "infinite", "set");
1468
1577
  return snapshot;
1469
1578
  }
1470
1579
  prefetchInfinite(query, args) {
@@ -1587,9 +1696,11 @@ var FieldImpl = class {
1587
1696
  runId = 0;
1588
1697
  disposed = false;
1589
1698
  devtoolsOwner = null;
1590
- constructor(initial, validators = []) {
1699
+ onValidatorError = null;
1700
+ constructor(initial, validators = [], options) {
1591
1701
  this.initial = initial;
1592
1702
  this.validators = validators;
1703
+ this.onValidatorError = options?.onValidatorError ?? null;
1593
1704
  this.value$ = signal$1(initial);
1594
1705
  this.errors$ = signal$1([]);
1595
1706
  this.touched$ = signal$1(false);
@@ -1601,6 +1712,13 @@ var FieldImpl = class {
1601
1712
  this.runValidators();
1602
1713
  });
1603
1714
  }
1715
+ /**
1716
+ * Internal hook for `ctx.field` / `createForm` to route synchronous
1717
+ * validator throws through `root.onError`. See `ValidatorErrorReporter`.
1718
+ */
1719
+ bindValidatorErrorReporter(reporter) {
1720
+ this.onValidatorError = reporter;
1721
+ }
1604
1722
  get value() {
1605
1723
  return this.value$.value;
1606
1724
  }
@@ -1707,10 +1825,15 @@ var FieldImpl = class {
1707
1825
  const myId = ++this.runId;
1708
1826
  const syncErrors = [];
1709
1827
  const asyncPromises = [];
1710
- for (const validator of this.validators) {
1828
+ for (const validator of this.validators) try {
1711
1829
  const result = validator(value, abort.signal);
1712
1830
  if (result instanceof Promise) asyncPromises.push(result);
1713
1831
  else if (result != null) syncErrors.push(result);
1832
+ } catch (err) {
1833
+ try {
1834
+ this.onValidatorError?.(err);
1835
+ } catch {}
1836
+ syncErrors.push(err instanceof Error ? err.message : String(err));
1714
1837
  }
1715
1838
  if (syncErrors.length > 0) {
1716
1839
  batch$1(() => {
@@ -1758,8 +1881,18 @@ function bindFieldDevtoolsOwner(field, owner) {
1758
1881
  const impl = field;
1759
1882
  if (typeof impl.bindDevtoolsOwner === "function") impl.bindDevtoolsOwner(owner);
1760
1883
  }
1761
- function createField(initial, validators) {
1762
- return new FieldImpl(initial, validators);
1884
+ /**
1885
+ * Internal install a synchronous-validator-throw reporter on a `Field`
1886
+ * (matched structurally to keep the public `Field<T>` surface stable).
1887
+ * Called by `ctx.field` and `bindTreeToDevtools` so leaves inside a form/
1888
+ * field-array tree get the same reporting as a standalone field.
1889
+ */
1890
+ function bindFieldValidatorErrorReporter(field, reporter) {
1891
+ const impl = field;
1892
+ if (typeof impl.bindValidatorErrorReporter === "function") impl.bindValidatorErrorReporter(reporter);
1893
+ }
1894
+ function createField(initial, validators, options) {
1895
+ return new FieldImpl(initial, validators, options);
1763
1896
  }
1764
1897
  /**
1765
1898
  * Wrap an async validator with a debounce. The debounce timer resets on every
@@ -1806,17 +1939,40 @@ var FormImpl = class {
1806
1939
  validators;
1807
1940
  options;
1808
1941
  validatorDispose = null;
1942
+ initialDispose = null;
1809
1943
  currentValidatorRun = 0;
1810
1944
  currentValidatorAbort = null;
1811
1945
  disposed = false;
1812
- constructor(schema, options) {
1946
+ onValidatorError = null;
1947
+ /** Internal — wire a sync-throw reporter for the top-level validators. */
1948
+ bindValidatorErrorReporter(reporter) {
1949
+ this.onValidatorError = reporter;
1950
+ }
1951
+ constructor(schema, options, internalOptions) {
1813
1952
  this.fields = schema;
1814
1953
  this.options = options;
1815
1954
  this.validators = options?.validators ?? [];
1816
- if (options?.initial !== void 0) {
1817
- const ini = typeof options.initial === "function" ? options.initial() : options.initial;
1818
- if (ini !== void 0) this.applyPartial(ini, true);
1819
- }
1955
+ this.onValidatorError = internalOptions?.onValidatorError ?? null;
1956
+ if (options?.initial !== void 0) if (typeof options.initial === "function") {
1957
+ const initialFn = options.initial;
1958
+ const mode = options.resetOnInitialChange ?? "when-clean";
1959
+ let firstRun = true;
1960
+ this.initialDispose = effect$1(() => {
1961
+ const ini = initialFn();
1962
+ if (ini === void 0) return;
1963
+ untracked$1(() => {
1964
+ if (this.disposed) return;
1965
+ if (firstRun) {
1966
+ firstRun = false;
1967
+ this.applyPartial(ini, true);
1968
+ return;
1969
+ }
1970
+ if (mode === "never") return;
1971
+ if (mode === "when-clean" && this.isDirty.peek()) return;
1972
+ this.applyPartial(ini, true);
1973
+ });
1974
+ });
1975
+ } else this.applyPartial(options.initial, true);
1820
1976
  this.value = computed$1(() => this.computeValue());
1821
1977
  this.errors = computed$1(() => this.computeErrors());
1822
1978
  this.isDirty = computed$1(() => this.computeBool("isDirty"));
@@ -1873,6 +2029,7 @@ var FormImpl = class {
1873
2029
  for (const [k, val] of Object.entries(partial)) {
1874
2030
  const child = this.fields[k];
1875
2031
  if (!child) continue;
2032
+ if (val === void 0) continue;
1876
2033
  if (isForm(child)) if (asInitial) child.resetWithInitial(val);
1877
2034
  else child.set(val);
1878
2035
  else if (isFieldArray(child)) {
@@ -1915,6 +2072,7 @@ var FormImpl = class {
1915
2072
  for (const child of Object.values(this.fields)) if (isForm(child) || isFieldArray(child)) tasks.push(child.validate());
1916
2073
  else tasks.push(child.revalidate());
1917
2074
  await Promise.all(tasks);
2075
+ if (this.validators.length > 0) this.runTopLevelValidators();
1918
2076
  if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
1919
2077
  const unsub = this.topLevelValidating$.subscribe((v) => {
1920
2078
  if (!v) {
@@ -1929,6 +2087,7 @@ var FormImpl = class {
1929
2087
  if (this.disposed) return;
1930
2088
  this.disposed = true;
1931
2089
  this.validatorDispose?.();
2090
+ this.initialDispose?.();
1932
2091
  this.currentValidatorAbort?.abort();
1933
2092
  for (const child of Object.values(this.fields)) child.dispose?.();
1934
2093
  }
@@ -1941,10 +2100,15 @@ var FormImpl = class {
1941
2100
  const myId = ++this.currentValidatorRun;
1942
2101
  const syncErrors = [];
1943
2102
  const asyncPromises = [];
1944
- for (const v of this.validators) {
2103
+ for (const v of this.validators) try {
1945
2104
  const r = v(value, abort.signal);
1946
2105
  if (r instanceof Promise) asyncPromises.push(r);
1947
2106
  else if (r != null) syncErrors.push(r);
2107
+ } catch (err) {
2108
+ try {
2109
+ this.onValidatorError?.(err);
2110
+ } catch {}
2111
+ syncErrors.push(err instanceof Error ? err.message : String(err));
1948
2112
  }
1949
2113
  if (syncErrors.length > 0) {
1950
2114
  batch$1(() => {
@@ -2038,9 +2202,15 @@ var FieldArrayImpl = class {
2038
2202
  currentValidatorAbort = null;
2039
2203
  validatorDispose = null;
2040
2204
  disposed = false;
2041
- constructor(itemFactory, options) {
2205
+ onValidatorError = null;
2206
+ /** Internal — see `FormImpl.bindValidatorErrorReporter`. */
2207
+ bindValidatorErrorReporter(reporter) {
2208
+ this.onValidatorError = reporter;
2209
+ }
2210
+ constructor(itemFactory, options, internalOptions) {
2042
2211
  this.itemFactory = itemFactory;
2043
2212
  this.validators = options?.validators ?? [];
2213
+ this.onValidatorError = internalOptions?.onValidatorError ?? null;
2044
2214
  this.items$ = signal$1([]);
2045
2215
  if (options?.initial) {
2046
2216
  this.initialItems = options.initial;
@@ -2131,6 +2301,7 @@ var FieldArrayImpl = class {
2131
2301
  for (const item of this.items$.peek()) if (isForm(item)) tasks.push(item.validate());
2132
2302
  else tasks.push(item.revalidate());
2133
2303
  await Promise.all(tasks);
2304
+ if (this.validators.length > 0) this.runTopLevelValidators();
2134
2305
  if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
2135
2306
  const unsub = this.topLevelValidating$.subscribe((v) => {
2136
2307
  if (!v) {
@@ -2157,10 +2328,15 @@ var FieldArrayImpl = class {
2157
2328
  const myId = ++this.currentValidatorRun;
2158
2329
  const syncErrors = [];
2159
2330
  const asyncPromises = [];
2160
- for (const v of this.validators) {
2331
+ for (const v of this.validators) try {
2161
2332
  const r = v(value, abort.signal);
2162
2333
  if (r instanceof Promise) asyncPromises.push(r);
2163
2334
  else if (r != null) syncErrors.push(r);
2335
+ } catch (err) {
2336
+ try {
2337
+ this.onValidatorError?.(err);
2338
+ } catch {}
2339
+ syncErrors.push(err instanceof Error ? err.message : String(err));
2164
2340
  }
2165
2341
  if (syncErrors.length > 0) {
2166
2342
  batch$1(() => {
@@ -2191,11 +2367,11 @@ var FieldArrayImpl = class {
2191
2367
  });
2192
2368
  }
2193
2369
  };
2194
- function createForm(schema, options) {
2195
- return new FormImpl(schema, options);
2370
+ function createForm(schema, options, internalOptions) {
2371
+ return new FormImpl(schema, options, internalOptions);
2196
2372
  }
2197
- function createFieldArray(itemFactory, options) {
2198
- return new FieldArrayImpl(itemFactory, options);
2373
+ function createFieldArray(itemFactory, options, internalOptions) {
2374
+ return new FieldArrayImpl(itemFactory, options, internalOptions);
2199
2375
  }
2200
2376
  /**
2201
2377
  * Recursively wire every leaf `Field` in a form / field-array tree to a
@@ -2222,12 +2398,24 @@ function bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers
2222
2398
  }
2223
2399
  if (isFieldArray(node)) {
2224
2400
  const arr = node;
2401
+ let perPass = [];
2225
2402
  const stop = effect$1(() => {
2226
- arr.items.value.forEach((item, idx) => {
2227
- bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter, disposers);
2403
+ const items = arr.items.value;
2404
+ for (const d of perPass) try {
2405
+ d();
2406
+ } catch {}
2407
+ perPass = [];
2408
+ items.forEach((item, idx) => {
2409
+ bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter, perPass);
2228
2410
  });
2229
2411
  });
2230
2412
  disposers.push(stop);
2413
+ disposers.push(() => {
2414
+ for (const d of perPass) try {
2415
+ d();
2416
+ } catch {}
2417
+ perPass = [];
2418
+ });
2231
2419
  return;
2232
2420
  }
2233
2421
  bindFieldDevtoolsOwner(node, {
@@ -2236,6 +2424,26 @@ function bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers
2236
2424
  emitter
2237
2425
  });
2238
2426
  }
2427
+ /**
2428
+ * Walk a Form/FieldArray subtree and install `reporter` on every level —
2429
+ * leaf fields, nested forms' top-level validators, and field-arrays' top-level
2430
+ * validators. Called by `ctx.form` / `ctx.fieldArray` so synchronous validator
2431
+ * throws anywhere in the tree route through `root.onError`. See
2432
+ * `ValidatorErrorReporter` in `./field.ts`.
2433
+ */
2434
+ function bindTreeValidatorErrorReporter(node, reporter) {
2435
+ if (isForm(node)) {
2436
+ node.bindValidatorErrorReporter?.(reporter);
2437
+ for (const child of Object.values(node.fields)) bindTreeValidatorErrorReporter(child, reporter);
2438
+ return;
2439
+ }
2440
+ if (isFieldArray(node)) {
2441
+ node.bindValidatorErrorReporter?.(reporter);
2442
+ for (const item of node.items.value) bindTreeValidatorErrorReporter(item, reporter);
2443
+ return;
2444
+ }
2445
+ bindFieldValidatorErrorReporter(node, reporter);
2446
+ }
2239
2447
  //#endregion
2240
2448
  //#region src/query/local.ts
2241
2449
  var LocalCacheImpl = class {
@@ -2469,7 +2677,13 @@ var MutationImpl = class {
2469
2677
  reset() {
2470
2678
  if (this.disposed) return;
2471
2679
  for (const handle of this.inflight) handle.abort.abort();
2472
- this.serialQueue.length = 0;
2680
+ if (this.serialQueue.length > 0) {
2681
+ const aborted = new DOMException("Aborted", "AbortError");
2682
+ const queue = this.serialQueue;
2683
+ this.serialQueue = [];
2684
+ for (const queued of queue) queued.reject(aborted);
2685
+ }
2686
+ this.serialActive = false;
2473
2687
  batch$1(() => {
2474
2688
  this.data.set(void 0);
2475
2689
  this.error.set(void 0);
@@ -2517,24 +2731,6 @@ function raceAbort(promise, signal) {
2517
2731
  });
2518
2732
  });
2519
2733
  }
2520
- function abortableSleep(ms, signal) {
2521
- return new Promise((resolve, reject) => {
2522
- if (signal.aborted) {
2523
- reject(new DOMException("Aborted", "AbortError"));
2524
- return;
2525
- }
2526
- const timer = setTimeout(() => {
2527
- signal.removeEventListener("abort", onAbort);
2528
- resolve();
2529
- }, ms);
2530
- const onAbort = () => {
2531
- clearTimeout(timer);
2532
- signal.removeEventListener("abort", onAbort);
2533
- reject(new DOMException("Aborted", "AbortError"));
2534
- };
2535
- signal.addEventListener("abort", onAbort, { once: true });
2536
- });
2537
- }
2538
2734
  //#endregion
2539
2735
  //#region src/query/use.ts
2540
2736
  var SubscriptionImpl = class {
@@ -2605,7 +2801,9 @@ function createUse(client, query, keyOrOptions) {
2605
2801
  const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
2606
2802
  const sub = new SubscriptionImpl(keepPreviousData);
2607
2803
  let currentEntry = null;
2804
+ let suspended = false;
2608
2805
  const effectDispose = effect$1(() => {
2806
+ if (suspended) return;
2609
2807
  if (!(enabledFn ? enabledFn() : true)) {
2610
2808
  untracked$1(() => {
2611
2809
  if (currentEntry) {
@@ -2636,9 +2834,31 @@ function createUse(client, query, keyOrOptions) {
2636
2834
  }
2637
2835
  sub.detach();
2638
2836
  };
2837
+ const suspend = () => {
2838
+ if (suspended) return;
2839
+ suspended = true;
2840
+ if (currentEntry) {
2841
+ currentEntry.release();
2842
+ currentEntry = null;
2843
+ }
2844
+ };
2845
+ const resume = () => {
2846
+ if (!suspended) return;
2847
+ suspended = false;
2848
+ if (!(enabledFn ? enabledFn() : true)) return;
2849
+ const args = keyFn ? keyFn() : [];
2850
+ const entry = client.bindEntry(query, args);
2851
+ entry.acquire();
2852
+ currentEntry = entry;
2853
+ sub.attach(entry);
2854
+ const status = entry.entry.status.peek();
2855
+ if (status === "idle" || entry.entry.isStaleNow() || status === "error") entry.entry.startFetch().catch(() => {});
2856
+ };
2639
2857
  return {
2640
2858
  subscription: sub,
2641
- dispose
2859
+ dispose,
2860
+ suspend,
2861
+ resume
2642
2862
  };
2643
2863
  }
2644
2864
  var InfiniteSubscriptionImpl = class {
@@ -2738,7 +2958,9 @@ function createInfiniteUse(client, query, keyOrOptions) {
2738
2958
  const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
2739
2959
  const sub = new InfiniteSubscriptionImpl(keepPreviousData);
2740
2960
  let currentEntry = null;
2961
+ let suspended = false;
2741
2962
  const effectDispose = effect$1(() => {
2963
+ if (suspended) return;
2742
2964
  if (!(enabledFn ? enabledFn() : true)) {
2743
2965
  untracked$1(() => {
2744
2966
  if (currentEntry) {
@@ -2769,9 +2991,31 @@ function createInfiniteUse(client, query, keyOrOptions) {
2769
2991
  }
2770
2992
  sub.detach();
2771
2993
  };
2994
+ const suspend = () => {
2995
+ if (suspended) return;
2996
+ suspended = true;
2997
+ if (currentEntry) {
2998
+ currentEntry.release();
2999
+ currentEntry = null;
3000
+ }
3001
+ };
3002
+ const resume = () => {
3003
+ if (!suspended) return;
3004
+ suspended = false;
3005
+ if (!(enabledFn ? enabledFn() : true)) return;
3006
+ const args = keyFn ? keyFn() : [];
3007
+ const entry = client.bindInfiniteEntry(query, args);
3008
+ entry.acquire();
3009
+ currentEntry = entry;
3010
+ sub.attach(entry);
3011
+ const status = entry.entry.status.peek();
3012
+ if (status === "idle" || entry.entry.isStaleNow() || status === "error") entry.entry.startFetch().catch(() => {});
3013
+ };
2772
3014
  return {
2773
3015
  subscription: sub,
2774
- dispose
3016
+ dispose,
3017
+ suspend,
3018
+ resume
2775
3019
  };
2776
3020
  }
2777
3021
  //#endregion
@@ -2821,7 +3065,6 @@ var ControllerInstance = class ControllerInstance {
2821
3065
  }
2822
3066
  dispose() {
2823
3067
  if (this.state === "disposed") return;
2824
- this.state;
2825
3068
  this.state = "disposed";
2826
3069
  for (let i = this.entries.length - 1; i >= 0; i--) {
2827
3070
  const entry = this.entries[i];
@@ -2847,6 +3090,9 @@ var ControllerInstance = class ControllerInstance {
2847
3090
  case "cleanup":
2848
3091
  entry.dispose();
2849
3092
  break;
3093
+ case "subscription-cache":
3094
+ entry.dispose();
3095
+ break;
2850
3096
  case "child":
2851
3097
  entry.instance.dispose();
2852
3098
  break;
@@ -2872,6 +3118,9 @@ var ControllerInstance = class ControllerInstance {
2872
3118
  entry.dispose?.();
2873
3119
  entry.dispose = null;
2874
3120
  break;
3121
+ case "subscription-cache":
3122
+ entry.suspend();
3123
+ break;
2875
3124
  case "child":
2876
3125
  entry.instance.suspend();
2877
3126
  break;
@@ -2896,6 +3145,9 @@ var ControllerInstance = class ControllerInstance {
2896
3145
  case "effect":
2897
3146
  entry.dispose = effect$1(entry.factory);
2898
3147
  break;
3148
+ case "subscription-cache":
3149
+ entry.resume();
3150
+ break;
2899
3151
  case "child":
2900
3152
  entry.instance.resume();
2901
3153
  break;
@@ -2936,7 +3188,7 @@ var ControllerInstance = class ControllerInstance {
2936
3188
  }
2937
3189
  };
2938
3190
  entry.factory = wrapped;
2939
- entry.dispose = effect$1(wrapped);
3191
+ if (self.state !== "suspended") entry.dispose = effect$1(wrapped);
2940
3192
  self.entries.push(entry);
2941
3193
  },
2942
3194
  cache(fetcher, options) {
@@ -2949,19 +3201,23 @@ var ControllerInstance = class ControllerInstance {
2949
3201
  },
2950
3202
  use(query, keyOrOptions) {
2951
3203
  if (query.__olas === "infiniteQuery") {
2952
- const { subscription, dispose: d } = createInfiniteUse(self.rootShared.queryClient, query, keyOrOptions);
3204
+ const handle = createInfiniteUse(self.rootShared.queryClient, query, keyOrOptions);
2953
3205
  self.entries.push({
2954
- kind: "cleanup",
2955
- dispose: d
3206
+ kind: "subscription-cache",
3207
+ dispose: handle.dispose,
3208
+ suspend: handle.suspend,
3209
+ resume: handle.resume
2956
3210
  });
2957
- return subscription;
3211
+ return handle.subscription;
2958
3212
  }
2959
- const { subscription, dispose: d } = createUse(self.rootShared.queryClient, query, keyOrOptions);
3213
+ const handle = createUse(self.rootShared.queryClient, query, keyOrOptions);
2960
3214
  self.entries.push({
2961
- kind: "cleanup",
2962
- dispose: d
3215
+ kind: "subscription-cache",
3216
+ dispose: handle.dispose,
3217
+ suspend: handle.suspend,
3218
+ resume: handle.resume
2963
3219
  });
2964
- return subscription;
3220
+ return handle.subscription;
2965
3221
  },
2966
3222
  mutation(spec) {
2967
3223
  const m = createMutation(spec, self.rootShared.onError, self.path, self.rootShared.queryClient.mutationsInflight$, self.rootShared.devtools);
@@ -2980,7 +3236,12 @@ var ControllerInstance = class ControllerInstance {
2980
3236
  return e;
2981
3237
  },
2982
3238
  field(initial, validators) {
2983
- const f = createField(initial, validators);
3239
+ const f = createField(initial, validators, { onValidatorError: (err) => {
3240
+ dispatchError(self.rootShared.onError, err, {
3241
+ kind: "effect",
3242
+ controllerPath: self.path
3243
+ });
3244
+ } });
2984
3245
  self.entries.push({
2985
3246
  kind: "cleanup",
2986
3247
  dispose: () => f.dispose()
@@ -2993,7 +3254,13 @@ var ControllerInstance = class ControllerInstance {
2993
3254
  return f;
2994
3255
  },
2995
3256
  form(schema, options) {
2996
- const f = createForm(schema, options);
3257
+ const reporter = (err) => {
3258
+ dispatchError(self.rootShared.onError, err, {
3259
+ kind: "effect",
3260
+ controllerPath: self.path
3261
+ });
3262
+ };
3263
+ const f = createForm(schema, options, { onValidatorError: reporter });
2997
3264
  self.entries.push({
2998
3265
  kind: "cleanup",
2999
3266
  dispose: () => f.dispose()
@@ -3003,10 +3270,17 @@ var ControllerInstance = class ControllerInstance {
3003
3270
  kind: "cleanup",
3004
3271
  dispose: stop
3005
3272
  });
3273
+ bindTreeValidatorErrorReporter(f, reporter);
3006
3274
  return f;
3007
3275
  },
3008
3276
  fieldArray(itemFactory, options) {
3009
- const fa = createFieldArray(itemFactory, options);
3277
+ const reporter = (err) => {
3278
+ dispatchError(self.rootShared.onError, err, {
3279
+ kind: "effect",
3280
+ controllerPath: self.path
3281
+ });
3282
+ };
3283
+ const fa = createFieldArray(itemFactory, options, { onValidatorError: reporter });
3010
3284
  self.entries.push({
3011
3285
  kind: "cleanup",
3012
3286
  dispose: () => fa.dispose()
@@ -3016,6 +3290,7 @@ var ControllerInstance = class ControllerInstance {
3016
3290
  kind: "cleanup",
3017
3291
  dispose: stop
3018
3292
  });
3293
+ bindTreeValidatorErrorReporter(fa, reporter);
3019
3294
  return fa;
3020
3295
  },
3021
3296
  provide(scope, value) {
@@ -3095,6 +3370,28 @@ var ControllerInstance = class ControllerInstance {
3095
3370
  controllerPath: self.path
3096
3371
  });
3097
3372
  }
3373
+ },
3374
+ suspend: () => {
3375
+ if (disposed) return;
3376
+ try {
3377
+ childInstance.suspend();
3378
+ } catch (err) {
3379
+ dispatchError(self.rootShared.onError, err, {
3380
+ kind: "effect",
3381
+ controllerPath: self.path
3382
+ });
3383
+ }
3384
+ },
3385
+ resume: () => {
3386
+ if (disposed) return;
3387
+ try {
3388
+ childInstance.resume();
3389
+ } catch (err) {
3390
+ dispatchError(self.rootShared.onError, err, {
3391
+ kind: "effect",
3392
+ controllerPath: self.path
3393
+ });
3394
+ }
3098
3395
  }
3099
3396
  };
3100
3397
  },
@@ -3186,7 +3483,13 @@ function createRootWithProps(def, props, options) {
3186
3483
  onError: options.onError,
3187
3484
  queryClient
3188
3485
  }, "root", options.deps);
3189
- const api = instance.construct(getFactory(def), props);
3486
+ let api;
3487
+ try {
3488
+ api = instance.construct(getFactory(def), props);
3489
+ } catch (err) {
3490
+ queryClient.dispose();
3491
+ throw err;
3492
+ }
3190
3493
  if (typeof api !== "object" || api === null) return attachRootControls({ value: api }, instance, devtools, queryClient);
3191
3494
  return attachRootControls(api, instance, devtools, queryClient);
3192
3495
  }
@@ -3265,6 +3568,6 @@ function createRoot(def, options) {
3265
3568
  return createRootWithProps(def, void 0, options);
3266
3569
  }
3267
3570
  //#endregion
3268
- export { lookupRegisteredQuery as a, batch$1 as c, signal$1 as d, untracked$1 as f, createEmitter as i, computed$1 as l, createRootWithProps as n, registerQueryById as o, defineController as p, debouncedValidator as r, isAbortError as s, createRoot as t, effect$1 as u };
3571
+ export { lookupRegisteredQuery as a, isAbortError as c, effect$1 as d, signal$1 as f, createEmitter as i, batch$1 as l, defineController as m, createRootWithProps as n, registerQueryById as o, untracked$1 as p, debouncedValidator as r, stableHash as s, createRoot as t, computed$1 as u };
3269
3572
 
3270
- //# sourceMappingURL=root-BImHnGj1.mjs.map
3573
+ //# sourceMappingURL=root-BCZDC5Fv.mjs.map