@kontsedal/olas-core 0.0.1-rc.1 → 0.0.2
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.
- package/dist/index.cjs +40 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +32 -11
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +40 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{root-BImHnGj1.mjs → root-De-6KWIZ.mjs} +750 -149
- package/dist/root-De-6KWIZ.mjs.map +1 -0
- package/dist/{root-Bazp5_Ik.cjs → root-XKEsSmcd.cjs} +755 -148
- package/dist/root-XKEsSmcd.cjs.map +1 -0
- package/dist/testing.cjs +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CAMgqCMz.d.mts → types-C-zV1JZA.d.mts} +215 -13
- package/dist/types-C-zV1JZA.d.mts.map +1 -0
- package/dist/{types-emq_lZd7.d.cts → types-DKfpkm17.d.cts} +215 -13
- package/dist/types-DKfpkm17.d.cts.map +1 -0
- package/package.json +1 -1
- package/src/controller/index.ts +6 -0
- package/src/controller/instance.ts +432 -18
- package/src/controller/root.ts +9 -1
- package/src/controller/types.ts +148 -7
- package/src/emitter.ts +34 -3
- package/src/forms/field.ts +73 -8
- package/src/forms/form-types.ts +16 -0
- package/src/forms/form.ts +218 -26
- package/src/index.ts +12 -1
- package/src/query/client.ts +161 -6
- package/src/query/define.ts +14 -0
- package/src/query/entry.ts +64 -42
- package/src/query/infinite.ts +77 -55
- package/src/query/mutation.ts +11 -21
- package/src/query/plugin.ts +50 -0
- package/src/query/use.ts +80 -3
- package/src/signals/readonly.ts +3 -3
- package/src/timing/debounced.ts +24 -4
- package/src/timing/throttled.ts +22 -3
- package/src/utils.ts +32 -4
- package/dist/root-BImHnGj1.mjs.map +0 -1
- package/dist/root-Bazp5_Ik.cjs.map +0 -1
- package/dist/types-CAMgqCMz.d.mts.map +0 -1
- package/dist/types-emq_lZd7.d.cts.map +0 -1
|
@@ -212,16 +212,41 @@ function untracked(fn) {
|
|
|
212
212
|
//#endregion
|
|
213
213
|
//#region src/utils.ts
|
|
214
214
|
/**
|
|
215
|
-
* True iff `err`
|
|
216
|
-
*
|
|
215
|
+
* True iff `err` looks like an AbortError. Matches the standard `DOMException`
|
|
216
|
+
* shape thrown by `AbortController` AND any object whose `name === 'AbortError'`
|
|
217
|
+
* — that covers axios / msw / user-thrown plain Errors that signal abort.
|
|
217
218
|
*
|
|
218
|
-
* Spec: §20.12
|
|
219
|
-
*
|
|
219
|
+
* Spec: §20.12. Node 17+ exposes a global DOMException, so the instanceof
|
|
220
|
+
* branch works server-side; the name-based branch is the portable fallback.
|
|
220
221
|
*/
|
|
221
222
|
function isAbortError(err) {
|
|
222
223
|
if (typeof DOMException !== "undefined" && err instanceof DOMException) return err.name === "AbortError";
|
|
224
|
+
if (err != null && typeof err === "object" && "name" in err) return err.name === "AbortError";
|
|
223
225
|
return false;
|
|
224
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* `setTimeout` wrapped in a promise that rejects with `AbortError` if the
|
|
229
|
+
* passed signal fires. Internal — used by the retry loops in `Entry`,
|
|
230
|
+
* `InfiniteEntry`, and `Mutation` so a slow backoff never blocks a supersede.
|
|
231
|
+
*/
|
|
232
|
+
function abortableSleep(ms, signal) {
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
if (signal.aborted) {
|
|
235
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const timer = setTimeout(() => {
|
|
239
|
+
signal.removeEventListener("abort", onAbort);
|
|
240
|
+
resolve();
|
|
241
|
+
}, ms);
|
|
242
|
+
const onAbort = () => {
|
|
243
|
+
clearTimeout(timer);
|
|
244
|
+
signal.removeEventListener("abort", onAbort);
|
|
245
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
246
|
+
};
|
|
247
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
248
|
+
});
|
|
249
|
+
}
|
|
225
250
|
//#endregion
|
|
226
251
|
//#region src/query/entry.ts
|
|
227
252
|
/**
|
|
@@ -250,18 +275,37 @@ var Entry = class {
|
|
|
250
275
|
nextSnapshotId = 0;
|
|
251
276
|
disposed = false;
|
|
252
277
|
events;
|
|
278
|
+
onSuccessData;
|
|
253
279
|
fetchStartTime = 0;
|
|
280
|
+
/**
|
|
281
|
+
* Promises returned by `firstValue()` that haven't settled. Rejected on
|
|
282
|
+
* `dispose()` so awaiters (most notably `prefetch` and `subscription.firstValue`)
|
|
283
|
+
* don't hang when the controller tree is torn down mid-fetch.
|
|
284
|
+
*/
|
|
285
|
+
pendingFirstValueRejects = [];
|
|
254
286
|
constructor(options) {
|
|
255
287
|
this.fetcherProvider = options.fetcher;
|
|
256
288
|
this.staleTime = options.staleTime ?? 0;
|
|
257
289
|
this.retry = options.retry ?? 0;
|
|
258
290
|
this.retryDelay = options.retryDelay ?? 1e3;
|
|
259
291
|
this.events = options.events ?? {};
|
|
292
|
+
this.onSuccessData = options.onSuccessData;
|
|
260
293
|
this.data = signal(options.initialData);
|
|
261
294
|
if (options.initialData !== void 0) {
|
|
262
295
|
this.status = signal("success");
|
|
263
|
-
this.
|
|
264
|
-
|
|
296
|
+
if (this.staleTime === 0) this.isStale.set(true);
|
|
297
|
+
else {
|
|
298
|
+
const last = options.initialUpdatedAt;
|
|
299
|
+
const alreadyStale = last === void 0 || Date.now() - last >= this.staleTime;
|
|
300
|
+
this.isStale.set(alreadyStale);
|
|
301
|
+
if (!alreadyStale) {
|
|
302
|
+
const remaining = this.staleTime - (Date.now() - last);
|
|
303
|
+
this.staleTimer = setTimeout(() => {
|
|
304
|
+
this.staleTimer = null;
|
|
305
|
+
if (!this.disposed) this.isStale.set(true);
|
|
306
|
+
}, remaining);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
265
309
|
} else this.status = signal("idle");
|
|
266
310
|
this.lastUpdatedAt = signal(options.initialUpdatedAt);
|
|
267
311
|
}
|
|
@@ -294,7 +338,7 @@ var Entry = class {
|
|
|
294
338
|
} catch (err) {
|
|
295
339
|
if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
|
|
296
340
|
if (!this.shouldRetry(attempt, err)) return this.applyFailure(err);
|
|
297
|
-
await abortableSleep
|
|
341
|
+
await abortableSleep(this.computeDelay(attempt), abort.signal);
|
|
298
342
|
attempt += 1;
|
|
299
343
|
}
|
|
300
344
|
}
|
|
@@ -323,6 +367,7 @@ var Entry = class {
|
|
|
323
367
|
try {
|
|
324
368
|
this.events.onFetchSuccess?.(Date.now() - this.fetchStartTime);
|
|
325
369
|
} catch {}
|
|
370
|
+
this.onSuccessData?.(result);
|
|
326
371
|
return result;
|
|
327
372
|
}
|
|
328
373
|
applyFailure(err) {
|
|
@@ -401,26 +446,24 @@ var Entry = class {
|
|
|
401
446
|
}
|
|
402
447
|
};
|
|
403
448
|
}
|
|
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
449
|
firstValue() {
|
|
450
|
+
if (this.disposed) return Promise.reject(new DOMException("Entry disposed", "AbortError"));
|
|
414
451
|
if (this.status.peek() === "success") return Promise.resolve(this.data.peek());
|
|
415
452
|
if (this.status.peek() === "error") return Promise.reject(this.error.peek());
|
|
416
453
|
return new Promise((resolve, reject) => {
|
|
454
|
+
const tracked = (err) => {
|
|
455
|
+
this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
|
|
456
|
+
reject(err);
|
|
457
|
+
};
|
|
458
|
+
this.pendingFirstValueRejects.push(tracked);
|
|
417
459
|
const unsub = this.status.subscribe((s) => {
|
|
418
460
|
if (s === "success") {
|
|
419
461
|
unsub();
|
|
462
|
+
this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
|
|
420
463
|
resolve(this.data.peek());
|
|
421
464
|
} else if (s === "error") {
|
|
422
465
|
unsub();
|
|
423
|
-
|
|
466
|
+
tracked(this.error.peek());
|
|
424
467
|
}
|
|
425
468
|
});
|
|
426
469
|
});
|
|
@@ -443,27 +486,14 @@ var Entry = class {
|
|
|
443
486
|
}
|
|
444
487
|
this.currentAbort?.abort();
|
|
445
488
|
this.currentAbort = null;
|
|
489
|
+
if (this.pendingFirstValueRejects.length > 0) {
|
|
490
|
+
const disposed = new DOMException("Entry disposed", "AbortError");
|
|
491
|
+
const rejects = this.pendingFirstValueRejects;
|
|
492
|
+
this.pendingFirstValueRejects = [];
|
|
493
|
+
for (const fn of rejects) fn(disposed);
|
|
494
|
+
}
|
|
446
495
|
}
|
|
447
496
|
};
|
|
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
497
|
//#endregion
|
|
468
498
|
//#region src/query/focus-online.ts
|
|
469
499
|
const focusSubs = /* @__PURE__ */ new Set();
|
|
@@ -539,6 +569,8 @@ var InfiniteEntry = class {
|
|
|
539
569
|
snapshots = [];
|
|
540
570
|
nextSnapshotId = 0;
|
|
541
571
|
disposed = false;
|
|
572
|
+
/** Mirrors `Entry.pendingFirstValueRejects` — see that field for context. */
|
|
573
|
+
pendingFirstValueRejects = [];
|
|
542
574
|
fetcher;
|
|
543
575
|
initialPageParam;
|
|
544
576
|
getNextPageParam;
|
|
@@ -547,6 +579,13 @@ var InfiniteEntry = class {
|
|
|
547
579
|
retry;
|
|
548
580
|
retryDelay;
|
|
549
581
|
itemsOf;
|
|
582
|
+
/**
|
|
583
|
+
* Mirrors `Entry.onSuccessData`. Fires from `applyFetchSuccess`-equivalent
|
|
584
|
+
* branches AFTER `pages.set(...)` settles. Used by `InfiniteClientEntry`
|
|
585
|
+
* to emit `SetDataEvent { kind: 'infinite', source: 'fetch' }` for
|
|
586
|
+
* `QueryClientPlugin`s (e.g. entity normalization).
|
|
587
|
+
*/
|
|
588
|
+
onSuccessData;
|
|
550
589
|
constructor(opts) {
|
|
551
590
|
this.fetcher = opts.fetcher;
|
|
552
591
|
this.initialPageParam = opts.initialPageParam;
|
|
@@ -556,6 +595,7 @@ var InfiniteEntry = class {
|
|
|
556
595
|
this.staleTime = opts.staleTime ?? 0;
|
|
557
596
|
this.retry = opts.retry ?? 0;
|
|
558
597
|
this.retryDelay = opts.retryDelay ?? 1e3;
|
|
598
|
+
this.onSuccessData = opts.onSuccessData;
|
|
559
599
|
this.pageParams = signal([]);
|
|
560
600
|
this.data = computed(() => {
|
|
561
601
|
const ps = this.pages.value;
|
|
@@ -607,6 +647,7 @@ var InfiniteEntry = class {
|
|
|
607
647
|
this.isStale.set(this.staleTime === 0);
|
|
608
648
|
});
|
|
609
649
|
if (this.staleTime > 0) this.scheduleStaleness();
|
|
650
|
+
this.onSuccessData?.(this.pages.peek());
|
|
610
651
|
}, "initial");
|
|
611
652
|
}
|
|
612
653
|
fetchNextPage() {
|
|
@@ -633,6 +674,7 @@ var InfiniteEntry = class {
|
|
|
633
674
|
this.isFetching.set(false);
|
|
634
675
|
this.lastUpdatedAt.set(Date.now());
|
|
635
676
|
});
|
|
677
|
+
this.onSuccessData?.(this.pages.peek());
|
|
636
678
|
}, "next").then(() => {});
|
|
637
679
|
}
|
|
638
680
|
fetchPreviousPage() {
|
|
@@ -660,36 +702,46 @@ var InfiniteEntry = class {
|
|
|
660
702
|
this.isFetching.set(false);
|
|
661
703
|
this.lastUpdatedAt.set(Date.now());
|
|
662
704
|
});
|
|
705
|
+
this.onSuccessData?.(this.pages.peek());
|
|
663
706
|
}, "prev").then(() => {});
|
|
664
707
|
}
|
|
665
708
|
async runFetch(myId, signal, pageParam, onSuccess, direction) {
|
|
666
709
|
let attempt = 0;
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const page = await this.fetcher({
|
|
671
|
-
pageParam,
|
|
672
|
-
signal
|
|
673
|
-
});
|
|
710
|
+
let succeeded = false;
|
|
711
|
+
try {
|
|
712
|
+
while (true) {
|
|
674
713
|
if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (!(typeof this.retry === "number" ? attempt < this.retry : this.retry(attempt, err))) {
|
|
680
|
-
batch(() => {
|
|
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);
|
|
714
|
+
try {
|
|
715
|
+
const page = await this.fetcher({
|
|
716
|
+
pageParam,
|
|
717
|
+
signal
|
|
687
718
|
});
|
|
688
|
-
throw
|
|
719
|
+
if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
|
|
720
|
+
onSuccess(page, pageParam);
|
|
721
|
+
succeeded = true;
|
|
722
|
+
return page;
|
|
723
|
+
} catch (err) {
|
|
724
|
+
if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
|
|
725
|
+
if (!(typeof this.retry === "number" ? attempt < this.retry : this.retry(attempt, err))) {
|
|
726
|
+
batch(() => {
|
|
727
|
+
this.error.set(err);
|
|
728
|
+
this.status.set("error");
|
|
729
|
+
this.isLoading.set(false);
|
|
730
|
+
this.isFetching.set(false);
|
|
731
|
+
if (direction === "next") this.isFetchingNextPage.set(false);
|
|
732
|
+
if (direction === "prev") this.isFetchingPreviousPage.set(false);
|
|
733
|
+
});
|
|
734
|
+
throw err;
|
|
735
|
+
}
|
|
736
|
+
await abortableSleep(typeof this.retryDelay === "function" ? this.retryDelay(attempt) : this.retryDelay, signal);
|
|
737
|
+
attempt += 1;
|
|
689
738
|
}
|
|
690
|
-
await abortableSleep$1(typeof this.retryDelay === "function" ? this.retryDelay(attempt) : this.retryDelay, signal);
|
|
691
|
-
attempt += 1;
|
|
692
739
|
}
|
|
740
|
+
} finally {
|
|
741
|
+
if (!succeeded) batch(() => {
|
|
742
|
+
if (direction === "next") this.isFetchingNextPage.set(false);
|
|
743
|
+
if (direction === "prev") this.isFetchingPreviousPage.set(false);
|
|
744
|
+
});
|
|
693
745
|
}
|
|
694
746
|
}
|
|
695
747
|
refetch() {
|
|
@@ -749,16 +801,23 @@ var InfiniteEntry = class {
|
|
|
749
801
|
};
|
|
750
802
|
}
|
|
751
803
|
firstValue() {
|
|
804
|
+
if (this.disposed) return Promise.reject(new DOMException("Entry disposed", "AbortError"));
|
|
752
805
|
if (this.status.peek() === "success") return Promise.resolve(this.pages.peek());
|
|
753
806
|
if (this.status.peek() === "error") return Promise.reject(this.error.peek());
|
|
754
807
|
return new Promise((resolve, reject) => {
|
|
808
|
+
const tracked = (err) => {
|
|
809
|
+
this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
|
|
810
|
+
reject(err);
|
|
811
|
+
};
|
|
812
|
+
this.pendingFirstValueRejects.push(tracked);
|
|
755
813
|
const unsub = this.status.subscribe((s) => {
|
|
756
814
|
if (s === "success") {
|
|
757
815
|
unsub();
|
|
816
|
+
this.pendingFirstValueRejects = this.pendingFirstValueRejects.filter((f) => f !== tracked);
|
|
758
817
|
resolve(this.pages.peek());
|
|
759
818
|
} else if (s === "error") {
|
|
760
819
|
unsub();
|
|
761
|
-
|
|
820
|
+
tracked(this.error.peek());
|
|
762
821
|
}
|
|
763
822
|
});
|
|
764
823
|
});
|
|
@@ -784,26 +843,14 @@ var InfiniteEntry = class {
|
|
|
784
843
|
}
|
|
785
844
|
this.currentAbort?.abort();
|
|
786
845
|
this.currentAbort = null;
|
|
846
|
+
if (this.pendingFirstValueRejects.length > 0) {
|
|
847
|
+
const disposed = new DOMException("Entry disposed", "AbortError");
|
|
848
|
+
const rejects = this.pendingFirstValueRejects;
|
|
849
|
+
this.pendingFirstValueRejects = [];
|
|
850
|
+
for (const fn of rejects) fn(disposed);
|
|
851
|
+
}
|
|
787
852
|
}
|
|
788
853
|
};
|
|
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
854
|
//#endregion
|
|
808
855
|
//#region src/query/keys.ts
|
|
809
856
|
/**
|
|
@@ -870,7 +917,7 @@ var ClientEntry = class {
|
|
|
870
917
|
refetchInterval;
|
|
871
918
|
refetchOnWindowFocus;
|
|
872
919
|
refetchOnReconnect;
|
|
873
|
-
constructor(client, query, callArgs, keyArgs, spec, hydrated) {
|
|
920
|
+
constructor(client, query, callArgs, keyArgs, spec, hydrated, onFetchSuccess) {
|
|
874
921
|
this.client = client;
|
|
875
922
|
this.query = query;
|
|
876
923
|
this.callArgs = callArgs;
|
|
@@ -893,7 +940,8 @@ var ClientEntry = class {
|
|
|
893
940
|
retryDelay: spec.retryDelay,
|
|
894
941
|
initialData: hydrated?.data,
|
|
895
942
|
initialUpdatedAt: hydrated?.lastUpdatedAt,
|
|
896
|
-
events: void 0
|
|
943
|
+
events: void 0,
|
|
944
|
+
onSuccessData: onFetchSuccess
|
|
897
945
|
});
|
|
898
946
|
}
|
|
899
947
|
acquire() {
|
|
@@ -993,7 +1041,7 @@ var InfiniteClientEntry = class {
|
|
|
993
1041
|
intervalTimer = null;
|
|
994
1042
|
gcTime;
|
|
995
1043
|
refetchInterval;
|
|
996
|
-
constructor(client, query, callArgs, keyArgs, spec) {
|
|
1044
|
+
constructor(client, query, callArgs, keyArgs, spec, onFetchSuccess) {
|
|
997
1045
|
this.client = client;
|
|
998
1046
|
this.query = query;
|
|
999
1047
|
this.callArgs = callArgs;
|
|
@@ -1014,7 +1062,8 @@ var InfiniteClientEntry = class {
|
|
|
1014
1062
|
itemsOf: spec.itemsOf,
|
|
1015
1063
|
staleTime: spec.staleTime,
|
|
1016
1064
|
retry: spec.retry,
|
|
1017
|
-
retryDelay: spec.retryDelay
|
|
1065
|
+
retryDelay: spec.retryDelay,
|
|
1066
|
+
onSuccessData: onFetchSuccess
|
|
1018
1067
|
});
|
|
1019
1068
|
}
|
|
1020
1069
|
acquire() {
|
|
@@ -1130,6 +1179,9 @@ var QueryClient = class {
|
|
|
1130
1179
|
applyRemoteInvalidate(queryId, keyArgs) {
|
|
1131
1180
|
self.applyRemoteInvalidate(queryId, keyArgs);
|
|
1132
1181
|
},
|
|
1182
|
+
setEntryData(queryId, keyArgs, updater) {
|
|
1183
|
+
self.setEntryData(queryId, keyArgs, updater);
|
|
1184
|
+
},
|
|
1133
1185
|
subscribedKeys(queryId) {
|
|
1134
1186
|
return self.subscribedKeysFor(queryId);
|
|
1135
1187
|
}
|
|
@@ -1146,7 +1198,24 @@ var QueryClient = class {
|
|
|
1146
1198
|
});
|
|
1147
1199
|
}
|
|
1148
1200
|
}
|
|
1149
|
-
|
|
1201
|
+
/**
|
|
1202
|
+
* Emit a `SetDataEvent` to every installed plugin. The `source` field
|
|
1203
|
+
* tells layered plugins where the write originated:
|
|
1204
|
+
* - `'set'`: explicit `client.setData`, including mutations and plugin-
|
|
1205
|
+
* initiated `setEntryData` calls (e.g. entity backpropagation).
|
|
1206
|
+
* - `'fetch'`: a query fetcher resolved successfully (`Entry.applySuccess`
|
|
1207
|
+
* reaches this through `onSuccessData`), or hydrated data was first
|
|
1208
|
+
* bound (a per-tab arrival of pre-fetched data; cross-tab skips
|
|
1209
|
+
* `'fetch'` so this stays a per-tab concern).
|
|
1210
|
+
* - `'remote'`: `applyRemoteSetData` — cross-tab / server-push. Mirrors
|
|
1211
|
+
* `isRemote === true`.
|
|
1212
|
+
*
|
|
1213
|
+
* Private — fetcher-success emission goes through the `onFetchSuccess`
|
|
1214
|
+
* closure that `bindEntry` builds and hands to each new `ClientEntry`.
|
|
1215
|
+
* Hydrated emission goes through this method directly from `bindEntry`.
|
|
1216
|
+
* Mutation / remote paths call it from within QueryClient methods.
|
|
1217
|
+
*/
|
|
1218
|
+
emitSetData(query, keyArgs, data, kind, source) {
|
|
1150
1219
|
if (this.plugins.length === 0) return;
|
|
1151
1220
|
const queryId = query.__spec.queryId;
|
|
1152
1221
|
if (queryId == null) return;
|
|
@@ -1155,7 +1224,8 @@ var QueryClient = class {
|
|
|
1155
1224
|
keyArgs,
|
|
1156
1225
|
data,
|
|
1157
1226
|
kind,
|
|
1158
|
-
isRemote: this.applyingRemote
|
|
1227
|
+
isRemote: this.applyingRemote,
|
|
1228
|
+
source
|
|
1159
1229
|
};
|
|
1160
1230
|
for (const plugin of this.plugins) if (plugin.onSetData) {
|
|
1161
1231
|
const cb = plugin.onSetData;
|
|
@@ -1233,11 +1303,44 @@ var QueryClient = class {
|
|
|
1233
1303
|
this.applyingRemote = true;
|
|
1234
1304
|
try {
|
|
1235
1305
|
entry.entry.setData(() => data);
|
|
1236
|
-
this.emitSetData(internal, entry.keyArgs, data, "data");
|
|
1306
|
+
this.emitSetData(internal, entry.keyArgs, data, "data", "remote");
|
|
1237
1307
|
} finally {
|
|
1238
1308
|
this.applyingRemote = false;
|
|
1239
1309
|
}
|
|
1240
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Local-originated `setData` keyed by `queryId + keyArgs`. Plugin-facing
|
|
1313
|
+
* (exposed via `QueryClientPluginApi.setEntryData`); used by the
|
|
1314
|
+
* `@kontsedal/olas-entities` plugin to backpropagate entity patches into
|
|
1315
|
+
* every query holding the entity, without forcing the plugin to recover
|
|
1316
|
+
* the original `callArgs`.
|
|
1317
|
+
*
|
|
1318
|
+
* Drops silently in the same cases as `applyRemoteSetData` (unknown
|
|
1319
|
+
* queryId / infinite query / no local entry). Emits `SetDataEvent` with
|
|
1320
|
+
* `isRemote: false`, `source: 'set'` — cross-tab WILL rebroadcast.
|
|
1321
|
+
*/
|
|
1322
|
+
setEntryData(queryId, keyArgs, updater) {
|
|
1323
|
+
const query = lookupRegisteredQuery(queryId);
|
|
1324
|
+
if (!query) return;
|
|
1325
|
+
const hash = stableHash(keyArgs);
|
|
1326
|
+
if (query.__olas === "query") {
|
|
1327
|
+
const internal = query;
|
|
1328
|
+
const map = this.maps.get(internal);
|
|
1329
|
+
if (!map) return;
|
|
1330
|
+
const entry = map.get(hash);
|
|
1331
|
+
if (!entry) return;
|
|
1332
|
+
entry.entry.setData(updater);
|
|
1333
|
+
this.emitSetData(internal, entry.keyArgs, entry.entry.data.peek(), "data", "set");
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
const internal = query;
|
|
1337
|
+
const map = this.infiniteMaps.get(internal);
|
|
1338
|
+
if (!map) return;
|
|
1339
|
+
const entry = map.get(hash);
|
|
1340
|
+
if (!entry) return;
|
|
1341
|
+
entry.entry.setData(updater);
|
|
1342
|
+
this.emitSetData(internal, entry.keyArgs, entry.entry.pages.peek(), "infinite", "set");
|
|
1343
|
+
}
|
|
1241
1344
|
applyRemoteInvalidate(queryId, keyArgs) {
|
|
1242
1345
|
const query = lookupRegisteredQuery(queryId);
|
|
1243
1346
|
if (!query) return;
|
|
@@ -1251,6 +1354,7 @@ var QueryClient = class {
|
|
|
1251
1354
|
this.applyingRemote = true;
|
|
1252
1355
|
try {
|
|
1253
1356
|
entry.entry.invalidate().catch((err) => {
|
|
1357
|
+
if (isAbortError(err)) return;
|
|
1254
1358
|
dispatchError(this.onError, err, {
|
|
1255
1359
|
kind: "cache",
|
|
1256
1360
|
controllerPath: [],
|
|
@@ -1346,9 +1450,11 @@ var QueryClient = class {
|
|
|
1346
1450
|
if (!entry) {
|
|
1347
1451
|
const hydrated = this.hydratedData.get(hash);
|
|
1348
1452
|
if (hydrated) this.hydratedData.delete(hash);
|
|
1349
|
-
|
|
1453
|
+
const onFetchSuccess = internal.__spec.queryId != null ? (data) => this.emitSetData(internal, keyArgs, data, "data", "fetch") : void 0;
|
|
1454
|
+
entry = new ClientEntry(this, internal, args, keyArgs, internal.__spec, hydrated, onFetchSuccess);
|
|
1350
1455
|
map.set(hash, entry);
|
|
1351
1456
|
entry.scheduleGcIfOrphan();
|
|
1457
|
+
if (hydrated !== void 0) this.emitSetData(internal, keyArgs, hydrated.data, "data", "fetch");
|
|
1352
1458
|
}
|
|
1353
1459
|
return entry;
|
|
1354
1460
|
}
|
|
@@ -1371,6 +1477,7 @@ var QueryClient = class {
|
|
|
1371
1477
|
const entry = map.get(hash);
|
|
1372
1478
|
if (!entry) return;
|
|
1373
1479
|
entry.entry.invalidate().catch((err) => {
|
|
1480
|
+
if (isAbortError(err)) return;
|
|
1374
1481
|
dispatchError(this.onError, err, {
|
|
1375
1482
|
kind: "cache",
|
|
1376
1483
|
controllerPath: [],
|
|
@@ -1385,6 +1492,7 @@ var QueryClient = class {
|
|
|
1385
1492
|
if (!map) return;
|
|
1386
1493
|
for (const [hash, entry] of map) {
|
|
1387
1494
|
entry.entry.invalidate().catch((err) => {
|
|
1495
|
+
if (isAbortError(err)) return;
|
|
1388
1496
|
dispatchError(this.onError, err, {
|
|
1389
1497
|
kind: "cache",
|
|
1390
1498
|
controllerPath: [],
|
|
@@ -1397,7 +1505,7 @@ var QueryClient = class {
|
|
|
1397
1505
|
setData(query, args, updater) {
|
|
1398
1506
|
const entry = this.bindEntry(query, args);
|
|
1399
1507
|
const snapshot = entry.entry.setData(updater);
|
|
1400
|
-
this.emitSetData(entry.query, entry.keyArgs, entry.entry.data.peek(), "data");
|
|
1508
|
+
this.emitSetData(entry.query, entry.keyArgs, entry.entry.data.peek(), "data", "set");
|
|
1401
1509
|
return snapshot;
|
|
1402
1510
|
}
|
|
1403
1511
|
bindInfiniteEntry(query, args) {
|
|
@@ -1413,7 +1521,8 @@ var QueryClient = class {
|
|
|
1413
1521
|
const hash = stableHash(keyArgs);
|
|
1414
1522
|
let entry = map.get(hash);
|
|
1415
1523
|
if (!entry) {
|
|
1416
|
-
|
|
1524
|
+
const onFetchSuccess = internal.__spec.queryId != null ? (pages) => this.emitSetData(internal, keyArgs, pages, "infinite", "fetch") : void 0;
|
|
1525
|
+
entry = new InfiniteClientEntry(this, internal, args, keyArgs, internal.__spec, onFetchSuccess);
|
|
1417
1526
|
map.set(hash, entry);
|
|
1418
1527
|
entry.scheduleGcIfOrphan();
|
|
1419
1528
|
}
|
|
@@ -1438,6 +1547,7 @@ var QueryClient = class {
|
|
|
1438
1547
|
const entry = map.get(hash);
|
|
1439
1548
|
if (!entry) return;
|
|
1440
1549
|
entry.entry.invalidate().catch((err) => {
|
|
1550
|
+
if (isAbortError(err)) return;
|
|
1441
1551
|
dispatchError(this.onError, err, {
|
|
1442
1552
|
kind: "cache",
|
|
1443
1553
|
controllerPath: [],
|
|
@@ -1452,6 +1562,7 @@ var QueryClient = class {
|
|
|
1452
1562
|
if (!map) return;
|
|
1453
1563
|
for (const entry of map.values()) {
|
|
1454
1564
|
entry.entry.invalidate().catch((err) => {
|
|
1565
|
+
if (isAbortError(err)) return;
|
|
1455
1566
|
dispatchError(this.onError, err, {
|
|
1456
1567
|
kind: "cache",
|
|
1457
1568
|
controllerPath: [],
|
|
@@ -1464,7 +1575,7 @@ var QueryClient = class {
|
|
|
1464
1575
|
setInfiniteData(query, args, updater) {
|
|
1465
1576
|
const entry = this.bindInfiniteEntry(query, args);
|
|
1466
1577
|
const snapshot = entry.entry.setData(updater);
|
|
1467
|
-
this.emitSetData(entry.query, entry.keyArgs, entry.entry.pages.peek(), "infinite");
|
|
1578
|
+
this.emitSetData(entry.query, entry.keyArgs, entry.entry.pages.peek(), "infinite", "set");
|
|
1468
1579
|
return snapshot;
|
|
1469
1580
|
}
|
|
1470
1581
|
prefetchInfinite(query, args) {
|
|
@@ -1521,12 +1632,25 @@ function waitUntilFalse(sig) {
|
|
|
1521
1632
|
//#endregion
|
|
1522
1633
|
//#region src/emitter.ts
|
|
1523
1634
|
var EmitterImpl = class {
|
|
1635
|
+
onError;
|
|
1524
1636
|
handlers = /* @__PURE__ */ new Set();
|
|
1525
1637
|
disposed = false;
|
|
1638
|
+
constructor(onError) {
|
|
1639
|
+
this.onError = onError;
|
|
1640
|
+
}
|
|
1526
1641
|
emit(value) {
|
|
1527
1642
|
if (this.disposed) return;
|
|
1528
1643
|
const snapshot = Array.from(this.handlers);
|
|
1529
|
-
for (const handler of snapshot)
|
|
1644
|
+
for (const handler of snapshot) try {
|
|
1645
|
+
handler(value);
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
if (this.onError) try {
|
|
1648
|
+
this.onError(err);
|
|
1649
|
+
} catch {
|
|
1650
|
+
console.error("[olas] emitter handler threw and reporter threw:", err);
|
|
1651
|
+
}
|
|
1652
|
+
else console.error("[olas] emitter handler threw:", err);
|
|
1653
|
+
}
|
|
1530
1654
|
}
|
|
1531
1655
|
on(handler) {
|
|
1532
1656
|
if (this.disposed) return () => {};
|
|
@@ -1558,9 +1682,14 @@ var EmitterImpl = class {
|
|
|
1558
1682
|
* (or the emitter is disposed). Use this for emitters that live outside any
|
|
1559
1683
|
* single controller — typically in deps. Use `ctx.emitter()` for emitters that
|
|
1560
1684
|
* should auto-clean with a controller.
|
|
1685
|
+
*
|
|
1686
|
+
* Pass `onError` to receive emit-time handler throws (spec §20.6 — one
|
|
1687
|
+
* throwing handler must not block the rest of the fan-out). `ctx.emitter()`
|
|
1688
|
+
* wires this to the root's `onError` so deps-level emitters get isolation
|
|
1689
|
+
* by default when constructed via `ctx`.
|
|
1561
1690
|
*/
|
|
1562
|
-
function createEmitter() {
|
|
1563
|
-
const impl = new EmitterImpl();
|
|
1691
|
+
function createEmitter(options) {
|
|
1692
|
+
const impl = new EmitterImpl(options?.onError);
|
|
1564
1693
|
return {
|
|
1565
1694
|
emit: ((value) => impl.emit(value)),
|
|
1566
1695
|
on: (handler) => impl.on(handler),
|
|
@@ -1587,9 +1716,11 @@ var FieldImpl = class {
|
|
|
1587
1716
|
runId = 0;
|
|
1588
1717
|
disposed = false;
|
|
1589
1718
|
devtoolsOwner = null;
|
|
1590
|
-
|
|
1719
|
+
onValidatorError = null;
|
|
1720
|
+
constructor(initial, validators = [], options) {
|
|
1591
1721
|
this.initial = initial;
|
|
1592
1722
|
this.validators = validators;
|
|
1723
|
+
this.onValidatorError = options?.onValidatorError ?? null;
|
|
1593
1724
|
this.value$ = signal(initial);
|
|
1594
1725
|
this.errors$ = signal([]);
|
|
1595
1726
|
this.touched$ = signal(false);
|
|
@@ -1601,6 +1732,13 @@ var FieldImpl = class {
|
|
|
1601
1732
|
this.runValidators();
|
|
1602
1733
|
});
|
|
1603
1734
|
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Internal hook for `ctx.field` / `createForm` to route synchronous
|
|
1737
|
+
* validator throws through `root.onError`. See `ValidatorErrorReporter`.
|
|
1738
|
+
*/
|
|
1739
|
+
bindValidatorErrorReporter(reporter) {
|
|
1740
|
+
this.onValidatorError = reporter;
|
|
1741
|
+
}
|
|
1604
1742
|
get value() {
|
|
1605
1743
|
return this.value$.value;
|
|
1606
1744
|
}
|
|
@@ -1707,10 +1845,15 @@ var FieldImpl = class {
|
|
|
1707
1845
|
const myId = ++this.runId;
|
|
1708
1846
|
const syncErrors = [];
|
|
1709
1847
|
const asyncPromises = [];
|
|
1710
|
-
for (const validator of this.validators) {
|
|
1848
|
+
for (const validator of this.validators) try {
|
|
1711
1849
|
const result = validator(value, abort.signal);
|
|
1712
1850
|
if (result instanceof Promise) asyncPromises.push(result);
|
|
1713
1851
|
else if (result != null) syncErrors.push(result);
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
try {
|
|
1854
|
+
this.onValidatorError?.(err);
|
|
1855
|
+
} catch {}
|
|
1856
|
+
syncErrors.push(err instanceof Error ? err.message : String(err));
|
|
1714
1857
|
}
|
|
1715
1858
|
if (syncErrors.length > 0) {
|
|
1716
1859
|
batch(() => {
|
|
@@ -1758,8 +1901,18 @@ function bindFieldDevtoolsOwner(field, owner) {
|
|
|
1758
1901
|
const impl = field;
|
|
1759
1902
|
if (typeof impl.bindDevtoolsOwner === "function") impl.bindDevtoolsOwner(owner);
|
|
1760
1903
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1904
|
+
/**
|
|
1905
|
+
* Internal — install a synchronous-validator-throw reporter on a `Field`
|
|
1906
|
+
* (matched structurally to keep the public `Field<T>` surface stable).
|
|
1907
|
+
* Called by `ctx.field` and `bindTreeToDevtools` so leaves inside a form/
|
|
1908
|
+
* field-array tree get the same reporting as a standalone field.
|
|
1909
|
+
*/
|
|
1910
|
+
function bindFieldValidatorErrorReporter(field, reporter) {
|
|
1911
|
+
const impl = field;
|
|
1912
|
+
if (typeof impl.bindValidatorErrorReporter === "function") impl.bindValidatorErrorReporter(reporter);
|
|
1913
|
+
}
|
|
1914
|
+
function createField(initial, validators, options) {
|
|
1915
|
+
return new FieldImpl(initial, validators, options);
|
|
1763
1916
|
}
|
|
1764
1917
|
/**
|
|
1765
1918
|
* Wrap an async validator with a debounce. The debounce timer resets on every
|
|
@@ -1806,17 +1959,40 @@ var FormImpl = class {
|
|
|
1806
1959
|
validators;
|
|
1807
1960
|
options;
|
|
1808
1961
|
validatorDispose = null;
|
|
1962
|
+
initialDispose = null;
|
|
1809
1963
|
currentValidatorRun = 0;
|
|
1810
1964
|
currentValidatorAbort = null;
|
|
1811
1965
|
disposed = false;
|
|
1812
|
-
|
|
1966
|
+
onValidatorError = null;
|
|
1967
|
+
/** Internal — wire a sync-throw reporter for the top-level validators. */
|
|
1968
|
+
bindValidatorErrorReporter(reporter) {
|
|
1969
|
+
this.onValidatorError = reporter;
|
|
1970
|
+
}
|
|
1971
|
+
constructor(schema, options, internalOptions) {
|
|
1813
1972
|
this.fields = schema;
|
|
1814
1973
|
this.options = options;
|
|
1815
1974
|
this.validators = options?.validators ?? [];
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1975
|
+
this.onValidatorError = internalOptions?.onValidatorError ?? null;
|
|
1976
|
+
if (options?.initial !== void 0) if (typeof options.initial === "function") {
|
|
1977
|
+
const initialFn = options.initial;
|
|
1978
|
+
const mode = options.resetOnInitialChange ?? "when-clean";
|
|
1979
|
+
let firstRun = true;
|
|
1980
|
+
this.initialDispose = effect(() => {
|
|
1981
|
+
const ini = initialFn();
|
|
1982
|
+
if (ini === void 0) return;
|
|
1983
|
+
untracked(() => {
|
|
1984
|
+
if (this.disposed) return;
|
|
1985
|
+
if (firstRun) {
|
|
1986
|
+
firstRun = false;
|
|
1987
|
+
this.applyPartial(ini, true);
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
if (mode === "never") return;
|
|
1991
|
+
if (mode === "when-clean" && this.isDirty.peek()) return;
|
|
1992
|
+
this.applyPartial(ini, true);
|
|
1993
|
+
});
|
|
1994
|
+
});
|
|
1995
|
+
} else this.applyPartial(options.initial, true);
|
|
1820
1996
|
this.value = computed(() => this.computeValue());
|
|
1821
1997
|
this.errors = computed(() => this.computeErrors());
|
|
1822
1998
|
this.isDirty = computed(() => this.computeBool("isDirty"));
|
|
@@ -1873,12 +2049,28 @@ var FormImpl = class {
|
|
|
1873
2049
|
for (const [k, val] of Object.entries(partial)) {
|
|
1874
2050
|
const child = this.fields[k];
|
|
1875
2051
|
if (!child) continue;
|
|
2052
|
+
if (val === void 0) continue;
|
|
1876
2053
|
if (isForm(child)) if (asInitial) child.resetWithInitial(val);
|
|
1877
2054
|
else child.set(val);
|
|
1878
2055
|
else if (isFieldArray(child)) {
|
|
1879
2056
|
const arr = child;
|
|
1880
|
-
|
|
1881
|
-
|
|
2057
|
+
const newValues = val;
|
|
2058
|
+
if (asInitial) {
|
|
2059
|
+
arr.clear();
|
|
2060
|
+
for (const itemVal of newValues) arr.add(itemVal);
|
|
2061
|
+
arr.replaceInitialItems(newValues);
|
|
2062
|
+
} else {
|
|
2063
|
+
const current = arr.items.peek();
|
|
2064
|
+
const overlap = Math.min(current.length, newValues.length);
|
|
2065
|
+
for (let i = 0; i < overlap; i++) {
|
|
2066
|
+
const item = current[i];
|
|
2067
|
+
const v = newValues[i];
|
|
2068
|
+
if (isForm(item)) item.set(v);
|
|
2069
|
+
else item.set(v);
|
|
2070
|
+
}
|
|
2071
|
+
for (let i = current.length; i < newValues.length; i++) arr.add(newValues[i]);
|
|
2072
|
+
for (let i = current.length - 1; i >= newValues.length; i--) arr.remove(i);
|
|
2073
|
+
}
|
|
1882
2074
|
} else {
|
|
1883
2075
|
const f = child;
|
|
1884
2076
|
if (asInitial) f.setAsInitial(val);
|
|
@@ -1915,6 +2107,7 @@ var FormImpl = class {
|
|
|
1915
2107
|
for (const child of Object.values(this.fields)) if (isForm(child) || isFieldArray(child)) tasks.push(child.validate());
|
|
1916
2108
|
else tasks.push(child.revalidate());
|
|
1917
2109
|
await Promise.all(tasks);
|
|
2110
|
+
if (this.validators.length > 0) this.runTopLevelValidators();
|
|
1918
2111
|
if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
|
|
1919
2112
|
const unsub = this.topLevelValidating$.subscribe((v) => {
|
|
1920
2113
|
if (!v) {
|
|
@@ -1929,6 +2122,7 @@ var FormImpl = class {
|
|
|
1929
2122
|
if (this.disposed) return;
|
|
1930
2123
|
this.disposed = true;
|
|
1931
2124
|
this.validatorDispose?.();
|
|
2125
|
+
this.initialDispose?.();
|
|
1932
2126
|
this.currentValidatorAbort?.abort();
|
|
1933
2127
|
for (const child of Object.values(this.fields)) child.dispose?.();
|
|
1934
2128
|
}
|
|
@@ -1941,10 +2135,15 @@ var FormImpl = class {
|
|
|
1941
2135
|
const myId = ++this.currentValidatorRun;
|
|
1942
2136
|
const syncErrors = [];
|
|
1943
2137
|
const asyncPromises = [];
|
|
1944
|
-
for (const v of this.validators) {
|
|
2138
|
+
for (const v of this.validators) try {
|
|
1945
2139
|
const r = v(value, abort.signal);
|
|
1946
2140
|
if (r instanceof Promise) asyncPromises.push(r);
|
|
1947
2141
|
else if (r != null) syncErrors.push(r);
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
try {
|
|
2144
|
+
this.onValidatorError?.(err);
|
|
2145
|
+
} catch {}
|
|
2146
|
+
syncErrors.push(err instanceof Error ? err.message : String(err));
|
|
1948
2147
|
}
|
|
1949
2148
|
if (syncErrors.length > 0) {
|
|
1950
2149
|
batch(() => {
|
|
@@ -2038,9 +2237,15 @@ var FieldArrayImpl = class {
|
|
|
2038
2237
|
currentValidatorAbort = null;
|
|
2039
2238
|
validatorDispose = null;
|
|
2040
2239
|
disposed = false;
|
|
2041
|
-
|
|
2240
|
+
onValidatorError = null;
|
|
2241
|
+
/** Internal — see `FormImpl.bindValidatorErrorReporter`. */
|
|
2242
|
+
bindValidatorErrorReporter(reporter) {
|
|
2243
|
+
this.onValidatorError = reporter;
|
|
2244
|
+
}
|
|
2245
|
+
constructor(itemFactory, options, internalOptions) {
|
|
2042
2246
|
this.itemFactory = itemFactory;
|
|
2043
2247
|
this.validators = options?.validators ?? [];
|
|
2248
|
+
this.onValidatorError = internalOptions?.onValidatorError ?? null;
|
|
2044
2249
|
this.items$ = signal([]);
|
|
2045
2250
|
if (options?.initial) {
|
|
2046
2251
|
this.initialItems = options.initial;
|
|
@@ -2113,6 +2318,15 @@ var FieldArrayImpl = class {
|
|
|
2113
2318
|
for (const item of this.items$.peek()) item.dispose?.();
|
|
2114
2319
|
this.items$.set([]);
|
|
2115
2320
|
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Internal — used by `Form.resetWithInitial` to re-anchor the array's
|
|
2323
|
+
* initial items after a parent-driven `applyPartial(..., asInitial: true)`.
|
|
2324
|
+
* Without this, a subsequent `reset()` would revert to the construction-
|
|
2325
|
+
* time initials rather than the most-recently-applied ones.
|
|
2326
|
+
*/
|
|
2327
|
+
replaceInitialItems(items) {
|
|
2328
|
+
this.initialItems = [...items];
|
|
2329
|
+
}
|
|
2116
2330
|
reset() {
|
|
2117
2331
|
if (this.disposed) return;
|
|
2118
2332
|
batch(() => {
|
|
@@ -2131,6 +2345,7 @@ var FieldArrayImpl = class {
|
|
|
2131
2345
|
for (const item of this.items$.peek()) if (isForm(item)) tasks.push(item.validate());
|
|
2132
2346
|
else tasks.push(item.revalidate());
|
|
2133
2347
|
await Promise.all(tasks);
|
|
2348
|
+
if (this.validators.length > 0) this.runTopLevelValidators();
|
|
2134
2349
|
if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
|
|
2135
2350
|
const unsub = this.topLevelValidating$.subscribe((v) => {
|
|
2136
2351
|
if (!v) {
|
|
@@ -2157,10 +2372,15 @@ var FieldArrayImpl = class {
|
|
|
2157
2372
|
const myId = ++this.currentValidatorRun;
|
|
2158
2373
|
const syncErrors = [];
|
|
2159
2374
|
const asyncPromises = [];
|
|
2160
|
-
for (const v of this.validators) {
|
|
2375
|
+
for (const v of this.validators) try {
|
|
2161
2376
|
const r = v(value, abort.signal);
|
|
2162
2377
|
if (r instanceof Promise) asyncPromises.push(r);
|
|
2163
2378
|
else if (r != null) syncErrors.push(r);
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
try {
|
|
2381
|
+
this.onValidatorError?.(err);
|
|
2382
|
+
} catch {}
|
|
2383
|
+
syncErrors.push(err instanceof Error ? err.message : String(err));
|
|
2164
2384
|
}
|
|
2165
2385
|
if (syncErrors.length > 0) {
|
|
2166
2386
|
batch(() => {
|
|
@@ -2191,11 +2411,11 @@ var FieldArrayImpl = class {
|
|
|
2191
2411
|
});
|
|
2192
2412
|
}
|
|
2193
2413
|
};
|
|
2194
|
-
function createForm(schema, options) {
|
|
2195
|
-
return new FormImpl(schema, options);
|
|
2414
|
+
function createForm(schema, options, internalOptions) {
|
|
2415
|
+
return new FormImpl(schema, options, internalOptions);
|
|
2196
2416
|
}
|
|
2197
|
-
function createFieldArray(itemFactory, options) {
|
|
2198
|
-
return new FieldArrayImpl(itemFactory, options);
|
|
2417
|
+
function createFieldArray(itemFactory, options, internalOptions) {
|
|
2418
|
+
return new FieldArrayImpl(itemFactory, options, internalOptions);
|
|
2199
2419
|
}
|
|
2200
2420
|
/**
|
|
2201
2421
|
* Recursively wire every leaf `Field` in a form / field-array tree to a
|
|
@@ -2222,12 +2442,24 @@ function bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers
|
|
|
2222
2442
|
}
|
|
2223
2443
|
if (isFieldArray(node)) {
|
|
2224
2444
|
const arr = node;
|
|
2445
|
+
let perPass = [];
|
|
2225
2446
|
const stop = effect(() => {
|
|
2226
|
-
arr.items.value
|
|
2227
|
-
|
|
2447
|
+
const items = arr.items.value;
|
|
2448
|
+
for (const d of perPass) try {
|
|
2449
|
+
d();
|
|
2450
|
+
} catch {}
|
|
2451
|
+
perPass = [];
|
|
2452
|
+
items.forEach((item, idx) => {
|
|
2453
|
+
bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter, perPass);
|
|
2228
2454
|
});
|
|
2229
2455
|
});
|
|
2230
2456
|
disposers.push(stop);
|
|
2457
|
+
disposers.push(() => {
|
|
2458
|
+
for (const d of perPass) try {
|
|
2459
|
+
d();
|
|
2460
|
+
} catch {}
|
|
2461
|
+
perPass = [];
|
|
2462
|
+
});
|
|
2231
2463
|
return;
|
|
2232
2464
|
}
|
|
2233
2465
|
bindFieldDevtoolsOwner(node, {
|
|
@@ -2236,6 +2468,26 @@ function bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers
|
|
|
2236
2468
|
emitter
|
|
2237
2469
|
});
|
|
2238
2470
|
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Walk a Form/FieldArray subtree and install `reporter` on every level —
|
|
2473
|
+
* leaf fields, nested forms' top-level validators, and field-arrays' top-level
|
|
2474
|
+
* validators. Called by `ctx.form` / `ctx.fieldArray` so synchronous validator
|
|
2475
|
+
* throws anywhere in the tree route through `root.onError`. See
|
|
2476
|
+
* `ValidatorErrorReporter` in `./field.ts`.
|
|
2477
|
+
*/
|
|
2478
|
+
function bindTreeValidatorErrorReporter(node, reporter) {
|
|
2479
|
+
if (isForm(node)) {
|
|
2480
|
+
node.bindValidatorErrorReporter?.(reporter);
|
|
2481
|
+
for (const child of Object.values(node.fields)) bindTreeValidatorErrorReporter(child, reporter);
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
if (isFieldArray(node)) {
|
|
2485
|
+
node.bindValidatorErrorReporter?.(reporter);
|
|
2486
|
+
for (const item of node.items.value) bindTreeValidatorErrorReporter(item, reporter);
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
bindFieldValidatorErrorReporter(node, reporter);
|
|
2490
|
+
}
|
|
2239
2491
|
//#endregion
|
|
2240
2492
|
//#region src/query/local.ts
|
|
2241
2493
|
var LocalCacheImpl = class {
|
|
@@ -2469,7 +2721,13 @@ var MutationImpl = class {
|
|
|
2469
2721
|
reset() {
|
|
2470
2722
|
if (this.disposed) return;
|
|
2471
2723
|
for (const handle of this.inflight) handle.abort.abort();
|
|
2472
|
-
this.serialQueue.length
|
|
2724
|
+
if (this.serialQueue.length > 0) {
|
|
2725
|
+
const aborted = new DOMException("Aborted", "AbortError");
|
|
2726
|
+
const queue = this.serialQueue;
|
|
2727
|
+
this.serialQueue = [];
|
|
2728
|
+
for (const queued of queue) queued.reject(aborted);
|
|
2729
|
+
}
|
|
2730
|
+
this.serialActive = false;
|
|
2473
2731
|
batch(() => {
|
|
2474
2732
|
this.data.set(void 0);
|
|
2475
2733
|
this.error.set(void 0);
|
|
@@ -2517,24 +2775,6 @@ function raceAbort(promise, signal) {
|
|
|
2517
2775
|
});
|
|
2518
2776
|
});
|
|
2519
2777
|
}
|
|
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
2778
|
//#endregion
|
|
2539
2779
|
//#region src/query/use.ts
|
|
2540
2780
|
var SubscriptionImpl = class {
|
|
@@ -2605,7 +2845,9 @@ function createUse(client, query, keyOrOptions) {
|
|
|
2605
2845
|
const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
|
|
2606
2846
|
const sub = new SubscriptionImpl(keepPreviousData);
|
|
2607
2847
|
let currentEntry = null;
|
|
2848
|
+
let suspended = false;
|
|
2608
2849
|
const effectDispose = effect(() => {
|
|
2850
|
+
if (suspended) return;
|
|
2609
2851
|
if (!(enabledFn ? enabledFn() : true)) {
|
|
2610
2852
|
untracked(() => {
|
|
2611
2853
|
if (currentEntry) {
|
|
@@ -2636,9 +2878,31 @@ function createUse(client, query, keyOrOptions) {
|
|
|
2636
2878
|
}
|
|
2637
2879
|
sub.detach();
|
|
2638
2880
|
};
|
|
2881
|
+
const suspend = () => {
|
|
2882
|
+
if (suspended) return;
|
|
2883
|
+
suspended = true;
|
|
2884
|
+
if (currentEntry) {
|
|
2885
|
+
currentEntry.release();
|
|
2886
|
+
currentEntry = null;
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
const resume = () => {
|
|
2890
|
+
if (!suspended) return;
|
|
2891
|
+
suspended = false;
|
|
2892
|
+
if (!(enabledFn ? enabledFn() : true)) return;
|
|
2893
|
+
const args = keyFn ? keyFn() : [];
|
|
2894
|
+
const entry = client.bindEntry(query, args);
|
|
2895
|
+
entry.acquire();
|
|
2896
|
+
currentEntry = entry;
|
|
2897
|
+
sub.attach(entry);
|
|
2898
|
+
const status = entry.entry.status.peek();
|
|
2899
|
+
if (status === "idle" || entry.entry.isStaleNow() || status === "error") entry.entry.startFetch().catch(() => {});
|
|
2900
|
+
};
|
|
2639
2901
|
return {
|
|
2640
2902
|
subscription: sub,
|
|
2641
|
-
dispose
|
|
2903
|
+
dispose,
|
|
2904
|
+
suspend,
|
|
2905
|
+
resume
|
|
2642
2906
|
};
|
|
2643
2907
|
}
|
|
2644
2908
|
var InfiniteSubscriptionImpl = class {
|
|
@@ -2738,7 +3002,9 @@ function createInfiniteUse(client, query, keyOrOptions) {
|
|
|
2738
3002
|
const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
|
|
2739
3003
|
const sub = new InfiniteSubscriptionImpl(keepPreviousData);
|
|
2740
3004
|
let currentEntry = null;
|
|
3005
|
+
let suspended = false;
|
|
2741
3006
|
const effectDispose = effect(() => {
|
|
3007
|
+
if (suspended) return;
|
|
2742
3008
|
if (!(enabledFn ? enabledFn() : true)) {
|
|
2743
3009
|
untracked(() => {
|
|
2744
3010
|
if (currentEntry) {
|
|
@@ -2769,9 +3035,31 @@ function createInfiniteUse(client, query, keyOrOptions) {
|
|
|
2769
3035
|
}
|
|
2770
3036
|
sub.detach();
|
|
2771
3037
|
};
|
|
3038
|
+
const suspend = () => {
|
|
3039
|
+
if (suspended) return;
|
|
3040
|
+
suspended = true;
|
|
3041
|
+
if (currentEntry) {
|
|
3042
|
+
currentEntry.release();
|
|
3043
|
+
currentEntry = null;
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
const resume = () => {
|
|
3047
|
+
if (!suspended) return;
|
|
3048
|
+
suspended = false;
|
|
3049
|
+
if (!(enabledFn ? enabledFn() : true)) return;
|
|
3050
|
+
const args = keyFn ? keyFn() : [];
|
|
3051
|
+
const entry = client.bindInfiniteEntry(query, args);
|
|
3052
|
+
entry.acquire();
|
|
3053
|
+
currentEntry = entry;
|
|
3054
|
+
sub.attach(entry);
|
|
3055
|
+
const status = entry.entry.status.peek();
|
|
3056
|
+
if (status === "idle" || entry.entry.isStaleNow() || status === "error") entry.entry.startFetch().catch(() => {});
|
|
3057
|
+
};
|
|
2772
3058
|
return {
|
|
2773
3059
|
subscription: sub,
|
|
2774
|
-
dispose
|
|
3060
|
+
dispose,
|
|
3061
|
+
suspend,
|
|
3062
|
+
resume
|
|
2775
3063
|
};
|
|
2776
3064
|
}
|
|
2777
3065
|
//#endregion
|
|
@@ -2821,7 +3109,6 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2821
3109
|
}
|
|
2822
3110
|
dispose() {
|
|
2823
3111
|
if (this.state === "disposed") return;
|
|
2824
|
-
this.state;
|
|
2825
3112
|
this.state = "disposed";
|
|
2826
3113
|
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
2827
3114
|
const entry = this.entries[i];
|
|
@@ -2847,6 +3134,9 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2847
3134
|
case "cleanup":
|
|
2848
3135
|
entry.dispose();
|
|
2849
3136
|
break;
|
|
3137
|
+
case "subscription-cache":
|
|
3138
|
+
entry.dispose();
|
|
3139
|
+
break;
|
|
2850
3140
|
case "child":
|
|
2851
3141
|
entry.instance.dispose();
|
|
2852
3142
|
break;
|
|
@@ -2872,6 +3162,9 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2872
3162
|
entry.dispose?.();
|
|
2873
3163
|
entry.dispose = null;
|
|
2874
3164
|
break;
|
|
3165
|
+
case "subscription-cache":
|
|
3166
|
+
entry.suspend();
|
|
3167
|
+
break;
|
|
2875
3168
|
case "child":
|
|
2876
3169
|
entry.instance.suspend();
|
|
2877
3170
|
break;
|
|
@@ -2896,6 +3189,9 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2896
3189
|
case "effect":
|
|
2897
3190
|
entry.dispose = effect(entry.factory);
|
|
2898
3191
|
break;
|
|
3192
|
+
case "subscription-cache":
|
|
3193
|
+
entry.resume();
|
|
3194
|
+
break;
|
|
2899
3195
|
case "child":
|
|
2900
3196
|
entry.instance.resume();
|
|
2901
3197
|
break;
|
|
@@ -2936,7 +3232,7 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2936
3232
|
}
|
|
2937
3233
|
};
|
|
2938
3234
|
entry.factory = wrapped;
|
|
2939
|
-
entry.dispose = effect(wrapped);
|
|
3235
|
+
if (self.state !== "suspended") entry.dispose = effect(wrapped);
|
|
2940
3236
|
self.entries.push(entry);
|
|
2941
3237
|
},
|
|
2942
3238
|
cache(fetcher, options) {
|
|
@@ -2949,19 +3245,23 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2949
3245
|
},
|
|
2950
3246
|
use(query, keyOrOptions) {
|
|
2951
3247
|
if (query.__olas === "infiniteQuery") {
|
|
2952
|
-
const
|
|
3248
|
+
const handle = createInfiniteUse(self.rootShared.queryClient, query, keyOrOptions);
|
|
2953
3249
|
self.entries.push({
|
|
2954
|
-
kind: "
|
|
2955
|
-
dispose:
|
|
3250
|
+
kind: "subscription-cache",
|
|
3251
|
+
dispose: handle.dispose,
|
|
3252
|
+
suspend: handle.suspend,
|
|
3253
|
+
resume: handle.resume
|
|
2956
3254
|
});
|
|
2957
|
-
return subscription;
|
|
3255
|
+
return handle.subscription;
|
|
2958
3256
|
}
|
|
2959
|
-
const
|
|
3257
|
+
const handle = createUse(self.rootShared.queryClient, query, keyOrOptions);
|
|
2960
3258
|
self.entries.push({
|
|
2961
|
-
kind: "
|
|
2962
|
-
dispose:
|
|
3259
|
+
kind: "subscription-cache",
|
|
3260
|
+
dispose: handle.dispose,
|
|
3261
|
+
suspend: handle.suspend,
|
|
3262
|
+
resume: handle.resume
|
|
2963
3263
|
});
|
|
2964
|
-
return subscription;
|
|
3264
|
+
return handle.subscription;
|
|
2965
3265
|
},
|
|
2966
3266
|
mutation(spec) {
|
|
2967
3267
|
const m = createMutation(spec, self.rootShared.onError, self.path, self.rootShared.queryClient.mutationsInflight$, self.rootShared.devtools);
|
|
@@ -2972,7 +3272,12 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2972
3272
|
return m;
|
|
2973
3273
|
},
|
|
2974
3274
|
emitter() {
|
|
2975
|
-
const e = createEmitter()
|
|
3275
|
+
const e = createEmitter({ onError: (err) => {
|
|
3276
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3277
|
+
kind: "emitter",
|
|
3278
|
+
controllerPath: self.path
|
|
3279
|
+
});
|
|
3280
|
+
} });
|
|
2976
3281
|
self.entries.push({
|
|
2977
3282
|
kind: "cleanup",
|
|
2978
3283
|
dispose: () => e.dispose()
|
|
@@ -2980,7 +3285,12 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2980
3285
|
return e;
|
|
2981
3286
|
},
|
|
2982
3287
|
field(initial, validators) {
|
|
2983
|
-
const f = createField(initial, validators)
|
|
3288
|
+
const f = createField(initial, validators, { onValidatorError: (err) => {
|
|
3289
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3290
|
+
kind: "effect",
|
|
3291
|
+
controllerPath: self.path
|
|
3292
|
+
});
|
|
3293
|
+
} });
|
|
2984
3294
|
self.entries.push({
|
|
2985
3295
|
kind: "cleanup",
|
|
2986
3296
|
dispose: () => f.dispose()
|
|
@@ -2993,7 +3303,13 @@ var ControllerInstance = class ControllerInstance {
|
|
|
2993
3303
|
return f;
|
|
2994
3304
|
},
|
|
2995
3305
|
form(schema, options) {
|
|
2996
|
-
const
|
|
3306
|
+
const reporter = (err) => {
|
|
3307
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3308
|
+
kind: "effect",
|
|
3309
|
+
controllerPath: self.path
|
|
3310
|
+
});
|
|
3311
|
+
};
|
|
3312
|
+
const f = createForm(schema, options, { onValidatorError: reporter });
|
|
2997
3313
|
self.entries.push({
|
|
2998
3314
|
kind: "cleanup",
|
|
2999
3315
|
dispose: () => f.dispose()
|
|
@@ -3003,10 +3319,17 @@ var ControllerInstance = class ControllerInstance {
|
|
|
3003
3319
|
kind: "cleanup",
|
|
3004
3320
|
dispose: stop
|
|
3005
3321
|
});
|
|
3322
|
+
bindTreeValidatorErrorReporter(f, reporter);
|
|
3006
3323
|
return f;
|
|
3007
3324
|
},
|
|
3008
3325
|
fieldArray(itemFactory, options) {
|
|
3009
|
-
const
|
|
3326
|
+
const reporter = (err) => {
|
|
3327
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3328
|
+
kind: "effect",
|
|
3329
|
+
controllerPath: self.path
|
|
3330
|
+
});
|
|
3331
|
+
};
|
|
3332
|
+
const fa = createFieldArray(itemFactory, options, { onValidatorError: reporter });
|
|
3010
3333
|
self.entries.push({
|
|
3011
3334
|
kind: "cleanup",
|
|
3012
3335
|
dispose: () => fa.dispose()
|
|
@@ -3016,6 +3339,7 @@ var ControllerInstance = class ControllerInstance {
|
|
|
3016
3339
|
kind: "cleanup",
|
|
3017
3340
|
dispose: stop
|
|
3018
3341
|
});
|
|
3342
|
+
bindTreeValidatorErrorReporter(fa, reporter);
|
|
3019
3343
|
return fa;
|
|
3020
3344
|
},
|
|
3021
3345
|
provide(scope, value) {
|
|
@@ -3095,9 +3419,280 @@ var ControllerInstance = class ControllerInstance {
|
|
|
3095
3419
|
controllerPath: self.path
|
|
3096
3420
|
});
|
|
3097
3421
|
}
|
|
3422
|
+
},
|
|
3423
|
+
suspend: () => {
|
|
3424
|
+
if (disposed) return;
|
|
3425
|
+
try {
|
|
3426
|
+
childInstance.suspend();
|
|
3427
|
+
} catch (err) {
|
|
3428
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3429
|
+
kind: "effect",
|
|
3430
|
+
controllerPath: self.path
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
},
|
|
3434
|
+
resume: () => {
|
|
3435
|
+
if (disposed) return;
|
|
3436
|
+
try {
|
|
3437
|
+
childInstance.resume();
|
|
3438
|
+
} catch (err) {
|
|
3439
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3440
|
+
kind: "effect",
|
|
3441
|
+
controllerPath: self.path
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3098
3444
|
}
|
|
3099
3445
|
};
|
|
3100
3446
|
},
|
|
3447
|
+
session(def, props, options) {
|
|
3448
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3449
|
+
const override = options?.deps;
|
|
3450
|
+
const childDeps = override !== void 0 ? {
|
|
3451
|
+
...self.deps,
|
|
3452
|
+
...override
|
|
3453
|
+
} : self.deps;
|
|
3454
|
+
const childInstance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3455
|
+
const api = childInstance.construct(getFactory(def), props);
|
|
3456
|
+
const entry = {
|
|
3457
|
+
kind: "child",
|
|
3458
|
+
instance: childInstance
|
|
3459
|
+
};
|
|
3460
|
+
self.entries.push(entry);
|
|
3461
|
+
let disposed = false;
|
|
3462
|
+
const dispose = () => {
|
|
3463
|
+
if (disposed) return;
|
|
3464
|
+
disposed = true;
|
|
3465
|
+
const idx = self.entries.indexOf(entry);
|
|
3466
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3467
|
+
try {
|
|
3468
|
+
childInstance.dispose();
|
|
3469
|
+
} catch (err) {
|
|
3470
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3471
|
+
kind: "effect",
|
|
3472
|
+
controllerPath: self.path
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
};
|
|
3476
|
+
return [api, dispose];
|
|
3477
|
+
},
|
|
3478
|
+
collection(options) {
|
|
3479
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
3480
|
+
const items$ = signal([]);
|
|
3481
|
+
const size$ = computed(() => items$.value.length);
|
|
3482
|
+
const isFactoryForm = options.factory !== void 0;
|
|
3483
|
+
const buildChild = (item) => {
|
|
3484
|
+
let def;
|
|
3485
|
+
let childProps;
|
|
3486
|
+
if (isFactoryForm) {
|
|
3487
|
+
const result = options.factory(item);
|
|
3488
|
+
def = result.controller;
|
|
3489
|
+
childProps = result.props;
|
|
3490
|
+
} else {
|
|
3491
|
+
const homoOpts = options;
|
|
3492
|
+
def = homoOpts.controller;
|
|
3493
|
+
childProps = homoOpts.propsOf(item);
|
|
3494
|
+
}
|
|
3495
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3496
|
+
const childDeps = options.deps !== void 0 ? {
|
|
3497
|
+
...self.deps,
|
|
3498
|
+
...options.deps
|
|
3499
|
+
} : self.deps;
|
|
3500
|
+
const instance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3501
|
+
try {
|
|
3502
|
+
return {
|
|
3503
|
+
instance,
|
|
3504
|
+
api: instance.construct(getFactory(def), childProps),
|
|
3505
|
+
def
|
|
3506
|
+
};
|
|
3507
|
+
} catch (err) {
|
|
3508
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3509
|
+
kind: "construction",
|
|
3510
|
+
controllerPath: self.path
|
|
3511
|
+
});
|
|
3512
|
+
return null;
|
|
3513
|
+
}
|
|
3514
|
+
};
|
|
3515
|
+
const removeKey = (key) => {
|
|
3516
|
+
const info = childMap.get(key);
|
|
3517
|
+
if (info === void 0) return;
|
|
3518
|
+
childMap.delete(key);
|
|
3519
|
+
const idx = self.entries.indexOf(info.entry);
|
|
3520
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3521
|
+
try {
|
|
3522
|
+
info.instance.dispose();
|
|
3523
|
+
} catch (err) {
|
|
3524
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3525
|
+
kind: "effect",
|
|
3526
|
+
controllerPath: self.path
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3529
|
+
};
|
|
3530
|
+
const reconcile = () => {
|
|
3531
|
+
const source = options.source.value;
|
|
3532
|
+
const itemByKey = /* @__PURE__ */ new Map();
|
|
3533
|
+
for (const item of source) {
|
|
3534
|
+
const key = options.keyOf(item);
|
|
3535
|
+
if (!itemByKey.has(key)) itemByKey.set(key, item);
|
|
3536
|
+
}
|
|
3537
|
+
for (const key of [...childMap.keys()]) if (!itemByKey.has(key)) removeKey(key);
|
|
3538
|
+
for (const [key, item] of itemByKey) {
|
|
3539
|
+
const existing = childMap.get(key);
|
|
3540
|
+
if (existing !== void 0) {
|
|
3541
|
+
if (isFactoryForm) {
|
|
3542
|
+
if (options.factory(item).controller !== existing.def) {
|
|
3543
|
+
removeKey(key);
|
|
3544
|
+
const built = buildChild(item);
|
|
3545
|
+
if (built !== null) {
|
|
3546
|
+
const entry = {
|
|
3547
|
+
kind: "child",
|
|
3548
|
+
instance: built.instance
|
|
3549
|
+
};
|
|
3550
|
+
self.entries.push(entry);
|
|
3551
|
+
childMap.set(key, {
|
|
3552
|
+
...built,
|
|
3553
|
+
entry
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
continue;
|
|
3559
|
+
}
|
|
3560
|
+
const built = buildChild(item);
|
|
3561
|
+
if (built !== null) {
|
|
3562
|
+
const entry = {
|
|
3563
|
+
kind: "child",
|
|
3564
|
+
instance: built.instance
|
|
3565
|
+
};
|
|
3566
|
+
self.entries.push(entry);
|
|
3567
|
+
childMap.set(key, {
|
|
3568
|
+
...built,
|
|
3569
|
+
entry
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
const next = [];
|
|
3574
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3575
|
+
for (const item of source) {
|
|
3576
|
+
const key = options.keyOf(item);
|
|
3577
|
+
if (seen.has(key)) continue;
|
|
3578
|
+
seen.add(key);
|
|
3579
|
+
const info = childMap.get(key);
|
|
3580
|
+
if (info !== void 0) next.push({
|
|
3581
|
+
key,
|
|
3582
|
+
api: info.api
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
items$.set(next);
|
|
3586
|
+
};
|
|
3587
|
+
const wrapped = () => {
|
|
3588
|
+
try {
|
|
3589
|
+
reconcile();
|
|
3590
|
+
} catch (err) {
|
|
3591
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3592
|
+
kind: "effect",
|
|
3593
|
+
controllerPath: self.path
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
};
|
|
3597
|
+
const effectEntry = {
|
|
3598
|
+
kind: "effect",
|
|
3599
|
+
factory: wrapped,
|
|
3600
|
+
dispose: null
|
|
3601
|
+
};
|
|
3602
|
+
if (self.state !== "suspended") effectEntry.dispose = effect(wrapped);
|
|
3603
|
+
self.entries.push(effectEntry);
|
|
3604
|
+
return {
|
|
3605
|
+
items: items$,
|
|
3606
|
+
size: size$,
|
|
3607
|
+
get: (key) => childMap.get(key)?.api,
|
|
3608
|
+
has: (key) => childMap.has(key)
|
|
3609
|
+
};
|
|
3610
|
+
},
|
|
3611
|
+
lazyChild(loader, props, options) {
|
|
3612
|
+
const status$ = signal("idle");
|
|
3613
|
+
const api$ = signal(void 0);
|
|
3614
|
+
const error$ = signal(void 0);
|
|
3615
|
+
let childInstance = null;
|
|
3616
|
+
let childEntry = null;
|
|
3617
|
+
let pendingLoad = null;
|
|
3618
|
+
let disposed = false;
|
|
3619
|
+
const flagEntry = {
|
|
3620
|
+
kind: "onDispose",
|
|
3621
|
+
fn: () => {
|
|
3622
|
+
disposed = true;
|
|
3623
|
+
}
|
|
3624
|
+
};
|
|
3625
|
+
self.entries.push(flagEntry);
|
|
3626
|
+
const handleFailure = (err) => {
|
|
3627
|
+
status$.set("error");
|
|
3628
|
+
error$.set(err);
|
|
3629
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3630
|
+
kind: "construction",
|
|
3631
|
+
controllerPath: self.path
|
|
3632
|
+
});
|
|
3633
|
+
};
|
|
3634
|
+
const load = () => {
|
|
3635
|
+
if (disposed) return Promise.reject(/* @__PURE__ */ new Error("[olas] ctx.lazyChild: cannot load after dispose"));
|
|
3636
|
+
if (pendingLoad !== null) return pendingLoad;
|
|
3637
|
+
status$.set("loading");
|
|
3638
|
+
pendingLoad = loader().then((def) => {
|
|
3639
|
+
if (disposed) throw new Error("[olas] ctx.lazyChild: disposed during load");
|
|
3640
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3641
|
+
const childDeps = options?.deps !== void 0 ? {
|
|
3642
|
+
...self.deps,
|
|
3643
|
+
...options.deps
|
|
3644
|
+
} : self.deps;
|
|
3645
|
+
const instance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3646
|
+
try {
|
|
3647
|
+
const api = instance.construct(getFactory(def), props);
|
|
3648
|
+
childInstance = instance;
|
|
3649
|
+
childEntry = {
|
|
3650
|
+
kind: "child",
|
|
3651
|
+
instance
|
|
3652
|
+
};
|
|
3653
|
+
self.entries.push(childEntry);
|
|
3654
|
+
api$.set(api);
|
|
3655
|
+
status$.set("ready");
|
|
3656
|
+
return api;
|
|
3657
|
+
} catch (err) {
|
|
3658
|
+
handleFailure(err);
|
|
3659
|
+
throw err;
|
|
3660
|
+
}
|
|
3661
|
+
}, (err) => {
|
|
3662
|
+
if (disposed) throw err;
|
|
3663
|
+
handleFailure(err);
|
|
3664
|
+
throw err;
|
|
3665
|
+
});
|
|
3666
|
+
return pendingLoad;
|
|
3667
|
+
};
|
|
3668
|
+
const dispose = () => {
|
|
3669
|
+
if (disposed) return;
|
|
3670
|
+
disposed = true;
|
|
3671
|
+
if (childEntry !== null && childInstance !== null) {
|
|
3672
|
+
const idx = self.entries.indexOf(childEntry);
|
|
3673
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3674
|
+
try {
|
|
3675
|
+
childInstance.dispose();
|
|
3676
|
+
} catch (err) {
|
|
3677
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3678
|
+
kind: "effect",
|
|
3679
|
+
controllerPath: self.path
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
childInstance = null;
|
|
3683
|
+
childEntry = null;
|
|
3684
|
+
}
|
|
3685
|
+
const flagIdx = self.entries.indexOf(flagEntry);
|
|
3686
|
+
if (flagIdx >= 0) self.entries.splice(flagIdx, 1);
|
|
3687
|
+
};
|
|
3688
|
+
return {
|
|
3689
|
+
status: status$,
|
|
3690
|
+
api: api$,
|
|
3691
|
+
error: error$,
|
|
3692
|
+
load,
|
|
3693
|
+
dispose
|
|
3694
|
+
};
|
|
3695
|
+
},
|
|
3101
3696
|
onDispose(fn) {
|
|
3102
3697
|
self.entries.push({
|
|
3103
3698
|
kind: "onDispose",
|
|
@@ -3186,7 +3781,13 @@ function createRootWithProps(def, props, options) {
|
|
|
3186
3781
|
onError: options.onError,
|
|
3187
3782
|
queryClient
|
|
3188
3783
|
}, "root", options.deps);
|
|
3189
|
-
|
|
3784
|
+
let api;
|
|
3785
|
+
try {
|
|
3786
|
+
api = instance.construct(getFactory(def), props);
|
|
3787
|
+
} catch (err) {
|
|
3788
|
+
queryClient.dispose();
|
|
3789
|
+
throw err;
|
|
3790
|
+
}
|
|
3190
3791
|
if (typeof api !== "object" || api === null) return attachRootControls({ value: api }, instance, devtools, queryClient);
|
|
3191
3792
|
return attachRootControls(api, instance, devtools, queryClient);
|
|
3192
3793
|
}
|
|
@@ -3337,6 +3938,12 @@ Object.defineProperty(exports, "signal", {
|
|
|
3337
3938
|
return signal;
|
|
3338
3939
|
}
|
|
3339
3940
|
});
|
|
3941
|
+
Object.defineProperty(exports, "stableHash", {
|
|
3942
|
+
enumerable: true,
|
|
3943
|
+
get: function() {
|
|
3944
|
+
return stableHash;
|
|
3945
|
+
}
|
|
3946
|
+
});
|
|
3340
3947
|
Object.defineProperty(exports, "untracked", {
|
|
3341
3948
|
enumerable: true,
|
|
3342
3949
|
get: function() {
|
|
@@ -3344,4 +3951,4 @@ Object.defineProperty(exports, "untracked", {
|
|
|
3344
3951
|
}
|
|
3345
3952
|
});
|
|
3346
3953
|
|
|
3347
|
-
//# sourceMappingURL=root-
|
|
3954
|
+
//# sourceMappingURL=root-XKEsSmcd.cjs.map
|