@pumped-fn/lite 2.1.0 → 2.1.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.mjs CHANGED
@@ -87,7 +87,7 @@ function getAtomsForTag(tag$1) {
87
87
  return live;
88
88
  }
89
89
  function tag(options) {
90
- const key = Symbol.for(`@pumped-fn/lite/tag/${options.label}`);
90
+ const key = Symbol(`@pumped-fn/lite/tag/${options.label}`);
91
91
  const hasDefault = "default" in options;
92
92
  const defaultValue = hasDefault ? options.default : void 0;
93
93
  const parse = options.parse;
@@ -265,7 +265,8 @@ function isAtom(value) {
265
265
  * @param options - Optional configuration:
266
266
  * - `resolve: true` — auto-resolves the dep before the parent factory runs; `config.get()` is safe.
267
267
  * - `watch: true` — atom deps only; requires `resolve: true`; automatically re-runs the parent factory
268
- * when the dep resolves to a new value (value-equality gated via `Object.is` by default). Replaces
268
+ * when the dep resolves to a new value (value-equality gated via plain-object `shallowEqual` by default,
269
+ * otherwise `Object.is`). Replaces
269
270
  * manual `ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))` wiring. Watch
270
271
  * listeners are auto-cleaned on re-resolve, release, and dispose.
271
272
  * - `eq` — custom equality function `(a: T, b: T) => boolean`; only used with `watch: true`.
@@ -424,12 +425,38 @@ function isResource(value) {
424
425
  //#endregion
425
426
  //#region src/service.ts
426
427
  function service(config) {
427
- return {
428
+ const atomInstance = {
428
429
  [atomSymbol]: true,
429
430
  factory: config.factory,
430
431
  deps: config.deps,
431
432
  tags: config.tags
432
433
  };
434
+ if (config.tags?.length) registerAtomToTags(atomInstance, config.tags);
435
+ return atomInstance;
436
+ }
437
+
438
+ //#endregion
439
+ //#region src/equality.ts
440
+ function isPlainObject(value) {
441
+ const prototype = Object.getPrototypeOf(value);
442
+ return prototype === Object.prototype || prototype === null;
443
+ }
444
+ function enumerableOwnKeys(value) {
445
+ return Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key));
446
+ }
447
+ function shallowEqual(a, b) {
448
+ if (Object.is(a, b)) return true;
449
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
450
+ if (!isPlainObject(a) || !isPlainObject(b)) return false;
451
+ const objA = a;
452
+ const objB = b;
453
+ const keysA = enumerableOwnKeys(objA);
454
+ if (keysA.length !== enumerableOwnKeys(objB).length) return false;
455
+ for (const key of keysA) {
456
+ if (!Object.hasOwn(objB, key)) return false;
457
+ if (!Object.is(objA[key], objB[key])) return false;
458
+ }
459
+ return true;
433
460
  }
434
461
 
435
462
  //#endregion
@@ -445,7 +472,7 @@ function getResourceKey(resource$1) {
445
472
  return key;
446
473
  }
447
474
  const inflightResources = /* @__PURE__ */ new WeakMap();
448
- const resolvingResources = /* @__PURE__ */ new Set();
475
+ const resolvingResourcesMap = /* @__PURE__ */ new WeakMap();
449
476
  var ContextDataImpl = class {
450
477
  map = /* @__PURE__ */ new Map();
451
478
  constructor(parentData) {
@@ -470,6 +497,10 @@ var ContextDataImpl = class {
470
497
  if (this.map.has(key)) return this.map.get(key);
471
498
  return this.parentData?.seek(key);
472
499
  }
500
+ seekHas(key) {
501
+ if (this.map.has(key)) return true;
502
+ return this.parentData?.seekHas(key) ?? false;
503
+ }
473
504
  getTag(tag$1) {
474
505
  return this.map.get(tag$1.key);
475
506
  }
@@ -515,6 +546,16 @@ var SelectHandleImpl = class {
515
546
  return this.currentValue;
516
547
  }
517
548
  subscribe(listener) {
549
+ if (!this.ctrlUnsub) {
550
+ this.currentValue = this.selector(this.ctrl.get());
551
+ this.ctrlUnsub = this.ctrl.on("resolved", () => {
552
+ const nextValue = this.selector(this.ctrl.get());
553
+ if (!this.eq(this.currentValue, nextValue)) {
554
+ this.currentValue = nextValue;
555
+ this.notifyListeners();
556
+ }
557
+ });
558
+ }
518
559
  this.listeners.add(listener);
519
560
  return () => {
520
561
  this.listeners.delete(listener);
@@ -522,12 +563,15 @@ var SelectHandleImpl = class {
522
563
  };
523
564
  }
524
565
  notifyListeners() {
525
- for (const listener of this.listeners) listener();
566
+ for (const listener of [...this.listeners]) listener();
567
+ }
568
+ dispose() {
569
+ this.listeners.clear();
570
+ this.cleanup();
526
571
  }
527
572
  cleanup() {
528
573
  this.ctrlUnsub?.();
529
574
  this.ctrlUnsub = null;
530
- this.listeners.clear();
531
575
  }
532
576
  };
533
577
  var ControllerImpl = class {
@@ -576,11 +620,15 @@ var ScopeImpl = class {
576
620
  invalidationScheduled = false;
577
621
  invalidationChain = null;
578
622
  chainPromise = null;
623
+ chainError = null;
579
624
  initialized = false;
625
+ disposed = false;
580
626
  controllers = /* @__PURE__ */ new Map();
581
627
  gcOptions;
582
628
  extensions;
583
629
  tags;
630
+ resolveExts;
631
+ execExts;
584
632
  ready;
585
633
  scheduleInvalidation(atom$1) {
586
634
  const entry = this.cache.get(atom$1);
@@ -593,16 +641,22 @@ var ScopeImpl = class {
593
641
  if (!this.chainPromise) {
594
642
  this.invalidationChain = /* @__PURE__ */ new Set();
595
643
  this.invalidationScheduled = true;
596
- this.chainPromise = new Promise((resolve, reject) => {
597
- queueMicrotask(() => {
598
- this.processInvalidationChain().then(resolve).catch(reject);
644
+ this.chainError = null;
645
+ this.chainPromise = (async () => {
646
+ await new Promise((resolve) => {
647
+ queueMicrotask(resolve);
599
648
  });
600
- });
649
+ try {
650
+ await this.processInvalidationChain();
651
+ } catch (error) {
652
+ if (this.chainError === null) this.chainError = error;
653
+ }
654
+ })();
601
655
  }
602
656
  }
603
657
  async processInvalidationChain() {
604
658
  try {
605
- while (this.invalidationQueue.size > 0) {
659
+ while (this.invalidationQueue.size > 0 && !this.disposed) {
606
660
  const atom$1 = this.invalidationQueue.values().next().value;
607
661
  this.invalidationQueue.delete(atom$1);
608
662
  if (this.invalidationChain.has(atom$1)) {
@@ -623,6 +677,8 @@ var ScopeImpl = class {
623
677
  constructor(options) {
624
678
  this.extensions = options?.extensions ?? [];
625
679
  this.tags = options?.tags ?? [];
680
+ this.resolveExts = this.extensions.filter((e) => e.wrapResolve);
681
+ this.execExts = this.extensions.filter((e) => e.wrapExec);
626
682
  for (const p of options?.presets ?? []) this.presets.set(p.target, p.value);
627
683
  this.gcOptions = {
628
684
  enabled: options?.gc?.enabled ?? true,
@@ -715,21 +771,21 @@ var ScopeImpl = class {
715
771
  const entry = this.cache.get(atom$1);
716
772
  if (!entry) return;
717
773
  const eventListeners = entry.listeners.get(event);
718
- if (eventListeners) for (const listener of eventListeners) listener();
774
+ if (eventListeners?.size) for (const listener of [...eventListeners]) listener();
719
775
  const allListeners = entry.listeners.get("*");
720
- if (allListeners) for (const listener of allListeners) listener();
776
+ if (allListeners?.size) for (const listener of [...allListeners]) listener();
721
777
  }
722
778
  notifyAllListeners(atom$1) {
723
779
  const entry = this.cache.get(atom$1);
724
780
  if (!entry) return;
725
781
  const allListeners = entry.listeners.get("*");
726
- if (allListeners) for (const listener of allListeners) listener();
782
+ if (allListeners?.size) for (const listener of [...allListeners]) listener();
727
783
  }
728
784
  emitStateChange(state, atom$1) {
729
785
  const stateMap = this.stateListeners.get(state);
730
786
  if (stateMap) {
731
787
  const listeners = stateMap.get(atom$1);
732
- if (listeners) for (const listener of listeners) listener();
788
+ if (listeners?.size) for (const listener of [...listeners]) listener();
733
789
  }
734
790
  }
735
791
  on(event, atom$1, listener) {
@@ -755,14 +811,15 @@ var ScopeImpl = class {
755
811
  };
756
812
  }
757
813
  async resolve(atom$1) {
814
+ if (this.disposed) throw new Error("Scope is disposed");
758
815
  if (!this.initialized) await this.ready;
759
816
  const entry = this.cache.get(atom$1);
760
817
  if (entry?.state === "resolved") return entry.value;
761
818
  const pendingPromise = this.pending.get(atom$1);
762
819
  if (pendingPromise) return pendingPromise;
763
820
  if (this.resolving.has(atom$1)) throw new Error("Circular dependency detected");
764
- const presetValue = this.presets.get(atom$1);
765
- if (presetValue !== void 0) {
821
+ if (this.presets.has(atom$1)) {
822
+ const presetValue = this.presets.get(atom$1);
766
823
  if (isAtom(presetValue)) return this.resolve(presetValue);
767
824
  const newEntry = this.getOrCreateEntry(atom$1);
768
825
  newEntry.state = "resolved";
@@ -785,7 +842,9 @@ var ScopeImpl = class {
785
842
  async doResolve(atom$1) {
786
843
  const entry = this.getOrCreateEntry(atom$1);
787
844
  if (!(entry.state === "resolving")) {
788
- for (let i = entry.cleanups.length - 1; i >= 0; i--) await entry.cleanups[i]?.();
845
+ for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
846
+ await entry.cleanups[i]?.();
847
+ } catch {}
789
848
  entry.cleanups = [];
790
849
  entry.state = "resolving";
791
850
  this.emitStateChange("resolving", atom$1);
@@ -805,7 +864,7 @@ var ScopeImpl = class {
805
864
  };
806
865
  const factory = atom$1.factory;
807
866
  const doResolve = async () => {
808
- if (atom$1.deps && Object.keys(atom$1.deps).length > 0) return factory(ctx, resolvedDeps);
867
+ if (atom$1.deps) return factory(ctx, resolvedDeps);
809
868
  else return factory(ctx);
810
869
  };
811
870
  try {
@@ -841,88 +900,101 @@ var ScopeImpl = class {
841
900
  entry.pendingInvalidate = false;
842
901
  this.invalidationChain?.delete(atom$1);
843
902
  this.scheduleInvalidation(atom$1);
844
- }
903
+ } else if (entry.pendingSet && "value" in entry.pendingSet) {
904
+ this.invalidationChain?.delete(atom$1);
905
+ this.scheduleInvalidation(atom$1);
906
+ } else entry.pendingSet = void 0;
845
907
  throw entry.error;
846
908
  }
847
909
  }
848
910
  async applyResolveExtensions(event, doResolve) {
849
911
  let next = doResolve;
850
- for (let i = this.extensions.length - 1; i >= 0; i--) {
851
- const ext = this.extensions[i];
852
- if (ext?.wrapResolve) {
853
- const currentNext = next;
854
- next = ext.wrapResolve.bind(ext, currentNext, event);
855
- }
912
+ for (let i = this.resolveExts.length - 1; i >= 0; i--) {
913
+ const ext = this.resolveExts[i];
914
+ const currentNext = next;
915
+ next = ext.wrapResolve.bind(ext, currentNext, event);
856
916
  }
857
917
  return next();
858
918
  }
859
919
  async resolveDeps(deps, ctx, dependentAtom) {
860
920
  if (!deps) return {};
861
921
  const result = {};
862
- for (const [key, dep] of Object.entries(deps)) if (isAtom(dep)) {
863
- result[key] = await this.resolve(dep);
864
- if (dependentAtom) {
865
- const depEntry = this.getEntry(dep);
866
- if (depEntry) depEntry.dependents.add(dependentAtom);
867
- }
868
- } else if (isControllerDep(dep)) {
869
- if (dep.watch) {
870
- if (!dependentAtom) throw new Error("controller({ watch: true }) is only supported in atom dependencies");
871
- if (!dep.resolve) throw new Error("controller({ watch: true }) requires resolve: true");
872
- }
873
- const ctrl = this.controller(dep.atom);
874
- if (dep.resolve) await ctrl.resolve();
875
- result[key] = ctrl;
876
- if (dependentAtom) {
877
- const depEntry = this.getEntry(dep.atom);
878
- if (depEntry) depEntry.dependents.add(dependentAtom);
879
- }
880
- if (dep.watch) {
881
- const eq = dep.eq ?? Object.is;
882
- let prev = ctrl.get();
883
- const unsub = this.on("resolved", dep.atom, () => {
884
- const next = ctrl.get();
885
- if (!eq(prev, next)) {
886
- prev = next;
887
- this.scheduleInvalidation(dependentAtom);
922
+ const parallel = [];
923
+ const deferredResources = [];
924
+ for (const key in deps) {
925
+ const dep = deps[key];
926
+ if (isAtom(dep)) parallel.push(this.resolve(dep).then((value) => {
927
+ result[key] = value;
928
+ if (dependentAtom) {
929
+ const depEntry = this.getEntry(dep);
930
+ if (depEntry) depEntry.dependents.add(dependentAtom);
931
+ }
932
+ }));
933
+ else if (isControllerDep(dep)) {
934
+ if (dep.watch) {
935
+ if (!dependentAtom) throw new Error("controller({ watch: true }) is only supported in atom dependencies");
936
+ if (!dep.resolve) throw new Error("controller({ watch: true }) requires resolve: true");
937
+ }
938
+ const ctrl = this.controller(dep.atom);
939
+ if (dep.resolve) parallel.push(ctrl.resolve().then(() => {
940
+ result[key] = ctrl;
941
+ if (dependentAtom) {
942
+ const depEntry = this.getEntry(dep.atom);
943
+ if (depEntry) depEntry.dependents.add(dependentAtom);
944
+ }
945
+ if (dep.watch) {
946
+ const eq = dep.eq ?? shallowEqual;
947
+ let prev = ctrl.get();
948
+ const unsub = this.on("resolved", dep.atom, () => {
949
+ const next = ctrl.get();
950
+ if (!eq(prev, next)) this.scheduleInvalidation(dependentAtom);
951
+ prev = next;
952
+ });
953
+ const depEntry = this.getEntry(dependentAtom);
954
+ if (depEntry) depEntry.cleanups.push(unsub);
955
+ else unsub();
956
+ }
957
+ }));
958
+ else {
959
+ result[key] = ctrl;
960
+ if (dependentAtom) {
961
+ const depEntry = this.getEntry(dep.atom);
962
+ if (depEntry) depEntry.dependents.add(dependentAtom);
888
963
  }
889
- });
890
- const depEntry = this.getEntry(dependentAtom);
891
- if (depEntry) depEntry.cleanups.push(unsub);
892
- else unsub();
893
- }
894
- } else if (tagExecutorSymbol in dep) {
895
- const tagExecutor = dep;
896
- switch (tagExecutor.mode) {
897
- case "required": {
898
- const value = ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags);
899
- if (value !== void 0) result[key] = value;
900
- else if (tagExecutor.tag.hasDefault) result[key] = tagExecutor.tag.defaultValue;
901
- else throw new Error(`Tag "${tagExecutor.tag.label}" not found`);
902
- break;
903
964
  }
904
- case "optional":
905
- result[key] = (ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags)) ?? tagExecutor.tag.defaultValue;
906
- break;
907
- case "all":
908
- result[key] = ctx ? this.collectFromHierarchy(ctx, tagExecutor.tag) : tagExecutor.tag.collect(this.tags);
909
- break;
910
- }
911
- } else if (isResource(dep)) {
965
+ } else if (tagExecutorSymbol in dep) {
966
+ const tagExecutor = dep;
967
+ switch (tagExecutor.mode) {
968
+ case "required": {
969
+ const value = ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags);
970
+ if (value !== void 0) result[key] = value;
971
+ else if (tagExecutor.tag.hasDefault) result[key] = tagExecutor.tag.defaultValue;
972
+ else throw new Error(`Tag "${tagExecutor.tag.label}" not found`);
973
+ break;
974
+ }
975
+ case "optional":
976
+ result[key] = (ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags)) ?? tagExecutor.tag.defaultValue;
977
+ break;
978
+ case "all":
979
+ result[key] = ctx ? this.collectFromHierarchy(ctx, tagExecutor.tag) : tagExecutor.tag.collect(this.tags);
980
+ break;
981
+ }
982
+ } else if (isResource(dep)) deferredResources.push([key, dep]);
983
+ }
984
+ if (parallel.length === 1) await parallel[0];
985
+ else if (parallel.length > 1) await Promise.all(parallel);
986
+ for (const [key, resource$1] of deferredResources) {
912
987
  if (!ctx) throw new Error("Resource deps require an ExecutionContext");
913
- const resource$1 = dep;
914
988
  const resourceKey = getResourceKey(resource$1);
915
989
  const storeCtx = ctx.parent ?? ctx;
916
990
  if (storeCtx.data.has(resourceKey)) {
917
991
  result[key] = storeCtx.data.get(resourceKey);
918
992
  continue;
919
993
  }
920
- const existingSeek = ctx.data.seek(resourceKey);
921
- if (existingSeek !== void 0 || ctx.data.has(resourceKey)) {
922
- result[key] = existingSeek;
994
+ if (ctx.data.seekHas(resourceKey)) {
995
+ result[key] = ctx.data.seek(resourceKey);
923
996
  continue;
924
997
  }
925
- if (resolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
926
998
  let flights = inflightResources.get(storeCtx.data);
927
999
  if (!flights) {
928
1000
  flights = /* @__PURE__ */ new Map();
@@ -933,8 +1005,14 @@ var ScopeImpl = class {
933
1005
  result[key] = await inflight;
934
1006
  continue;
935
1007
  }
1008
+ let localResolvingResources = resolvingResourcesMap.get(storeCtx.data);
1009
+ if (!localResolvingResources) {
1010
+ localResolvingResources = /* @__PURE__ */ new Set();
1011
+ resolvingResourcesMap.set(storeCtx.data, localResolvingResources);
1012
+ }
1013
+ if (localResolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
936
1014
  const resolve = async () => {
937
- resolvingResources.add(resourceKey);
1015
+ localResolvingResources.add(resourceKey);
938
1016
  try {
939
1017
  const resourceDeps = await this.resolveDeps(resource$1.deps, ctx);
940
1018
  const event = {
@@ -944,14 +1022,14 @@ var ScopeImpl = class {
944
1022
  };
945
1023
  const doResolve = async () => {
946
1024
  const factory = resource$1.factory;
947
- if (resource$1.deps && Object.keys(resource$1.deps).length > 0) return factory(storeCtx, resourceDeps);
1025
+ if (resource$1.deps) return factory(storeCtx, resourceDeps);
948
1026
  return factory(storeCtx);
949
1027
  };
950
1028
  const value = await this.applyResolveExtensions(event, doResolve);
951
1029
  storeCtx.data.set(resourceKey, value);
952
1030
  return value;
953
1031
  } finally {
954
- resolvingResources.delete(resourceKey);
1032
+ localResolvingResources.delete(resourceKey);
955
1033
  }
956
1034
  };
957
1035
  const promise = resolve();
@@ -975,6 +1053,7 @@ var ScopeImpl = class {
975
1053
  return results;
976
1054
  }
977
1055
  controller(atom$1, options) {
1056
+ if (this.disposed) throw new Error("Scope is disposed");
978
1057
  let ctrl = this.controllers.get(atom$1);
979
1058
  if (!ctrl) {
980
1059
  ctrl = new ControllerImpl(atom$1, this);
@@ -984,7 +1063,7 @@ var ScopeImpl = class {
984
1063
  return ctrl;
985
1064
  }
986
1065
  select(atom$1, selector, options) {
987
- return new SelectHandleImpl(this.controller(atom$1), selector, options?.eq ?? ((a, b) => a === b));
1066
+ return new SelectHandleImpl(this.controller(atom$1), selector, options?.eq ?? Object.is);
988
1067
  }
989
1068
  getFlowPreset(flow$1) {
990
1069
  return this.presets.get(flow$1);
@@ -1028,10 +1107,27 @@ var ScopeImpl = class {
1028
1107
  const previousValue = entry.value;
1029
1108
  const pendingSet = entry.pendingSet;
1030
1109
  entry.pendingSet = void 0;
1031
- for (let i = entry.cleanups.length - 1; i >= 0; i--) {
1032
- const cleanup = entry.cleanups[i];
1033
- if (cleanup) await cleanup();
1110
+ if (pendingSet) {
1111
+ entry.state = "resolving";
1112
+ entry.value = previousValue;
1113
+ entry.error = void 0;
1114
+ entry.pendingInvalidate = false;
1115
+ this.pending.delete(atom$1);
1116
+ this.resolving.delete(atom$1);
1117
+ this.emitStateChange("resolving", atom$1);
1118
+ this.notifyListeners(atom$1, "resolving");
1119
+ if ("value" in pendingSet) entry.value = pendingSet.value;
1120
+ else entry.value = pendingSet.fn(previousValue);
1121
+ entry.state = "resolved";
1122
+ entry.hasValue = true;
1123
+ this.emitStateChange("resolved", atom$1);
1124
+ this.notifyListeners(atom$1, "resolved");
1125
+ this.invalidationChain?.delete(atom$1);
1126
+ return;
1034
1127
  }
1128
+ for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
1129
+ await entry.cleanups[i]?.();
1130
+ } catch {}
1035
1131
  entry.cleanups = [];
1036
1132
  entry.state = "resolving";
1037
1133
  entry.value = previousValue;
@@ -1041,16 +1137,11 @@ var ScopeImpl = class {
1041
1137
  this.resolving.delete(atom$1);
1042
1138
  this.emitStateChange("resolving", atom$1);
1043
1139
  this.notifyListeners(atom$1, "resolving");
1044
- if (pendingSet) {
1045
- if ("value" in pendingSet) entry.value = pendingSet.value;
1046
- else entry.value = pendingSet.fn(previousValue);
1047
- entry.state = "resolved";
1048
- entry.hasValue = true;
1049
- this.emitStateChange("resolved", atom$1);
1050
- this.notifyListeners(atom$1, "resolved");
1051
- return;
1140
+ try {
1141
+ await this.resolve(atom$1);
1142
+ } catch (e) {
1143
+ if (!entry.pendingSet && !entry.pendingInvalidate) throw e;
1052
1144
  }
1053
- await this.resolve(atom$1);
1054
1145
  }
1055
1146
  async release(atom$1) {
1056
1147
  const entry = this.cache.get(atom$1);
@@ -1059,14 +1150,33 @@ var ScopeImpl = class {
1059
1150
  clearTimeout(entry.gcScheduled);
1060
1151
  entry.gcScheduled = null;
1061
1152
  }
1062
- for (let i = entry.cleanups.length - 1; i >= 0; i--) {
1063
- const cleanup = entry.cleanups[i];
1064
- if (cleanup) await cleanup();
1153
+ for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
1154
+ await entry.cleanups[i]?.();
1155
+ } catch {}
1156
+ if (atom$1.deps) for (const dep of Object.values(atom$1.deps)) {
1157
+ const depAtom = isAtom(dep) ? dep : isControllerDep(dep) ? dep.atom : null;
1158
+ if (!depAtom) continue;
1159
+ const depEntry = this.cache.get(depAtom);
1160
+ if (depEntry) {
1161
+ depEntry.dependents.delete(atom$1);
1162
+ this.maybeScheduleGC(depAtom);
1163
+ }
1065
1164
  }
1066
1165
  this.cache.delete(atom$1);
1067
1166
  this.controllers.delete(atom$1);
1167
+ for (const [state, stateMap] of this.stateListeners) {
1168
+ stateMap.delete(atom$1);
1169
+ if (stateMap.size === 0) this.stateListeners.delete(state);
1170
+ }
1068
1171
  }
1069
1172
  async dispose() {
1173
+ if (this.chainPromise) try {
1174
+ await this.chainPromise;
1175
+ } catch {}
1176
+ this.disposed = true;
1177
+ this.invalidationQueue.clear();
1178
+ this.invalidationChain = null;
1179
+ this.chainPromise = null;
1070
1180
  for (const ext of this.extensions) if (ext.dispose) await ext.dispose(this);
1071
1181
  for (const entry of this.cache.values()) if (entry.gcScheduled) {
1072
1182
  clearTimeout(entry.gcScheduled);
@@ -1077,8 +1187,14 @@ var ScopeImpl = class {
1077
1187
  }
1078
1188
  async flush() {
1079
1189
  if (this.chainPromise) await this.chainPromise;
1190
+ if (this.chainError !== null) {
1191
+ const error = this.chainError;
1192
+ this.chainError = null;
1193
+ throw error;
1194
+ }
1080
1195
  }
1081
1196
  createContext(options) {
1197
+ if (this.disposed) throw new Error("Scope is disposed");
1082
1198
  const ctx = new ExecutionContextImpl(this, options);
1083
1199
  for (const tagged of options?.tags ?? []) ctx.data.set(tagged.key, tagged.value);
1084
1200
  for (const tagged of this.tags) if (!ctx.data.has(tagged.key)) ctx.data.set(tagged.key, tagged.value);
@@ -1172,7 +1288,7 @@ var ExecutionContextImpl = class ExecutionContextImpl {
1172
1288
  const resolvedDeps = await this.scope.resolveDeps(flow$1.deps, this);
1173
1289
  const factory = flow$1.factory;
1174
1290
  const doExec = async () => {
1175
- if (flow$1.deps && Object.keys(flow$1.deps).length > 0) return factory(this, resolvedDeps);
1291
+ if (flow$1.deps) return factory(this, resolvedDeps);
1176
1292
  else return factory(this);
1177
1293
  };
1178
1294
  return this.applyExecExtensions(flow$1, doExec);
@@ -1188,12 +1304,10 @@ var ExecutionContextImpl = class ExecutionContextImpl {
1188
1304
  }
1189
1305
  async applyExecExtensions(target, doExec) {
1190
1306
  let next = doExec;
1191
- for (let i = this.scope.extensions.length - 1; i >= 0; i--) {
1192
- const ext = this.scope.extensions[i];
1193
- if (ext?.wrapExec) {
1194
- const currentNext = next;
1195
- next = ext.wrapExec.bind(ext, currentNext, target, this);
1196
- }
1307
+ for (let i = this.scope.execExts.length - 1; i >= 0; i--) {
1308
+ const ext = this.scope.execExts[i];
1309
+ const currentNext = next;
1310
+ next = ext.wrapExec.bind(ext, currentNext, target, this);
1197
1311
  }
1198
1312
  return next();
1199
1313
  }
@@ -1203,10 +1317,9 @@ var ExecutionContextImpl = class ExecutionContextImpl {
1203
1317
  async close(result = { ok: true }) {
1204
1318
  if (this.closed) return;
1205
1319
  this.closed = true;
1206
- for (let i = this.cleanups.length - 1; i >= 0; i--) {
1207
- const cleanup = this.cleanups[i];
1208
- if (cleanup) await cleanup(result);
1209
- }
1320
+ for (let i = this.cleanups.length - 1; i >= 0; i--) try {
1321
+ await this.cleanups[i]?.(result);
1322
+ } catch {}
1210
1323
  }
1211
1324
  };
1212
1325
  /**
@@ -1243,5 +1356,5 @@ function createScope(options) {
1243
1356
  const VERSION = "0.0.1";
1244
1357
 
1245
1358
  //#endregion
1246
- export { ParseError, VERSION, atom, atomSymbol, controller, controllerDepSymbol, controllerSymbol, createScope, flow, flowSymbol, getAllTags, isAtom, isControllerDep, isFlow, isPreset, isResource, isTag, isTagExecutor, isTagged, preset, presetSymbol, resource, resourceSymbol, service, tag, tagExecutorSymbol, tagSymbol, taggedSymbol, tags, typed, typedSymbol };
1359
+ export { ParseError, VERSION, atom, atomSymbol, controller, controllerDepSymbol, controllerSymbol, createScope, flow, flowSymbol, getAllTags, isAtom, isControllerDep, isFlow, isPreset, isResource, isTag, isTagExecutor, isTagged, preset, presetSymbol, resource, resourceSymbol, service, shallowEqual, tag, tagExecutorSymbol, tagSymbol, taggedSymbol, tags, typed, typedSymbol };
1247
1360
  //# sourceMappingURL=index.mjs.map