@kontsedal/olas-core 0.0.1 → 0.0.3
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 +72 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -12
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +72 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +71 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{root-BCZDC5Fv.mjs → root-Cnkb3I--.mjs} +556 -28
- package/dist/root-Cnkb3I--.mjs.map +1 -0
- package/dist/{root-DXV1gVbQ.cjs → root-D_xAdcom.cjs} +556 -28
- package/dist/root-D_xAdcom.cjs.map +1 -0
- package/dist/testing.cjs +2 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -1
- package/dist/testing.d.cts.map +1 -1
- package/dist/testing.d.mts +2 -1
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.mjs +2 -1
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-CffZ1QXt.d.cts → types-CRn4UoLn.d.mts} +196 -8
- package/dist/types-CRn4UoLn.d.mts.map +1 -0
- package/dist/{types-DSlDowpE.d.mts → types-r_TVaRkD.d.cts} +196 -8
- package/dist/types-r_TVaRkD.d.cts.map +1 -0
- package/package.json +1 -1
- package/src/controller/index.ts +6 -0
- package/src/controller/instance.ts +317 -3
- package/src/controller/types.ts +151 -0
- package/src/emitter.ts +34 -3
- package/src/forms/field.ts +42 -9
- package/src/forms/form-types.ts +37 -0
- package/src/forms/form.ts +165 -5
- package/src/forms/index.ts +12 -1
- package/src/forms/standard-schema.ts +37 -0
- package/src/forms/validators.ts +31 -0
- package/src/index.ts +20 -4
- package/src/query/entry.ts +10 -3
- package/src/query/infinite.ts +8 -1
- package/src/query/structural-share.ts +114 -0
- package/src/query/types.ts +15 -2
- package/src/query/use.ts +47 -13
- package/src/signals/readonly.ts +3 -3
- package/src/testing.ts +2 -0
- package/src/timing/debounced.ts +24 -4
- package/src/timing/throttled.ts +22 -3
- package/src/utils.ts +8 -4
- package/dist/root-BCZDC5Fv.mjs.map +0 -1
- package/dist/root-DXV1gVbQ.cjs.map +0 -1
- package/dist/types-CffZ1QXt.d.cts.map +0 -1
- package/dist/types-DSlDowpE.d.mts.map +0 -1
|
@@ -212,14 +212,16 @@ function untracked$1(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
|
}
|
|
225
227
|
/**
|
|
@@ -246,6 +248,83 @@ function abortableSleep(ms, signal) {
|
|
|
246
248
|
});
|
|
247
249
|
}
|
|
248
250
|
//#endregion
|
|
251
|
+
//#region src/query/structural-share.ts
|
|
252
|
+
/**
|
|
253
|
+
* Walk `prev` and `next` in parallel. Wherever a sub-tree in `next` is
|
|
254
|
+
* structurally equal to the corresponding sub-tree in `prev`, return `prev`'s
|
|
255
|
+
* reference for that sub-tree. Otherwise return `next`'s.
|
|
256
|
+
*
|
|
257
|
+
* Result: a value that is `===` to `prev` on every refetch where the payload
|
|
258
|
+
* didn't actually change, and shares maximum ref-identity on partial changes.
|
|
259
|
+
* Downstream `computed`s and React `useSyncExternalStore` snapshots stop
|
|
260
|
+
* thrashing because reference equality holds where content equality holds.
|
|
261
|
+
*
|
|
262
|
+
* Bails (returns the `next` ref unchanged, no recursion) on:
|
|
263
|
+
* - Mismatched constructors / different `typeof` between `prev` and `next`
|
|
264
|
+
* - `Map`, `Set`, `Date`, `RegExp`, class instances (anything where the
|
|
265
|
+
* plain-object / array fast path isn't safe)
|
|
266
|
+
* - Functions, symbols, promises
|
|
267
|
+
*
|
|
268
|
+
* Handles cycles via a `WeakSet` of in-progress objects — a self-referential
|
|
269
|
+
* payload that compares structurally identical against itself won't loop.
|
|
270
|
+
*/
|
|
271
|
+
function structuralShare(prev, next) {
|
|
272
|
+
if (Object.is(prev, next)) return prev;
|
|
273
|
+
return walk(prev, next, /* @__PURE__ */ new WeakSet());
|
|
274
|
+
}
|
|
275
|
+
function walk(prev, next, seen) {
|
|
276
|
+
if (Object.is(prev, next)) return prev;
|
|
277
|
+
if (prev === null || next === null) return next;
|
|
278
|
+
if (typeof prev !== "object" || typeof next !== "object") return next;
|
|
279
|
+
if (seen.has(prev) || seen.has(next)) return next;
|
|
280
|
+
if (Array.isArray(prev) && Array.isArray(next)) return walkArray(prev, next, seen);
|
|
281
|
+
if (Array.isArray(prev) !== Array.isArray(next)) return next;
|
|
282
|
+
const prevProto = Object.getPrototypeOf(prev);
|
|
283
|
+
if (prevProto !== Object.getPrototypeOf(next)) return next;
|
|
284
|
+
if (prevProto !== Object.prototype && prevProto !== null) return next;
|
|
285
|
+
return walkPlainObject(prev, next, seen);
|
|
286
|
+
}
|
|
287
|
+
function walkArray(prev, next, seen) {
|
|
288
|
+
if (prev.length !== next.length) {}
|
|
289
|
+
seen.add(prev);
|
|
290
|
+
seen.add(next);
|
|
291
|
+
try {
|
|
292
|
+
const out = new Array(next.length);
|
|
293
|
+
let changed = next.length !== prev.length;
|
|
294
|
+
for (let i = 0; i < next.length; i++) {
|
|
295
|
+
const shared = walk(i < prev.length ? prev[i] : void 0, next[i], seen);
|
|
296
|
+
out[i] = shared;
|
|
297
|
+
if (shared !== prev[i]) changed = true;
|
|
298
|
+
}
|
|
299
|
+
if (!changed) return prev;
|
|
300
|
+
return out;
|
|
301
|
+
} finally {
|
|
302
|
+
seen.delete(prev);
|
|
303
|
+
seen.delete(next);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function walkPlainObject(prev, next, seen) {
|
|
307
|
+
const prevKeys = Object.keys(prev);
|
|
308
|
+
const nextKeys = Object.keys(next);
|
|
309
|
+
let changed = prevKeys.length !== nextKeys.length;
|
|
310
|
+
seen.add(prev);
|
|
311
|
+
seen.add(next);
|
|
312
|
+
try {
|
|
313
|
+
const out = {};
|
|
314
|
+
for (const key of nextKeys) {
|
|
315
|
+
const shared = walk(prev[key], next[key], seen);
|
|
316
|
+
out[key] = shared;
|
|
317
|
+
if (shared !== prev[key]) changed = true;
|
|
318
|
+
else if (!(key in prev)) changed = true;
|
|
319
|
+
}
|
|
320
|
+
if (!changed) return prev;
|
|
321
|
+
return out;
|
|
322
|
+
} finally {
|
|
323
|
+
seen.delete(prev);
|
|
324
|
+
seen.delete(next);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
//#endregion
|
|
249
328
|
//#region src/query/entry.ts
|
|
250
329
|
/**
|
|
251
330
|
* One cache entry's state machine. Owns the AsyncState signals, race
|
|
@@ -352,8 +431,10 @@ var Entry = class {
|
|
|
352
431
|
return typeof d === "function" ? d(attempt) : d;
|
|
353
432
|
}
|
|
354
433
|
applySuccess(result) {
|
|
434
|
+
const prev = this.data.peek();
|
|
435
|
+
const shared = prev === void 0 ? result : structuralShare(prev, result);
|
|
355
436
|
batch$1(() => {
|
|
356
|
-
this.data.set(
|
|
437
|
+
this.data.set(shared);
|
|
357
438
|
this.error.set(void 0);
|
|
358
439
|
this.status.set("success");
|
|
359
440
|
this.isLoading.set(false);
|
|
@@ -365,8 +446,8 @@ var Entry = class {
|
|
|
365
446
|
try {
|
|
366
447
|
this.events.onFetchSuccess?.(Date.now() - this.fetchStartTime);
|
|
367
448
|
} catch {}
|
|
368
|
-
this.onSuccessData?.(
|
|
369
|
-
return
|
|
449
|
+
this.onSuccessData?.(shared);
|
|
450
|
+
return shared;
|
|
370
451
|
}
|
|
371
452
|
applyFailure(err) {
|
|
372
453
|
batch$1(() => {
|
|
@@ -634,8 +715,10 @@ var InfiniteEntry = class {
|
|
|
634
715
|
});
|
|
635
716
|
return this.runFetch(myId, abort.signal, this.initialPageParam, (page, param) => {
|
|
636
717
|
if (myId !== this.currentFetchId || this.disposed) return;
|
|
718
|
+
const prevPages = this.pages.peek();
|
|
719
|
+
const sharedPage = prevPages.length > 0 ? structuralShare(prevPages[0], page) : page;
|
|
637
720
|
batch$1(() => {
|
|
638
|
-
this.pages.set([
|
|
721
|
+
this.pages.set([sharedPage]);
|
|
639
722
|
this.pageParams.set([param]);
|
|
640
723
|
this.error.set(void 0);
|
|
641
724
|
this.status.set("success");
|
|
@@ -1630,12 +1713,25 @@ function waitUntilFalse(sig) {
|
|
|
1630
1713
|
//#endregion
|
|
1631
1714
|
//#region src/emitter.ts
|
|
1632
1715
|
var EmitterImpl = class {
|
|
1716
|
+
onError;
|
|
1633
1717
|
handlers = /* @__PURE__ */ new Set();
|
|
1634
1718
|
disposed = false;
|
|
1719
|
+
constructor(onError) {
|
|
1720
|
+
this.onError = onError;
|
|
1721
|
+
}
|
|
1635
1722
|
emit(value) {
|
|
1636
1723
|
if (this.disposed) return;
|
|
1637
1724
|
const snapshot = Array.from(this.handlers);
|
|
1638
|
-
for (const handler of snapshot)
|
|
1725
|
+
for (const handler of snapshot) try {
|
|
1726
|
+
handler(value);
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
if (this.onError) try {
|
|
1729
|
+
this.onError(err);
|
|
1730
|
+
} catch {
|
|
1731
|
+
console.error("[olas] emitter handler threw and reporter threw:", err);
|
|
1732
|
+
}
|
|
1733
|
+
else console.error("[olas] emitter handler threw:", err);
|
|
1734
|
+
}
|
|
1639
1735
|
}
|
|
1640
1736
|
on(handler) {
|
|
1641
1737
|
if (this.disposed) return () => {};
|
|
@@ -1667,9 +1763,14 @@ var EmitterImpl = class {
|
|
|
1667
1763
|
* (or the emitter is disposed). Use this for emitters that live outside any
|
|
1668
1764
|
* single controller — typically in deps. Use `ctx.emitter()` for emitters that
|
|
1669
1765
|
* should auto-clean with a controller.
|
|
1766
|
+
*
|
|
1767
|
+
* Pass `onError` to receive emit-time handler throws (spec §20.6 — one
|
|
1768
|
+
* throwing handler must not block the rest of the fan-out). `ctx.emitter()`
|
|
1769
|
+
* wires this to the root's `onError` so deps-level emitters get isolation
|
|
1770
|
+
* by default when constructed via `ctx`.
|
|
1670
1771
|
*/
|
|
1671
|
-
function createEmitter() {
|
|
1672
|
-
const impl = new EmitterImpl();
|
|
1772
|
+
function createEmitter(options) {
|
|
1773
|
+
const impl = new EmitterImpl(options?.onError);
|
|
1673
1774
|
return {
|
|
1674
1775
|
emit: ((value) => impl.emit(value)),
|
|
1675
1776
|
on: (handler) => impl.on(handler),
|
|
@@ -1681,6 +1782,17 @@ function createEmitter() {
|
|
|
1681
1782
|
//#region src/forms/field.ts
|
|
1682
1783
|
var FieldImpl = class {
|
|
1683
1784
|
value$;
|
|
1785
|
+
/**
|
|
1786
|
+
* Validator-produced errors. The public `errors` getter merges this with
|
|
1787
|
+
* `serverErrors$` so consumers see a single flat array. Kept separate so a
|
|
1788
|
+
* re-run of validators (after a new value) doesn't clobber server errors.
|
|
1789
|
+
*/
|
|
1790
|
+
validatorErrors$;
|
|
1791
|
+
/**
|
|
1792
|
+
* Externally-injected errors — see `setErrors`. Cleared on the next user
|
|
1793
|
+
* `set()`, on `reset()`, or via an explicit `setErrors([])`.
|
|
1794
|
+
*/
|
|
1795
|
+
serverErrors$;
|
|
1684
1796
|
errors$;
|
|
1685
1797
|
touched$;
|
|
1686
1798
|
dirty$;
|
|
@@ -1702,11 +1814,19 @@ var FieldImpl = class {
|
|
|
1702
1814
|
this.validators = validators;
|
|
1703
1815
|
this.onValidatorError = options?.onValidatorError ?? null;
|
|
1704
1816
|
this.value$ = signal$1(initial);
|
|
1705
|
-
this.
|
|
1817
|
+
this.validatorErrors$ = signal$1([]);
|
|
1818
|
+
this.serverErrors$ = signal$1([]);
|
|
1706
1819
|
this.touched$ = signal$1(false);
|
|
1707
1820
|
this.dirty$ = signal$1(false);
|
|
1708
1821
|
this.validating$ = signal$1(false);
|
|
1709
1822
|
this.revalidateTrigger$ = signal$1(0);
|
|
1823
|
+
this.errors$ = computed$1(() => {
|
|
1824
|
+
const v = this.validatorErrors$.value;
|
|
1825
|
+
const s = this.serverErrors$.value;
|
|
1826
|
+
if (s.length === 0) return v;
|
|
1827
|
+
if (v.length === 0) return s;
|
|
1828
|
+
return [...v, ...s];
|
|
1829
|
+
});
|
|
1710
1830
|
this.isValid$ = computed$1(() => this.errors$.value.length === 0 && !this.validating$.value);
|
|
1711
1831
|
if (validators.length > 0) this.validatorDispose = effect$1(() => {
|
|
1712
1832
|
this.runValidators();
|
|
@@ -1745,8 +1865,16 @@ var FieldImpl = class {
|
|
|
1745
1865
|
}
|
|
1746
1866
|
set(value) {
|
|
1747
1867
|
if (this.disposed) return;
|
|
1748
|
-
|
|
1749
|
-
|
|
1868
|
+
batch$1(() => {
|
|
1869
|
+
this.value$.set(value);
|
|
1870
|
+
this.dirty$.set(true);
|
|
1871
|
+
if (this.serverErrors$.peek().length > 0) this.serverErrors$.set([]);
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
setErrors(errors) {
|
|
1875
|
+
if (this.disposed) return;
|
|
1876
|
+
const next = errors.length === 0 ? [] : [...errors];
|
|
1877
|
+
this.serverErrors$.set(next);
|
|
1750
1878
|
}
|
|
1751
1879
|
/**
|
|
1752
1880
|
* Reseat the field as if this value had been its constructor `initial`.
|
|
@@ -1771,7 +1899,8 @@ var FieldImpl = class {
|
|
|
1771
1899
|
this.value$.set(this.initial);
|
|
1772
1900
|
this.dirty$.set(false);
|
|
1773
1901
|
this.touched$.set(false);
|
|
1774
|
-
this.
|
|
1902
|
+
this.validatorErrors$.set([]);
|
|
1903
|
+
this.serverErrors$.set([]);
|
|
1775
1904
|
this.validating$.set(false);
|
|
1776
1905
|
});
|
|
1777
1906
|
}
|
|
@@ -1837,7 +1966,7 @@ var FieldImpl = class {
|
|
|
1837
1966
|
}
|
|
1838
1967
|
if (syncErrors.length > 0) {
|
|
1839
1968
|
batch$1(() => {
|
|
1840
|
-
this.
|
|
1969
|
+
this.validatorErrors$.set(syncErrors);
|
|
1841
1970
|
this.validating$.set(false);
|
|
1842
1971
|
});
|
|
1843
1972
|
this.emitValidated(false, syncErrors);
|
|
@@ -1845,14 +1974,14 @@ var FieldImpl = class {
|
|
|
1845
1974
|
}
|
|
1846
1975
|
if (asyncPromises.length === 0) {
|
|
1847
1976
|
batch$1(() => {
|
|
1848
|
-
this.
|
|
1977
|
+
this.validatorErrors$.set([]);
|
|
1849
1978
|
this.validating$.set(false);
|
|
1850
1979
|
});
|
|
1851
1980
|
this.emitValidated(true, []);
|
|
1852
1981
|
return;
|
|
1853
1982
|
}
|
|
1854
1983
|
batch$1(() => {
|
|
1855
|
-
this.
|
|
1984
|
+
this.validatorErrors$.set([]);
|
|
1856
1985
|
this.validating$.set(true);
|
|
1857
1986
|
});
|
|
1858
1987
|
Promise.allSettled(asyncPromises).then((results) => {
|
|
@@ -1865,7 +1994,7 @@ var FieldImpl = class {
|
|
|
1865
1994
|
asyncErrors.push(msg);
|
|
1866
1995
|
}
|
|
1867
1996
|
batch$1(() => {
|
|
1868
|
-
this.
|
|
1997
|
+
this.validatorErrors$.set(asyncErrors);
|
|
1869
1998
|
this.validating$.set(false);
|
|
1870
1999
|
});
|
|
1871
2000
|
this.emitValidated(asyncErrors.length === 0, asyncErrors);
|
|
@@ -1936,6 +2065,12 @@ var FormImpl = class {
|
|
|
1936
2065
|
topLevelErrors$ = signal$1([]);
|
|
1937
2066
|
topLevelErrors = this.topLevelErrors$;
|
|
1938
2067
|
topLevelValidating$ = signal$1(false);
|
|
2068
|
+
isSubmitting$ = signal$1(false);
|
|
2069
|
+
submitCount$ = signal$1(0);
|
|
2070
|
+
submitError$ = signal$1(void 0);
|
|
2071
|
+
isSubmitting = this.isSubmitting$;
|
|
2072
|
+
submitCount = this.submitCount$;
|
|
2073
|
+
submitError = this.submitError$;
|
|
1939
2074
|
validators;
|
|
1940
2075
|
options;
|
|
1941
2076
|
validatorDispose = null;
|
|
@@ -2034,8 +2169,23 @@ var FormImpl = class {
|
|
|
2034
2169
|
else child.set(val);
|
|
2035
2170
|
else if (isFieldArray(child)) {
|
|
2036
2171
|
const arr = child;
|
|
2037
|
-
|
|
2038
|
-
|
|
2172
|
+
const newValues = val;
|
|
2173
|
+
if (asInitial) {
|
|
2174
|
+
arr.clear();
|
|
2175
|
+
for (const itemVal of newValues) arr.add(itemVal);
|
|
2176
|
+
arr.replaceInitialItems(newValues);
|
|
2177
|
+
} else {
|
|
2178
|
+
const current = arr.items.peek();
|
|
2179
|
+
const overlap = Math.min(current.length, newValues.length);
|
|
2180
|
+
for (let i = 0; i < overlap; i++) {
|
|
2181
|
+
const item = current[i];
|
|
2182
|
+
const v = newValues[i];
|
|
2183
|
+
if (isForm(item)) item.set(v);
|
|
2184
|
+
else item.set(v);
|
|
2185
|
+
}
|
|
2186
|
+
for (let i = current.length; i < newValues.length; i++) arr.add(newValues[i]);
|
|
2187
|
+
for (let i = current.length - 1; i >= newValues.length; i--) arr.remove(i);
|
|
2188
|
+
}
|
|
2039
2189
|
} else {
|
|
2040
2190
|
const f = child;
|
|
2041
2191
|
if (asInitial) f.setAsInitial(val);
|
|
@@ -2083,6 +2233,107 @@ var FormImpl = class {
|
|
|
2083
2233
|
});
|
|
2084
2234
|
return this.isValid.peek();
|
|
2085
2235
|
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Run a submission against this form. Wraps `handler(value)` with:
|
|
2238
|
+
* - `isSubmitting` set true while the handler is in flight.
|
|
2239
|
+
* - `submitCount` incremented before the handler runs.
|
|
2240
|
+
* - `submitError` set to the throw, if any.
|
|
2241
|
+
* - Optional pre-submit `validate()` (default true). When invalid every
|
|
2242
|
+
* field is marked touched and the handler is skipped — the returned
|
|
2243
|
+
* promise resolves with `{ ok: false }` and `submitError` is left
|
|
2244
|
+
* untouched (validation failure is not a thrown error).
|
|
2245
|
+
*
|
|
2246
|
+
* The handler may return a value (synchronously or via Promise); it's
|
|
2247
|
+
* captured in the resolved object's `data` field. Throws are captured
|
|
2248
|
+
* unless `onError: 'rethrow'`. A `resetOnSuccess: true` option calls
|
|
2249
|
+
* `reset()` after the handler resolves successfully.
|
|
2250
|
+
*/
|
|
2251
|
+
async submit(handler, options) {
|
|
2252
|
+
if (this.disposed) return {
|
|
2253
|
+
ok: false,
|
|
2254
|
+
error: /* @__PURE__ */ new Error("form is disposed")
|
|
2255
|
+
};
|
|
2256
|
+
if (this.isSubmitting$.peek()) return {
|
|
2257
|
+
ok: false,
|
|
2258
|
+
error: /* @__PURE__ */ new Error("submit already in progress")
|
|
2259
|
+
};
|
|
2260
|
+
const validateFirst = options?.validateBeforeSubmit ?? true;
|
|
2261
|
+
const onErrorMode = options?.onError ?? "capture";
|
|
2262
|
+
batch$1(() => {
|
|
2263
|
+
this.submitCount$.update((n) => n + 1);
|
|
2264
|
+
this.submitError$.set(void 0);
|
|
2265
|
+
this.isSubmitting$.set(true);
|
|
2266
|
+
});
|
|
2267
|
+
try {
|
|
2268
|
+
if (validateFirst) {
|
|
2269
|
+
if (!await this.validate()) {
|
|
2270
|
+
this.markAllTouched();
|
|
2271
|
+
this.isSubmitting$.set(false);
|
|
2272
|
+
return { ok: false };
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
const result = await handler(this.value.peek());
|
|
2276
|
+
if (options?.resetOnSuccess) this.reset();
|
|
2277
|
+
this.isSubmitting$.set(false);
|
|
2278
|
+
return {
|
|
2279
|
+
ok: true,
|
|
2280
|
+
data: result
|
|
2281
|
+
};
|
|
2282
|
+
} catch (err) {
|
|
2283
|
+
batch$1(() => {
|
|
2284
|
+
this.submitError$.set(err);
|
|
2285
|
+
this.isSubmitting$.set(false);
|
|
2286
|
+
});
|
|
2287
|
+
if (onErrorMode === "rethrow") throw err;
|
|
2288
|
+
return {
|
|
2289
|
+
ok: false,
|
|
2290
|
+
error: err
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Pin externally-sourced errors on specific fields — typically server-side
|
|
2296
|
+
* validation results from a failed submit. Paths are dot-separated and
|
|
2297
|
+
* traverse nested `Form` / `FieldArray` children (numeric segments are
|
|
2298
|
+
* array indices). Errors land in the field's `serverErrors` channel and
|
|
2299
|
+
* clear automatically on the next user write to that field. Passing an
|
|
2300
|
+
* empty array for a path clears that field's server errors immediately.
|
|
2301
|
+
*/
|
|
2302
|
+
setErrors(errors) {
|
|
2303
|
+
if (this.disposed) return;
|
|
2304
|
+
batch$1(() => {
|
|
2305
|
+
for (const [path, msgs] of Object.entries(errors)) {
|
|
2306
|
+
const target = this.resolvePath(path);
|
|
2307
|
+
if (target === void 0) continue;
|
|
2308
|
+
if (target.setErrors === void 0) continue;
|
|
2309
|
+
target.setErrors(msgs);
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
resolvePath(path) {
|
|
2314
|
+
if (path === "") return void 0;
|
|
2315
|
+
const segments = path.split(".");
|
|
2316
|
+
let cursor = this;
|
|
2317
|
+
for (const seg of segments) {
|
|
2318
|
+
if (cursor === void 0 || cursor === null) return void 0;
|
|
2319
|
+
if (isForm(cursor)) {
|
|
2320
|
+
cursor = cursor.fields[seg];
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
if (isFieldArray(cursor)) {
|
|
2324
|
+
const idx = Number(seg);
|
|
2325
|
+
if (!Number.isInteger(idx) || idx < 0) return void 0;
|
|
2326
|
+
cursor = cursor.at(idx);
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
if (cursor === this) {
|
|
2330
|
+
cursor = this.fields[seg];
|
|
2331
|
+
continue;
|
|
2332
|
+
}
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
return cursor;
|
|
2336
|
+
}
|
|
2086
2337
|
dispose() {
|
|
2087
2338
|
if (this.disposed) return;
|
|
2088
2339
|
this.disposed = true;
|
|
@@ -2283,6 +2534,15 @@ var FieldArrayImpl = class {
|
|
|
2283
2534
|
for (const item of this.items$.peek()) item.dispose?.();
|
|
2284
2535
|
this.items$.set([]);
|
|
2285
2536
|
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Internal — used by `Form.resetWithInitial` to re-anchor the array's
|
|
2539
|
+
* initial items after a parent-driven `applyPartial(..., asInitial: true)`.
|
|
2540
|
+
* Without this, a subsequent `reset()` would revert to the construction-
|
|
2541
|
+
* time initials rather than the most-recently-applied ones.
|
|
2542
|
+
*/
|
|
2543
|
+
replaceInitialItems(items) {
|
|
2544
|
+
this.initialItems = [...items];
|
|
2545
|
+
}
|
|
2286
2546
|
reset() {
|
|
2287
2547
|
if (this.disposed) return;
|
|
2288
2548
|
batch$1(() => {
|
|
@@ -2735,6 +2995,7 @@ function raceAbort(promise, signal) {
|
|
|
2735
2995
|
//#region src/query/use.ts
|
|
2736
2996
|
var SubscriptionImpl = class {
|
|
2737
2997
|
keepPreviousData;
|
|
2998
|
+
select;
|
|
2738
2999
|
current$ = signal$1(null);
|
|
2739
3000
|
previousData$ = signal$1(void 0);
|
|
2740
3001
|
data;
|
|
@@ -2745,13 +3006,18 @@ var SubscriptionImpl = class {
|
|
|
2745
3006
|
isStale;
|
|
2746
3007
|
lastUpdatedAt;
|
|
2747
3008
|
hasPendingMutations;
|
|
2748
|
-
constructor(keepPreviousData) {
|
|
3009
|
+
constructor(keepPreviousData, select) {
|
|
2749
3010
|
this.keepPreviousData = keepPreviousData;
|
|
2750
|
-
this.
|
|
3011
|
+
this.select = select;
|
|
3012
|
+
const rawData = computed$1(() => {
|
|
2751
3013
|
const curData = this.current$.value?.entry.data.value;
|
|
2752
3014
|
if (curData !== void 0) return curData;
|
|
2753
3015
|
if (keepPreviousData) return this.previousData$.value;
|
|
2754
3016
|
});
|
|
3017
|
+
this.data = select === void 0 ? rawData : computed$1(() => {
|
|
3018
|
+
const raw = rawData.value;
|
|
3019
|
+
return raw === void 0 ? void 0 : select(raw);
|
|
3020
|
+
});
|
|
2755
3021
|
this.error = computed$1(() => this.current$.value?.entry.error.value);
|
|
2756
3022
|
this.status = computed$1(() => this.current$.value?.entry.status.value ?? "idle");
|
|
2757
3023
|
this.isLoading = computed$1(() => {
|
|
@@ -2780,7 +3046,7 @@ var SubscriptionImpl = class {
|
|
|
2780
3046
|
refetch = () => {
|
|
2781
3047
|
const cur = this.current$.peek();
|
|
2782
3048
|
if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
|
|
2783
|
-
return cur.entry.refetch();
|
|
3049
|
+
return cur.entry.refetch().then((v) => this.project(v));
|
|
2784
3050
|
};
|
|
2785
3051
|
reset = () => {
|
|
2786
3052
|
this.current$.peek()?.entry.reset();
|
|
@@ -2788,18 +3054,26 @@ var SubscriptionImpl = class {
|
|
|
2788
3054
|
firstValue = () => {
|
|
2789
3055
|
const cur = this.current$.peek();
|
|
2790
3056
|
if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
|
|
2791
|
-
return cur.entry.firstValue();
|
|
3057
|
+
return cur.entry.firstValue().then((v) => this.project(v));
|
|
2792
3058
|
};
|
|
3059
|
+
project(v) {
|
|
3060
|
+
return this.select === void 0 ? v : this.select(v);
|
|
3061
|
+
}
|
|
2793
3062
|
};
|
|
2794
3063
|
/**
|
|
2795
3064
|
* Build a subscription + the effect that keeps it bound to the right entry.
|
|
2796
3065
|
* The controller container wires the disposer into the lifecycle.
|
|
3066
|
+
*
|
|
3067
|
+
* `keyOrOptions` may carry an optional `select` projection that maps the
|
|
3068
|
+
* underlying `T` to a view `U`; the returned subscription's data shape
|
|
3069
|
+
* widens accordingly. Without `select`, `U = T` and the projection
|
|
3070
|
+
* computed is skipped.
|
|
2797
3071
|
*/
|
|
2798
3072
|
function createUse(client, query, keyOrOptions) {
|
|
2799
3073
|
const keepPreviousData = query.__spec.keepPreviousData ?? false;
|
|
2800
3074
|
const keyFn = typeof keyOrOptions === "function" ? keyOrOptions : keyOrOptions?.key;
|
|
2801
3075
|
const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
|
|
2802
|
-
const sub = new SubscriptionImpl(keepPreviousData);
|
|
3076
|
+
const sub = new SubscriptionImpl(keepPreviousData, typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.select : void 0);
|
|
2803
3077
|
let currentEntry = null;
|
|
2804
3078
|
let suspended = false;
|
|
2805
3079
|
const effectDispose = effect$1(() => {
|
|
@@ -3228,7 +3502,12 @@ var ControllerInstance = class ControllerInstance {
|
|
|
3228
3502
|
return m;
|
|
3229
3503
|
},
|
|
3230
3504
|
emitter() {
|
|
3231
|
-
const e = createEmitter()
|
|
3505
|
+
const e = createEmitter({ onError: (err) => {
|
|
3506
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3507
|
+
kind: "emitter",
|
|
3508
|
+
controllerPath: self.path
|
|
3509
|
+
});
|
|
3510
|
+
} });
|
|
3232
3511
|
self.entries.push({
|
|
3233
3512
|
kind: "cleanup",
|
|
3234
3513
|
dispose: () => e.dispose()
|
|
@@ -3395,6 +3674,255 @@ var ControllerInstance = class ControllerInstance {
|
|
|
3395
3674
|
}
|
|
3396
3675
|
};
|
|
3397
3676
|
},
|
|
3677
|
+
session(def, props, options) {
|
|
3678
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3679
|
+
const override = options?.deps;
|
|
3680
|
+
const childDeps = override !== void 0 ? {
|
|
3681
|
+
...self.deps,
|
|
3682
|
+
...override
|
|
3683
|
+
} : self.deps;
|
|
3684
|
+
const childInstance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3685
|
+
const api = childInstance.construct(getFactory(def), props);
|
|
3686
|
+
const entry = {
|
|
3687
|
+
kind: "child",
|
|
3688
|
+
instance: childInstance
|
|
3689
|
+
};
|
|
3690
|
+
self.entries.push(entry);
|
|
3691
|
+
let disposed = false;
|
|
3692
|
+
const dispose = () => {
|
|
3693
|
+
if (disposed) return;
|
|
3694
|
+
disposed = true;
|
|
3695
|
+
const idx = self.entries.indexOf(entry);
|
|
3696
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3697
|
+
try {
|
|
3698
|
+
childInstance.dispose();
|
|
3699
|
+
} catch (err) {
|
|
3700
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3701
|
+
kind: "effect",
|
|
3702
|
+
controllerPath: self.path
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
};
|
|
3706
|
+
return [api, dispose];
|
|
3707
|
+
},
|
|
3708
|
+
collection(options) {
|
|
3709
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
3710
|
+
const items$ = signal$1([]);
|
|
3711
|
+
const size$ = computed$1(() => items$.value.length);
|
|
3712
|
+
const isFactoryForm = options.factory !== void 0;
|
|
3713
|
+
const buildChild = (item) => {
|
|
3714
|
+
let def;
|
|
3715
|
+
let childProps;
|
|
3716
|
+
if (isFactoryForm) {
|
|
3717
|
+
const result = options.factory(item);
|
|
3718
|
+
def = result.controller;
|
|
3719
|
+
childProps = result.props;
|
|
3720
|
+
} else {
|
|
3721
|
+
const homoOpts = options;
|
|
3722
|
+
def = homoOpts.controller;
|
|
3723
|
+
childProps = homoOpts.propsOf(item);
|
|
3724
|
+
}
|
|
3725
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3726
|
+
const childDeps = options.deps !== void 0 ? {
|
|
3727
|
+
...self.deps,
|
|
3728
|
+
...options.deps
|
|
3729
|
+
} : self.deps;
|
|
3730
|
+
const instance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3731
|
+
try {
|
|
3732
|
+
return {
|
|
3733
|
+
instance,
|
|
3734
|
+
api: instance.construct(getFactory(def), childProps),
|
|
3735
|
+
def
|
|
3736
|
+
};
|
|
3737
|
+
} catch (err) {
|
|
3738
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3739
|
+
kind: "construction",
|
|
3740
|
+
controllerPath: self.path
|
|
3741
|
+
});
|
|
3742
|
+
return null;
|
|
3743
|
+
}
|
|
3744
|
+
};
|
|
3745
|
+
const removeKey = (key) => {
|
|
3746
|
+
const info = childMap.get(key);
|
|
3747
|
+
if (info === void 0) return;
|
|
3748
|
+
childMap.delete(key);
|
|
3749
|
+
const idx = self.entries.indexOf(info.entry);
|
|
3750
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3751
|
+
try {
|
|
3752
|
+
info.instance.dispose();
|
|
3753
|
+
} catch (err) {
|
|
3754
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3755
|
+
kind: "effect",
|
|
3756
|
+
controllerPath: self.path
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
const reconcile = () => {
|
|
3761
|
+
const source = options.source.value;
|
|
3762
|
+
const itemByKey = /* @__PURE__ */ new Map();
|
|
3763
|
+
for (const item of source) {
|
|
3764
|
+
const key = options.keyOf(item);
|
|
3765
|
+
if (!itemByKey.has(key)) itemByKey.set(key, item);
|
|
3766
|
+
}
|
|
3767
|
+
for (const key of [...childMap.keys()]) if (!itemByKey.has(key)) removeKey(key);
|
|
3768
|
+
for (const [key, item] of itemByKey) {
|
|
3769
|
+
const existing = childMap.get(key);
|
|
3770
|
+
if (existing !== void 0) {
|
|
3771
|
+
if (isFactoryForm) {
|
|
3772
|
+
if (options.factory(item).controller !== existing.def) {
|
|
3773
|
+
removeKey(key);
|
|
3774
|
+
const built = buildChild(item);
|
|
3775
|
+
if (built !== null) {
|
|
3776
|
+
const entry = {
|
|
3777
|
+
kind: "child",
|
|
3778
|
+
instance: built.instance
|
|
3779
|
+
};
|
|
3780
|
+
self.entries.push(entry);
|
|
3781
|
+
childMap.set(key, {
|
|
3782
|
+
...built,
|
|
3783
|
+
entry
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
const built = buildChild(item);
|
|
3791
|
+
if (built !== null) {
|
|
3792
|
+
const entry = {
|
|
3793
|
+
kind: "child",
|
|
3794
|
+
instance: built.instance
|
|
3795
|
+
};
|
|
3796
|
+
self.entries.push(entry);
|
|
3797
|
+
childMap.set(key, {
|
|
3798
|
+
...built,
|
|
3799
|
+
entry
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
const next = [];
|
|
3804
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3805
|
+
for (const item of source) {
|
|
3806
|
+
const key = options.keyOf(item);
|
|
3807
|
+
if (seen.has(key)) continue;
|
|
3808
|
+
seen.add(key);
|
|
3809
|
+
const info = childMap.get(key);
|
|
3810
|
+
if (info !== void 0) next.push({
|
|
3811
|
+
key,
|
|
3812
|
+
api: info.api
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
items$.set(next);
|
|
3816
|
+
};
|
|
3817
|
+
const wrapped = () => {
|
|
3818
|
+
try {
|
|
3819
|
+
reconcile();
|
|
3820
|
+
} catch (err) {
|
|
3821
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3822
|
+
kind: "effect",
|
|
3823
|
+
controllerPath: self.path
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
};
|
|
3827
|
+
const effectEntry = {
|
|
3828
|
+
kind: "effect",
|
|
3829
|
+
factory: wrapped,
|
|
3830
|
+
dispose: null
|
|
3831
|
+
};
|
|
3832
|
+
if (self.state !== "suspended") effectEntry.dispose = effect$1(wrapped);
|
|
3833
|
+
self.entries.push(effectEntry);
|
|
3834
|
+
return {
|
|
3835
|
+
items: items$,
|
|
3836
|
+
size: size$,
|
|
3837
|
+
get: (key) => childMap.get(key)?.api,
|
|
3838
|
+
has: (key) => childMap.has(key)
|
|
3839
|
+
};
|
|
3840
|
+
},
|
|
3841
|
+
lazyChild(loader, props, options) {
|
|
3842
|
+
const status$ = signal$1("idle");
|
|
3843
|
+
const api$ = signal$1(void 0);
|
|
3844
|
+
const error$ = signal$1(void 0);
|
|
3845
|
+
let childInstance = null;
|
|
3846
|
+
let childEntry = null;
|
|
3847
|
+
let pendingLoad = null;
|
|
3848
|
+
let disposed = false;
|
|
3849
|
+
const flagEntry = {
|
|
3850
|
+
kind: "onDispose",
|
|
3851
|
+
fn: () => {
|
|
3852
|
+
disposed = true;
|
|
3853
|
+
}
|
|
3854
|
+
};
|
|
3855
|
+
self.entries.push(flagEntry);
|
|
3856
|
+
const handleFailure = (err) => {
|
|
3857
|
+
status$.set("error");
|
|
3858
|
+
error$.set(err);
|
|
3859
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3860
|
+
kind: "construction",
|
|
3861
|
+
controllerPath: self.path
|
|
3862
|
+
});
|
|
3863
|
+
};
|
|
3864
|
+
const load = () => {
|
|
3865
|
+
if (disposed) return Promise.reject(/* @__PURE__ */ new Error("[olas] ctx.lazyChild: cannot load after dispose"));
|
|
3866
|
+
if (pendingLoad !== null) return pendingLoad;
|
|
3867
|
+
status$.set("loading");
|
|
3868
|
+
pendingLoad = loader().then((def) => {
|
|
3869
|
+
if (disposed) throw new Error("[olas] ctx.lazyChild: disposed during load");
|
|
3870
|
+
const segment = self.makeChildSegment(getFactory(def), getName(def));
|
|
3871
|
+
const childDeps = options?.deps !== void 0 ? {
|
|
3872
|
+
...self.deps,
|
|
3873
|
+
...options.deps
|
|
3874
|
+
} : self.deps;
|
|
3875
|
+
const instance = new ControllerInstance(self, self.rootShared, segment, childDeps);
|
|
3876
|
+
try {
|
|
3877
|
+
const api = instance.construct(getFactory(def), props);
|
|
3878
|
+
childInstance = instance;
|
|
3879
|
+
childEntry = {
|
|
3880
|
+
kind: "child",
|
|
3881
|
+
instance
|
|
3882
|
+
};
|
|
3883
|
+
self.entries.push(childEntry);
|
|
3884
|
+
api$.set(api);
|
|
3885
|
+
status$.set("ready");
|
|
3886
|
+
return api;
|
|
3887
|
+
} catch (err) {
|
|
3888
|
+
handleFailure(err);
|
|
3889
|
+
throw err;
|
|
3890
|
+
}
|
|
3891
|
+
}, (err) => {
|
|
3892
|
+
if (disposed) throw err;
|
|
3893
|
+
handleFailure(err);
|
|
3894
|
+
throw err;
|
|
3895
|
+
});
|
|
3896
|
+
return pendingLoad;
|
|
3897
|
+
};
|
|
3898
|
+
const dispose = () => {
|
|
3899
|
+
if (disposed) return;
|
|
3900
|
+
disposed = true;
|
|
3901
|
+
if (childEntry !== null && childInstance !== null) {
|
|
3902
|
+
const idx = self.entries.indexOf(childEntry);
|
|
3903
|
+
if (idx >= 0) self.entries.splice(idx, 1);
|
|
3904
|
+
try {
|
|
3905
|
+
childInstance.dispose();
|
|
3906
|
+
} catch (err) {
|
|
3907
|
+
dispatchError(self.rootShared.onError, err, {
|
|
3908
|
+
kind: "effect",
|
|
3909
|
+
controllerPath: self.path
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
childInstance = null;
|
|
3913
|
+
childEntry = null;
|
|
3914
|
+
}
|
|
3915
|
+
const flagIdx = self.entries.indexOf(flagEntry);
|
|
3916
|
+
if (flagIdx >= 0) self.entries.splice(flagIdx, 1);
|
|
3917
|
+
};
|
|
3918
|
+
return {
|
|
3919
|
+
status: status$,
|
|
3920
|
+
api: api$,
|
|
3921
|
+
error: error$,
|
|
3922
|
+
load,
|
|
3923
|
+
dispose
|
|
3924
|
+
};
|
|
3925
|
+
},
|
|
3398
3926
|
onDispose(fn) {
|
|
3399
3927
|
self.entries.push({
|
|
3400
3928
|
kind: "onDispose",
|
|
@@ -3570,4 +4098,4 @@ function createRoot(def, options) {
|
|
|
3570
4098
|
//#endregion
|
|
3571
4099
|
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 };
|
|
3572
4100
|
|
|
3573
|
-
//# sourceMappingURL=root-
|
|
4101
|
+
//# sourceMappingURL=root-Cnkb3I--.mjs.map
|