@kontsedal/olas-core 0.0.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.
@@ -212,14 +212,16 @@ function untracked(fn) {
212
212
  //#endregion
213
213
  //#region src/utils.ts
214
214
  /**
215
- * True iff `err` is an AbortError. Used to filter superseded latest-wins
216
- * mutations and aborted fetches from genuine failures.
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 checks `err instanceof DOMException && err.name === 'AbortError'`.
219
- * Node 17+ exposes a global DOMException, so this works server-side too.
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
  /**
@@ -1630,12 +1632,25 @@ function waitUntilFalse(sig) {
1630
1632
  //#endregion
1631
1633
  //#region src/emitter.ts
1632
1634
  var EmitterImpl = class {
1635
+ onError;
1633
1636
  handlers = /* @__PURE__ */ new Set();
1634
1637
  disposed = false;
1638
+ constructor(onError) {
1639
+ this.onError = onError;
1640
+ }
1635
1641
  emit(value) {
1636
1642
  if (this.disposed) return;
1637
1643
  const snapshot = Array.from(this.handlers);
1638
- for (const handler of snapshot) handler(value);
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
+ }
1639
1654
  }
1640
1655
  on(handler) {
1641
1656
  if (this.disposed) return () => {};
@@ -1667,9 +1682,14 @@ var EmitterImpl = class {
1667
1682
  * (or the emitter is disposed). Use this for emitters that live outside any
1668
1683
  * single controller — typically in deps. Use `ctx.emitter()` for emitters that
1669
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`.
1670
1690
  */
1671
- function createEmitter() {
1672
- const impl = new EmitterImpl();
1691
+ function createEmitter(options) {
1692
+ const impl = new EmitterImpl(options?.onError);
1673
1693
  return {
1674
1694
  emit: ((value) => impl.emit(value)),
1675
1695
  on: (handler) => impl.on(handler),
@@ -2034,8 +2054,23 @@ var FormImpl = class {
2034
2054
  else child.set(val);
2035
2055
  else if (isFieldArray(child)) {
2036
2056
  const arr = child;
2037
- arr.clear();
2038
- for (const itemVal of val) arr.add(itemVal);
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
+ }
2039
2074
  } else {
2040
2075
  const f = child;
2041
2076
  if (asInitial) f.setAsInitial(val);
@@ -2283,6 +2318,15 @@ var FieldArrayImpl = class {
2283
2318
  for (const item of this.items$.peek()) item.dispose?.();
2284
2319
  this.items$.set([]);
2285
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
+ }
2286
2330
  reset() {
2287
2331
  if (this.disposed) return;
2288
2332
  batch(() => {
@@ -3228,7 +3272,12 @@ var ControllerInstance = class ControllerInstance {
3228
3272
  return m;
3229
3273
  },
3230
3274
  emitter() {
3231
- const e = createEmitter();
3275
+ const e = createEmitter({ onError: (err) => {
3276
+ dispatchError(self.rootShared.onError, err, {
3277
+ kind: "emitter",
3278
+ controllerPath: self.path
3279
+ });
3280
+ } });
3232
3281
  self.entries.push({
3233
3282
  kind: "cleanup",
3234
3283
  dispose: () => e.dispose()
@@ -3395,6 +3444,255 @@ var ControllerInstance = class ControllerInstance {
3395
3444
  }
3396
3445
  };
3397
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
+ },
3398
3696
  onDispose(fn) {
3399
3697
  self.entries.push({
3400
3698
  kind: "onDispose",
@@ -3653,4 +3951,4 @@ Object.defineProperty(exports, "untracked", {
3653
3951
  }
3654
3952
  });
3655
3953
 
3656
- //# sourceMappingURL=root-DXV1gVbQ.cjs.map
3954
+ //# sourceMappingURL=root-XKEsSmcd.cjs.map