@pumped-fn/lite 2.0.0 → 2.1.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/CHANGELOG.md +49 -0
- package/PATTERNS.md +12 -11
- package/README.md +8 -1
- package/dist/index.cjs +224 -99
- package/dist/index.d.cts +46 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +46 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +224 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|
|
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;
|
|
@@ -262,20 +262,33 @@ function isAtom(value) {
|
|
|
262
262
|
* The Controller provides full lifecycle control: get, resolve, release, invalidate, and subscribe.
|
|
263
263
|
*
|
|
264
264
|
* @param atom - The Atom to wrap
|
|
265
|
-
* @param options - Optional configuration
|
|
265
|
+
* @param options - Optional configuration:
|
|
266
|
+
* - `resolve: true` — auto-resolves the dep before the parent factory runs; `config.get()` is safe.
|
|
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
|
|
269
|
+
* manual `ctx.cleanup(ctx.scope.on('resolved', dep, () => ctx.invalidate()))` wiring. Watch
|
|
270
|
+
* listeners are auto-cleaned on re-resolve, release, and dispose.
|
|
271
|
+
* - `eq` — custom equality function `(a: T, b: T) => boolean`; only used with `watch: true`.
|
|
266
272
|
* @returns A ControllerDep that resolves to a Controller for the Atom
|
|
267
273
|
*
|
|
268
274
|
* @example
|
|
269
275
|
* ```typescript
|
|
270
|
-
*
|
|
276
|
+
* // resolve only
|
|
271
277
|
* const serverAtom = atom({
|
|
272
278
|
* deps: { config: controller(configAtom, { resolve: true }) },
|
|
273
|
-
* factory: (
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* }
|
|
279
|
+
* factory: (_, { config }) => createServer(config.get().port),
|
|
280
|
+
* })
|
|
281
|
+
*
|
|
282
|
+
* // watch: re-runs parent when dep value changes
|
|
283
|
+
* const profileAtom = atom({
|
|
284
|
+
* deps: { token: controller(tokenAtom, { resolve: true, watch: true }) },
|
|
285
|
+
* factory: (_, { token }) => ({ id: `user-${token.get().jwt}` }),
|
|
286
|
+
* })
|
|
287
|
+
*
|
|
288
|
+
* // watch with custom equality
|
|
289
|
+
* const derivedAtom = atom({
|
|
290
|
+
* deps: { src: controller(srcAtom, { resolve: true, watch: true, eq: (a, b) => a.id === b.id }) },
|
|
291
|
+
* factory: (_, { src }) => src.get().name,
|
|
279
292
|
* })
|
|
280
293
|
* ```
|
|
281
294
|
*/
|
|
@@ -283,7 +296,9 @@ function controller(atom$1, options) {
|
|
|
283
296
|
return {
|
|
284
297
|
[controllerDepSymbol]: true,
|
|
285
298
|
atom: atom$1,
|
|
286
|
-
resolve: options?.resolve
|
|
299
|
+
resolve: options?.resolve,
|
|
300
|
+
watch: options?.watch,
|
|
301
|
+
eq: options?.eq
|
|
287
302
|
};
|
|
288
303
|
}
|
|
289
304
|
/**
|
|
@@ -409,12 +424,14 @@ function isResource(value) {
|
|
|
409
424
|
//#endregion
|
|
410
425
|
//#region src/service.ts
|
|
411
426
|
function service(config) {
|
|
412
|
-
|
|
427
|
+
const atomInstance = {
|
|
413
428
|
[atomSymbol]: true,
|
|
414
429
|
factory: config.factory,
|
|
415
430
|
deps: config.deps,
|
|
416
431
|
tags: config.tags
|
|
417
432
|
};
|
|
433
|
+
if (config.tags?.length) registerAtomToTags(atomInstance, config.tags);
|
|
434
|
+
return atomInstance;
|
|
418
435
|
}
|
|
419
436
|
|
|
420
437
|
//#endregion
|
|
@@ -430,7 +447,7 @@ function getResourceKey(resource$1) {
|
|
|
430
447
|
return key;
|
|
431
448
|
}
|
|
432
449
|
const inflightResources = /* @__PURE__ */ new WeakMap();
|
|
433
|
-
const
|
|
450
|
+
const resolvingResourcesMap = /* @__PURE__ */ new WeakMap();
|
|
434
451
|
var ContextDataImpl = class {
|
|
435
452
|
map = /* @__PURE__ */ new Map();
|
|
436
453
|
constructor(parentData) {
|
|
@@ -455,6 +472,10 @@ var ContextDataImpl = class {
|
|
|
455
472
|
if (this.map.has(key)) return this.map.get(key);
|
|
456
473
|
return this.parentData?.seek(key);
|
|
457
474
|
}
|
|
475
|
+
seekHas(key) {
|
|
476
|
+
if (this.map.has(key)) return true;
|
|
477
|
+
return this.parentData?.seekHas(key) ?? false;
|
|
478
|
+
}
|
|
458
479
|
getTag(tag$1) {
|
|
459
480
|
return this.map.get(tag$1.key);
|
|
460
481
|
}
|
|
@@ -500,6 +521,16 @@ var SelectHandleImpl = class {
|
|
|
500
521
|
return this.currentValue;
|
|
501
522
|
}
|
|
502
523
|
subscribe(listener) {
|
|
524
|
+
if (!this.ctrlUnsub) {
|
|
525
|
+
this.currentValue = this.selector(this.ctrl.get());
|
|
526
|
+
this.ctrlUnsub = this.ctrl.on("resolved", () => {
|
|
527
|
+
const nextValue = this.selector(this.ctrl.get());
|
|
528
|
+
if (!this.eq(this.currentValue, nextValue)) {
|
|
529
|
+
this.currentValue = nextValue;
|
|
530
|
+
this.notifyListeners();
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
503
534
|
this.listeners.add(listener);
|
|
504
535
|
return () => {
|
|
505
536
|
this.listeners.delete(listener);
|
|
@@ -507,12 +538,15 @@ var SelectHandleImpl = class {
|
|
|
507
538
|
};
|
|
508
539
|
}
|
|
509
540
|
notifyListeners() {
|
|
510
|
-
for (const listener of this.listeners) listener();
|
|
541
|
+
for (const listener of [...this.listeners]) listener();
|
|
542
|
+
}
|
|
543
|
+
dispose() {
|
|
544
|
+
this.listeners.clear();
|
|
545
|
+
this.cleanup();
|
|
511
546
|
}
|
|
512
547
|
cleanup() {
|
|
513
548
|
this.ctrlUnsub?.();
|
|
514
549
|
this.ctrlUnsub = null;
|
|
515
|
-
this.listeners.clear();
|
|
516
550
|
}
|
|
517
551
|
};
|
|
518
552
|
var ControllerImpl = class {
|
|
@@ -561,11 +595,15 @@ var ScopeImpl = class {
|
|
|
561
595
|
invalidationScheduled = false;
|
|
562
596
|
invalidationChain = null;
|
|
563
597
|
chainPromise = null;
|
|
598
|
+
chainError = null;
|
|
564
599
|
initialized = false;
|
|
600
|
+
disposed = false;
|
|
565
601
|
controllers = /* @__PURE__ */ new Map();
|
|
566
602
|
gcOptions;
|
|
567
603
|
extensions;
|
|
568
604
|
tags;
|
|
605
|
+
resolveExts;
|
|
606
|
+
execExts;
|
|
569
607
|
ready;
|
|
570
608
|
scheduleInvalidation(atom$1) {
|
|
571
609
|
const entry = this.cache.get(atom$1);
|
|
@@ -578,16 +616,22 @@ var ScopeImpl = class {
|
|
|
578
616
|
if (!this.chainPromise) {
|
|
579
617
|
this.invalidationChain = /* @__PURE__ */ new Set();
|
|
580
618
|
this.invalidationScheduled = true;
|
|
581
|
-
this.
|
|
582
|
-
|
|
583
|
-
|
|
619
|
+
this.chainError = null;
|
|
620
|
+
this.chainPromise = (async () => {
|
|
621
|
+
await new Promise((resolve) => {
|
|
622
|
+
queueMicrotask(resolve);
|
|
584
623
|
});
|
|
585
|
-
|
|
624
|
+
try {
|
|
625
|
+
await this.processInvalidationChain();
|
|
626
|
+
} catch (error) {
|
|
627
|
+
if (this.chainError === null) this.chainError = error;
|
|
628
|
+
}
|
|
629
|
+
})();
|
|
586
630
|
}
|
|
587
631
|
}
|
|
588
632
|
async processInvalidationChain() {
|
|
589
633
|
try {
|
|
590
|
-
while (this.invalidationQueue.size > 0) {
|
|
634
|
+
while (this.invalidationQueue.size > 0 && !this.disposed) {
|
|
591
635
|
const atom$1 = this.invalidationQueue.values().next().value;
|
|
592
636
|
this.invalidationQueue.delete(atom$1);
|
|
593
637
|
if (this.invalidationChain.has(atom$1)) {
|
|
@@ -608,6 +652,8 @@ var ScopeImpl = class {
|
|
|
608
652
|
constructor(options) {
|
|
609
653
|
this.extensions = options?.extensions ?? [];
|
|
610
654
|
this.tags = options?.tags ?? [];
|
|
655
|
+
this.resolveExts = this.extensions.filter((e) => e.wrapResolve);
|
|
656
|
+
this.execExts = this.extensions.filter((e) => e.wrapExec);
|
|
611
657
|
for (const p of options?.presets ?? []) this.presets.set(p.target, p.value);
|
|
612
658
|
this.gcOptions = {
|
|
613
659
|
enabled: options?.gc?.enabled ?? true,
|
|
@@ -700,21 +746,21 @@ var ScopeImpl = class {
|
|
|
700
746
|
const entry = this.cache.get(atom$1);
|
|
701
747
|
if (!entry) return;
|
|
702
748
|
const eventListeners = entry.listeners.get(event);
|
|
703
|
-
if (eventListeners) for (const listener of eventListeners) listener();
|
|
749
|
+
if (eventListeners?.size) for (const listener of [...eventListeners]) listener();
|
|
704
750
|
const allListeners = entry.listeners.get("*");
|
|
705
|
-
if (allListeners) for (const listener of allListeners) listener();
|
|
751
|
+
if (allListeners?.size) for (const listener of [...allListeners]) listener();
|
|
706
752
|
}
|
|
707
753
|
notifyAllListeners(atom$1) {
|
|
708
754
|
const entry = this.cache.get(atom$1);
|
|
709
755
|
if (!entry) return;
|
|
710
756
|
const allListeners = entry.listeners.get("*");
|
|
711
|
-
if (allListeners) for (const listener of allListeners) listener();
|
|
757
|
+
if (allListeners?.size) for (const listener of [...allListeners]) listener();
|
|
712
758
|
}
|
|
713
759
|
emitStateChange(state, atom$1) {
|
|
714
760
|
const stateMap = this.stateListeners.get(state);
|
|
715
761
|
if (stateMap) {
|
|
716
762
|
const listeners = stateMap.get(atom$1);
|
|
717
|
-
if (listeners) for (const listener of listeners) listener();
|
|
763
|
+
if (listeners?.size) for (const listener of [...listeners]) listener();
|
|
718
764
|
}
|
|
719
765
|
}
|
|
720
766
|
on(event, atom$1, listener) {
|
|
@@ -740,14 +786,15 @@ var ScopeImpl = class {
|
|
|
740
786
|
};
|
|
741
787
|
}
|
|
742
788
|
async resolve(atom$1) {
|
|
789
|
+
if (this.disposed) throw new Error("Scope is disposed");
|
|
743
790
|
if (!this.initialized) await this.ready;
|
|
744
791
|
const entry = this.cache.get(atom$1);
|
|
745
792
|
if (entry?.state === "resolved") return entry.value;
|
|
746
793
|
const pendingPromise = this.pending.get(atom$1);
|
|
747
794
|
if (pendingPromise) return pendingPromise;
|
|
748
795
|
if (this.resolving.has(atom$1)) throw new Error("Circular dependency detected");
|
|
749
|
-
|
|
750
|
-
|
|
796
|
+
if (this.presets.has(atom$1)) {
|
|
797
|
+
const presetValue = this.presets.get(atom$1);
|
|
751
798
|
if (isAtom(presetValue)) return this.resolve(presetValue);
|
|
752
799
|
const newEntry = this.getOrCreateEntry(atom$1);
|
|
753
800
|
newEntry.state = "resolved";
|
|
@@ -770,6 +817,10 @@ var ScopeImpl = class {
|
|
|
770
817
|
async doResolve(atom$1) {
|
|
771
818
|
const entry = this.getOrCreateEntry(atom$1);
|
|
772
819
|
if (!(entry.state === "resolving")) {
|
|
820
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
|
|
821
|
+
await entry.cleanups[i]?.();
|
|
822
|
+
} catch {}
|
|
823
|
+
entry.cleanups = [];
|
|
773
824
|
entry.state = "resolving";
|
|
774
825
|
this.emitStateChange("resolving", atom$1);
|
|
775
826
|
this.notifyListeners(atom$1, "resolving");
|
|
@@ -788,7 +839,7 @@ var ScopeImpl = class {
|
|
|
788
839
|
};
|
|
789
840
|
const factory = atom$1.factory;
|
|
790
841
|
const doResolve = async () => {
|
|
791
|
-
if (atom$1.deps
|
|
842
|
+
if (atom$1.deps) return factory(ctx, resolvedDeps);
|
|
792
843
|
else return factory(ctx);
|
|
793
844
|
};
|
|
794
845
|
try {
|
|
@@ -824,70 +875,103 @@ var ScopeImpl = class {
|
|
|
824
875
|
entry.pendingInvalidate = false;
|
|
825
876
|
this.invalidationChain?.delete(atom$1);
|
|
826
877
|
this.scheduleInvalidation(atom$1);
|
|
827
|
-
}
|
|
878
|
+
} else if (entry.pendingSet && "value" in entry.pendingSet) {
|
|
879
|
+
this.invalidationChain?.delete(atom$1);
|
|
880
|
+
this.scheduleInvalidation(atom$1);
|
|
881
|
+
} else entry.pendingSet = void 0;
|
|
828
882
|
throw entry.error;
|
|
829
883
|
}
|
|
830
884
|
}
|
|
831
885
|
async applyResolveExtensions(event, doResolve) {
|
|
832
886
|
let next = doResolve;
|
|
833
|
-
for (let i = this.
|
|
834
|
-
const ext = this.
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
next = ext.wrapResolve.bind(ext, currentNext, event);
|
|
838
|
-
}
|
|
887
|
+
for (let i = this.resolveExts.length - 1; i >= 0; i--) {
|
|
888
|
+
const ext = this.resolveExts[i];
|
|
889
|
+
const currentNext = next;
|
|
890
|
+
next = ext.wrapResolve.bind(ext, currentNext, event);
|
|
839
891
|
}
|
|
840
892
|
return next();
|
|
841
893
|
}
|
|
842
894
|
async resolveDeps(deps, ctx, dependentAtom) {
|
|
843
895
|
if (!deps) return {};
|
|
844
896
|
const result = {};
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
result[key] = ctrl;
|
|
855
|
-
if (dependentAtom) {
|
|
856
|
-
const depEntry = this.getEntry(dep.atom);
|
|
857
|
-
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
858
|
-
}
|
|
859
|
-
} else if (tagExecutorSymbol in dep) {
|
|
860
|
-
const tagExecutor = dep;
|
|
861
|
-
switch (tagExecutor.mode) {
|
|
862
|
-
case "required": {
|
|
863
|
-
const value = ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags);
|
|
864
|
-
if (value !== void 0) result[key] = value;
|
|
865
|
-
else if (tagExecutor.tag.hasDefault) result[key] = tagExecutor.tag.defaultValue;
|
|
866
|
-
else throw new Error(`Tag "${tagExecutor.tag.label}" not found`);
|
|
867
|
-
break;
|
|
897
|
+
const parallel = [];
|
|
898
|
+
const deferredResources = [];
|
|
899
|
+
for (const key in deps) {
|
|
900
|
+
const dep = deps[key];
|
|
901
|
+
if (isAtom(dep)) parallel.push(this.resolve(dep).then((value) => {
|
|
902
|
+
result[key] = value;
|
|
903
|
+
if (dependentAtom) {
|
|
904
|
+
const depEntry = this.getEntry(dep);
|
|
905
|
+
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
868
906
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
907
|
+
}));
|
|
908
|
+
else if (isControllerDep(dep)) {
|
|
909
|
+
if (dep.watch) {
|
|
910
|
+
if (!dependentAtom) throw new Error("controller({ watch: true }) is only supported in atom dependencies");
|
|
911
|
+
if (!dep.resolve) throw new Error("controller({ watch: true }) requires resolve: true");
|
|
912
|
+
}
|
|
913
|
+
const ctrl = this.controller(dep.atom);
|
|
914
|
+
if (dep.resolve) parallel.push(ctrl.resolve().then(() => {
|
|
915
|
+
result[key] = ctrl;
|
|
916
|
+
if (dependentAtom) {
|
|
917
|
+
const depEntry = this.getEntry(dep.atom);
|
|
918
|
+
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
919
|
+
}
|
|
920
|
+
if (dep.watch) {
|
|
921
|
+
const eq = dep.eq ?? Object.is;
|
|
922
|
+
let prev = ctrl.get();
|
|
923
|
+
const unsub = this.on("resolved", dep.atom, () => {
|
|
924
|
+
const next = ctrl.get();
|
|
925
|
+
if (!eq(prev, next)) {
|
|
926
|
+
prev = next;
|
|
927
|
+
this.scheduleInvalidation(dependentAtom);
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
const depEntry = this.getEntry(dependentAtom);
|
|
931
|
+
if (depEntry) depEntry.cleanups.push(unsub);
|
|
932
|
+
else unsub();
|
|
933
|
+
}
|
|
934
|
+
}));
|
|
935
|
+
else {
|
|
936
|
+
result[key] = ctrl;
|
|
937
|
+
if (dependentAtom) {
|
|
938
|
+
const depEntry = this.getEntry(dep.atom);
|
|
939
|
+
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
} else if (tagExecutorSymbol in dep) {
|
|
943
|
+
const tagExecutor = dep;
|
|
944
|
+
switch (tagExecutor.mode) {
|
|
945
|
+
case "required": {
|
|
946
|
+
const value = ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags);
|
|
947
|
+
if (value !== void 0) result[key] = value;
|
|
948
|
+
else if (tagExecutor.tag.hasDefault) result[key] = tagExecutor.tag.defaultValue;
|
|
949
|
+
else throw new Error(`Tag "${tagExecutor.tag.label}" not found`);
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
case "optional":
|
|
953
|
+
result[key] = (ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags)) ?? tagExecutor.tag.defaultValue;
|
|
954
|
+
break;
|
|
955
|
+
case "all":
|
|
956
|
+
result[key] = ctx ? this.collectFromHierarchy(ctx, tagExecutor.tag) : tagExecutor.tag.collect(this.tags);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
} else if (isResource(dep)) deferredResources.push([key, dep]);
|
|
960
|
+
}
|
|
961
|
+
if (parallel.length === 1) await parallel[0];
|
|
962
|
+
else if (parallel.length > 1) await Promise.all(parallel);
|
|
963
|
+
for (const [key, resource$1] of deferredResources) {
|
|
877
964
|
if (!ctx) throw new Error("Resource deps require an ExecutionContext");
|
|
878
|
-
const resource$1 = dep;
|
|
879
965
|
const resourceKey = getResourceKey(resource$1);
|
|
880
966
|
const storeCtx = ctx.parent ?? ctx;
|
|
881
967
|
if (storeCtx.data.has(resourceKey)) {
|
|
882
968
|
result[key] = storeCtx.data.get(resourceKey);
|
|
883
969
|
continue;
|
|
884
970
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
result[key] = existingSeek;
|
|
971
|
+
if (ctx.data.seekHas(resourceKey)) {
|
|
972
|
+
result[key] = ctx.data.seek(resourceKey);
|
|
888
973
|
continue;
|
|
889
974
|
}
|
|
890
|
-
if (resolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
|
|
891
975
|
let flights = inflightResources.get(storeCtx.data);
|
|
892
976
|
if (!flights) {
|
|
893
977
|
flights = /* @__PURE__ */ new Map();
|
|
@@ -898,8 +982,14 @@ var ScopeImpl = class {
|
|
|
898
982
|
result[key] = await inflight;
|
|
899
983
|
continue;
|
|
900
984
|
}
|
|
985
|
+
let localResolvingResources = resolvingResourcesMap.get(storeCtx.data);
|
|
986
|
+
if (!localResolvingResources) {
|
|
987
|
+
localResolvingResources = /* @__PURE__ */ new Set();
|
|
988
|
+
resolvingResourcesMap.set(storeCtx.data, localResolvingResources);
|
|
989
|
+
}
|
|
990
|
+
if (localResolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
|
|
901
991
|
const resolve = async () => {
|
|
902
|
-
|
|
992
|
+
localResolvingResources.add(resourceKey);
|
|
903
993
|
try {
|
|
904
994
|
const resourceDeps = await this.resolveDeps(resource$1.deps, ctx);
|
|
905
995
|
const event = {
|
|
@@ -909,14 +999,14 @@ var ScopeImpl = class {
|
|
|
909
999
|
};
|
|
910
1000
|
const doResolve = async () => {
|
|
911
1001
|
const factory = resource$1.factory;
|
|
912
|
-
if (resource$1.deps
|
|
1002
|
+
if (resource$1.deps) return factory(storeCtx, resourceDeps);
|
|
913
1003
|
return factory(storeCtx);
|
|
914
1004
|
};
|
|
915
1005
|
const value = await this.applyResolveExtensions(event, doResolve);
|
|
916
1006
|
storeCtx.data.set(resourceKey, value);
|
|
917
1007
|
return value;
|
|
918
1008
|
} finally {
|
|
919
|
-
|
|
1009
|
+
localResolvingResources.delete(resourceKey);
|
|
920
1010
|
}
|
|
921
1011
|
};
|
|
922
1012
|
const promise = resolve();
|
|
@@ -940,6 +1030,7 @@ var ScopeImpl = class {
|
|
|
940
1030
|
return results;
|
|
941
1031
|
}
|
|
942
1032
|
controller(atom$1, options) {
|
|
1033
|
+
if (this.disposed) throw new Error("Scope is disposed");
|
|
943
1034
|
let ctrl = this.controllers.get(atom$1);
|
|
944
1035
|
if (!ctrl) {
|
|
945
1036
|
ctrl = new ControllerImpl(atom$1, this);
|
|
@@ -949,7 +1040,7 @@ var ScopeImpl = class {
|
|
|
949
1040
|
return ctrl;
|
|
950
1041
|
}
|
|
951
1042
|
select(atom$1, selector, options) {
|
|
952
|
-
return new SelectHandleImpl(this.controller(atom$1), selector, options?.eq ??
|
|
1043
|
+
return new SelectHandleImpl(this.controller(atom$1), selector, options?.eq ?? Object.is);
|
|
953
1044
|
}
|
|
954
1045
|
getFlowPreset(flow$1) {
|
|
955
1046
|
return this.presets.get(flow$1);
|
|
@@ -993,10 +1084,27 @@ var ScopeImpl = class {
|
|
|
993
1084
|
const previousValue = entry.value;
|
|
994
1085
|
const pendingSet = entry.pendingSet;
|
|
995
1086
|
entry.pendingSet = void 0;
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1087
|
+
if (pendingSet) {
|
|
1088
|
+
entry.state = "resolving";
|
|
1089
|
+
entry.value = previousValue;
|
|
1090
|
+
entry.error = void 0;
|
|
1091
|
+
entry.pendingInvalidate = false;
|
|
1092
|
+
this.pending.delete(atom$1);
|
|
1093
|
+
this.resolving.delete(atom$1);
|
|
1094
|
+
this.emitStateChange("resolving", atom$1);
|
|
1095
|
+
this.notifyListeners(atom$1, "resolving");
|
|
1096
|
+
if ("value" in pendingSet) entry.value = pendingSet.value;
|
|
1097
|
+
else entry.value = pendingSet.fn(previousValue);
|
|
1098
|
+
entry.state = "resolved";
|
|
1099
|
+
entry.hasValue = true;
|
|
1100
|
+
this.emitStateChange("resolved", atom$1);
|
|
1101
|
+
this.notifyListeners(atom$1, "resolved");
|
|
1102
|
+
this.invalidationChain?.delete(atom$1);
|
|
1103
|
+
return;
|
|
999
1104
|
}
|
|
1105
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
|
|
1106
|
+
await entry.cleanups[i]?.();
|
|
1107
|
+
} catch {}
|
|
1000
1108
|
entry.cleanups = [];
|
|
1001
1109
|
entry.state = "resolving";
|
|
1002
1110
|
entry.value = previousValue;
|
|
@@ -1006,16 +1114,11 @@ var ScopeImpl = class {
|
|
|
1006
1114
|
this.resolving.delete(atom$1);
|
|
1007
1115
|
this.emitStateChange("resolving", atom$1);
|
|
1008
1116
|
this.notifyListeners(atom$1, "resolving");
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
entry.
|
|
1013
|
-
entry.hasValue = true;
|
|
1014
|
-
this.emitStateChange("resolved", atom$1);
|
|
1015
|
-
this.notifyListeners(atom$1, "resolved");
|
|
1016
|
-
return;
|
|
1117
|
+
try {
|
|
1118
|
+
await this.resolve(atom$1);
|
|
1119
|
+
} catch (e) {
|
|
1120
|
+
if (!entry.pendingSet && !entry.pendingInvalidate) throw e;
|
|
1017
1121
|
}
|
|
1018
|
-
await this.resolve(atom$1);
|
|
1019
1122
|
}
|
|
1020
1123
|
async release(atom$1) {
|
|
1021
1124
|
const entry = this.cache.get(atom$1);
|
|
@@ -1024,14 +1127,33 @@ var ScopeImpl = class {
|
|
|
1024
1127
|
clearTimeout(entry.gcScheduled);
|
|
1025
1128
|
entry.gcScheduled = null;
|
|
1026
1129
|
}
|
|
1027
|
-
for (let i = entry.cleanups.length - 1; i >= 0; i--) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1130
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
|
|
1131
|
+
await entry.cleanups[i]?.();
|
|
1132
|
+
} catch {}
|
|
1133
|
+
if (atom$1.deps) for (const dep of Object.values(atom$1.deps)) {
|
|
1134
|
+
const depAtom = isAtom(dep) ? dep : isControllerDep(dep) ? dep.atom : null;
|
|
1135
|
+
if (!depAtom) continue;
|
|
1136
|
+
const depEntry = this.cache.get(depAtom);
|
|
1137
|
+
if (depEntry) {
|
|
1138
|
+
depEntry.dependents.delete(atom$1);
|
|
1139
|
+
this.maybeScheduleGC(depAtom);
|
|
1140
|
+
}
|
|
1030
1141
|
}
|
|
1031
1142
|
this.cache.delete(atom$1);
|
|
1032
1143
|
this.controllers.delete(atom$1);
|
|
1144
|
+
for (const [state, stateMap] of this.stateListeners) {
|
|
1145
|
+
stateMap.delete(atom$1);
|
|
1146
|
+
if (stateMap.size === 0) this.stateListeners.delete(state);
|
|
1147
|
+
}
|
|
1033
1148
|
}
|
|
1034
1149
|
async dispose() {
|
|
1150
|
+
if (this.chainPromise) try {
|
|
1151
|
+
await this.chainPromise;
|
|
1152
|
+
} catch {}
|
|
1153
|
+
this.disposed = true;
|
|
1154
|
+
this.invalidationQueue.clear();
|
|
1155
|
+
this.invalidationChain = null;
|
|
1156
|
+
this.chainPromise = null;
|
|
1035
1157
|
for (const ext of this.extensions) if (ext.dispose) await ext.dispose(this);
|
|
1036
1158
|
for (const entry of this.cache.values()) if (entry.gcScheduled) {
|
|
1037
1159
|
clearTimeout(entry.gcScheduled);
|
|
@@ -1042,8 +1164,14 @@ var ScopeImpl = class {
|
|
|
1042
1164
|
}
|
|
1043
1165
|
async flush() {
|
|
1044
1166
|
if (this.chainPromise) await this.chainPromise;
|
|
1167
|
+
if (this.chainError !== null) {
|
|
1168
|
+
const error = this.chainError;
|
|
1169
|
+
this.chainError = null;
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1045
1172
|
}
|
|
1046
1173
|
createContext(options) {
|
|
1174
|
+
if (this.disposed) throw new Error("Scope is disposed");
|
|
1047
1175
|
const ctx = new ExecutionContextImpl(this, options);
|
|
1048
1176
|
for (const tagged of options?.tags ?? []) ctx.data.set(tagged.key, tagged.value);
|
|
1049
1177
|
for (const tagged of this.tags) if (!ctx.data.has(tagged.key)) ctx.data.set(tagged.key, tagged.value);
|
|
@@ -1137,7 +1265,7 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1137
1265
|
const resolvedDeps = await this.scope.resolveDeps(flow$1.deps, this);
|
|
1138
1266
|
const factory = flow$1.factory;
|
|
1139
1267
|
const doExec = async () => {
|
|
1140
|
-
if (flow$1.deps
|
|
1268
|
+
if (flow$1.deps) return factory(this, resolvedDeps);
|
|
1141
1269
|
else return factory(this);
|
|
1142
1270
|
};
|
|
1143
1271
|
return this.applyExecExtensions(flow$1, doExec);
|
|
@@ -1153,12 +1281,10 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1153
1281
|
}
|
|
1154
1282
|
async applyExecExtensions(target, doExec) {
|
|
1155
1283
|
let next = doExec;
|
|
1156
|
-
for (let i = this.scope.
|
|
1157
|
-
const ext = this.scope.
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
next = ext.wrapExec.bind(ext, currentNext, target, this);
|
|
1161
|
-
}
|
|
1284
|
+
for (let i = this.scope.execExts.length - 1; i >= 0; i--) {
|
|
1285
|
+
const ext = this.scope.execExts[i];
|
|
1286
|
+
const currentNext = next;
|
|
1287
|
+
next = ext.wrapExec.bind(ext, currentNext, target, this);
|
|
1162
1288
|
}
|
|
1163
1289
|
return next();
|
|
1164
1290
|
}
|
|
@@ -1168,10 +1294,9 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1168
1294
|
async close(result = { ok: true }) {
|
|
1169
1295
|
if (this.closed) return;
|
|
1170
1296
|
this.closed = true;
|
|
1171
|
-
for (let i = this.cleanups.length - 1; i >= 0; i--) {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1297
|
+
for (let i = this.cleanups.length - 1; i >= 0; i--) try {
|
|
1298
|
+
await this.cleanups[i]?.(result);
|
|
1299
|
+
} catch {}
|
|
1175
1300
|
}
|
|
1176
1301
|
};
|
|
1177
1302
|
/**
|