@pumped-fn/lite 2.1.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 +41 -0
- package/dist/index.cjs +198 -108
- package/dist/index.d.cts +22 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +22 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +198 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @pumped-fn/lite
|
|
2
2
|
|
|
3
|
+
## 2.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8ed17e7: - Fix watch and invalidation edge cases in `@pumped-fn/lite` by aligning `select()` with `Object.is`, snapshotting select listeners during notification, making watch option typing match the runtime contract, and surfacing invalidation-chain failures from `flush()` instead of leaking them as background rejections.
|
|
8
|
+
- Fix `@pumped-fn/lite-react` hook refresh behavior by keeping stale values visible during re-resolution, recomputing `useSelect` snapshots when selector or equality semantics change, tracking pending promises per controller, and suppressing non-Suspense `unhandledRejection` leaks on failed refreshes.
|
|
9
|
+
|
|
10
|
+
## 2.1.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 2ce41fc: Fix 16 bugs found via adversarial triage + 5 rounds of Codex review:
|
|
15
|
+
|
|
16
|
+
**Correctness**
|
|
17
|
+
|
|
18
|
+
- `preset(atom, undefined)` now works — uses `has()` check instead of `!== undefined`
|
|
19
|
+
- `seekHas()` traverses parent chain via interface dispatch, not `instanceof`
|
|
20
|
+
- Error-path `pendingSet` only reschedules value-type sets — `fn(undefined)` no longer produces garbage
|
|
21
|
+
- `doInvalidateSequential` swallows resolve errors when pending operations exist
|
|
22
|
+
- Resource cycle detection moved to per-execution-chain WeakMap — fixes false errors with `ctx.exec()`
|
|
23
|
+
- Resource inflight check runs before circular check — sibling `ctx.exec()` no longer false-positives
|
|
24
|
+
|
|
25
|
+
**Reactive system**
|
|
26
|
+
|
|
27
|
+
- `set()`/`update()` pendingSet path skips cleanups — watch deps preserved since factory doesn't re-run
|
|
28
|
+
- Unconditional `invalidationChain.delete()` in pendingSet fast-path — prevents self-loops
|
|
29
|
+
- Copy-on-iterate on all 4 listener iteration sites — unsub during notification no longer drops siblings
|
|
30
|
+
|
|
31
|
+
**Lifecycle**
|
|
32
|
+
|
|
33
|
+
- `dispose()` awaits `chainPromise` before setting `disposed` — drains pending invalidation chain
|
|
34
|
+
- `resolve()`, `controller()`, `createContext()` throw after dispose
|
|
35
|
+
- `release()` cleans up dependents + schedules GC on freed deps
|
|
36
|
+
|
|
37
|
+
**SelectHandle**
|
|
38
|
+
|
|
39
|
+
- Eager subscription in constructor — tracks changes without active subscribers
|
|
40
|
+
- `dispose()` method for explicit teardown
|
|
41
|
+
- Re-subscribe refreshes cached value after auto-cleanup
|
|
42
|
+
- Added `seekHas()` to `ContextData` interface, `dispose()` to `SelectHandle` interface
|
|
43
|
+
|
|
3
44
|
## 2.1.0
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -88,7 +88,7 @@ function getAtomsForTag(tag$1) {
|
|
|
88
88
|
return live;
|
|
89
89
|
}
|
|
90
90
|
function tag(options) {
|
|
91
|
-
const key = Symbol
|
|
91
|
+
const key = Symbol(`@pumped-fn/lite/tag/${options.label}`);
|
|
92
92
|
const hasDefault = "default" in options;
|
|
93
93
|
const defaultValue = hasDefault ? options.default : void 0;
|
|
94
94
|
const parse = options.parse;
|
|
@@ -425,12 +425,14 @@ function isResource(value) {
|
|
|
425
425
|
//#endregion
|
|
426
426
|
//#region src/service.ts
|
|
427
427
|
function service(config) {
|
|
428
|
-
|
|
428
|
+
const atomInstance = {
|
|
429
429
|
[atomSymbol]: true,
|
|
430
430
|
factory: config.factory,
|
|
431
431
|
deps: config.deps,
|
|
432
432
|
tags: config.tags
|
|
433
433
|
};
|
|
434
|
+
if (config.tags?.length) registerAtomToTags(atomInstance, config.tags);
|
|
435
|
+
return atomInstance;
|
|
434
436
|
}
|
|
435
437
|
|
|
436
438
|
//#endregion
|
|
@@ -446,7 +448,7 @@ function getResourceKey(resource$1) {
|
|
|
446
448
|
return key;
|
|
447
449
|
}
|
|
448
450
|
const inflightResources = /* @__PURE__ */ new WeakMap();
|
|
449
|
-
const
|
|
451
|
+
const resolvingResourcesMap = /* @__PURE__ */ new WeakMap();
|
|
450
452
|
var ContextDataImpl = class {
|
|
451
453
|
map = /* @__PURE__ */ new Map();
|
|
452
454
|
constructor(parentData) {
|
|
@@ -471,6 +473,10 @@ var ContextDataImpl = class {
|
|
|
471
473
|
if (this.map.has(key)) return this.map.get(key);
|
|
472
474
|
return this.parentData?.seek(key);
|
|
473
475
|
}
|
|
476
|
+
seekHas(key) {
|
|
477
|
+
if (this.map.has(key)) return true;
|
|
478
|
+
return this.parentData?.seekHas(key) ?? false;
|
|
479
|
+
}
|
|
474
480
|
getTag(tag$1) {
|
|
475
481
|
return this.map.get(tag$1.key);
|
|
476
482
|
}
|
|
@@ -516,6 +522,16 @@ var SelectHandleImpl = class {
|
|
|
516
522
|
return this.currentValue;
|
|
517
523
|
}
|
|
518
524
|
subscribe(listener) {
|
|
525
|
+
if (!this.ctrlUnsub) {
|
|
526
|
+
this.currentValue = this.selector(this.ctrl.get());
|
|
527
|
+
this.ctrlUnsub = this.ctrl.on("resolved", () => {
|
|
528
|
+
const nextValue = this.selector(this.ctrl.get());
|
|
529
|
+
if (!this.eq(this.currentValue, nextValue)) {
|
|
530
|
+
this.currentValue = nextValue;
|
|
531
|
+
this.notifyListeners();
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
519
535
|
this.listeners.add(listener);
|
|
520
536
|
return () => {
|
|
521
537
|
this.listeners.delete(listener);
|
|
@@ -523,12 +539,15 @@ var SelectHandleImpl = class {
|
|
|
523
539
|
};
|
|
524
540
|
}
|
|
525
541
|
notifyListeners() {
|
|
526
|
-
for (const listener of this.listeners) listener();
|
|
542
|
+
for (const listener of [...this.listeners]) listener();
|
|
543
|
+
}
|
|
544
|
+
dispose() {
|
|
545
|
+
this.listeners.clear();
|
|
546
|
+
this.cleanup();
|
|
527
547
|
}
|
|
528
548
|
cleanup() {
|
|
529
549
|
this.ctrlUnsub?.();
|
|
530
550
|
this.ctrlUnsub = null;
|
|
531
|
-
this.listeners.clear();
|
|
532
551
|
}
|
|
533
552
|
};
|
|
534
553
|
var ControllerImpl = class {
|
|
@@ -577,11 +596,15 @@ var ScopeImpl = class {
|
|
|
577
596
|
invalidationScheduled = false;
|
|
578
597
|
invalidationChain = null;
|
|
579
598
|
chainPromise = null;
|
|
599
|
+
chainError = null;
|
|
580
600
|
initialized = false;
|
|
601
|
+
disposed = false;
|
|
581
602
|
controllers = /* @__PURE__ */ new Map();
|
|
582
603
|
gcOptions;
|
|
583
604
|
extensions;
|
|
584
605
|
tags;
|
|
606
|
+
resolveExts;
|
|
607
|
+
execExts;
|
|
585
608
|
ready;
|
|
586
609
|
scheduleInvalidation(atom$1) {
|
|
587
610
|
const entry = this.cache.get(atom$1);
|
|
@@ -594,16 +617,22 @@ var ScopeImpl = class {
|
|
|
594
617
|
if (!this.chainPromise) {
|
|
595
618
|
this.invalidationChain = /* @__PURE__ */ new Set();
|
|
596
619
|
this.invalidationScheduled = true;
|
|
597
|
-
this.
|
|
598
|
-
|
|
599
|
-
|
|
620
|
+
this.chainError = null;
|
|
621
|
+
this.chainPromise = (async () => {
|
|
622
|
+
await new Promise((resolve) => {
|
|
623
|
+
queueMicrotask(resolve);
|
|
600
624
|
});
|
|
601
|
-
|
|
625
|
+
try {
|
|
626
|
+
await this.processInvalidationChain();
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if (this.chainError === null) this.chainError = error;
|
|
629
|
+
}
|
|
630
|
+
})();
|
|
602
631
|
}
|
|
603
632
|
}
|
|
604
633
|
async processInvalidationChain() {
|
|
605
634
|
try {
|
|
606
|
-
while (this.invalidationQueue.size > 0) {
|
|
635
|
+
while (this.invalidationQueue.size > 0 && !this.disposed) {
|
|
607
636
|
const atom$1 = this.invalidationQueue.values().next().value;
|
|
608
637
|
this.invalidationQueue.delete(atom$1);
|
|
609
638
|
if (this.invalidationChain.has(atom$1)) {
|
|
@@ -624,6 +653,8 @@ var ScopeImpl = class {
|
|
|
624
653
|
constructor(options) {
|
|
625
654
|
this.extensions = options?.extensions ?? [];
|
|
626
655
|
this.tags = options?.tags ?? [];
|
|
656
|
+
this.resolveExts = this.extensions.filter((e) => e.wrapResolve);
|
|
657
|
+
this.execExts = this.extensions.filter((e) => e.wrapExec);
|
|
627
658
|
for (const p of options?.presets ?? []) this.presets.set(p.target, p.value);
|
|
628
659
|
this.gcOptions = {
|
|
629
660
|
enabled: options?.gc?.enabled ?? true,
|
|
@@ -716,21 +747,21 @@ var ScopeImpl = class {
|
|
|
716
747
|
const entry = this.cache.get(atom$1);
|
|
717
748
|
if (!entry) return;
|
|
718
749
|
const eventListeners = entry.listeners.get(event);
|
|
719
|
-
if (eventListeners) for (const listener of eventListeners) listener();
|
|
750
|
+
if (eventListeners?.size) for (const listener of [...eventListeners]) listener();
|
|
720
751
|
const allListeners = entry.listeners.get("*");
|
|
721
|
-
if (allListeners) for (const listener of allListeners) listener();
|
|
752
|
+
if (allListeners?.size) for (const listener of [...allListeners]) listener();
|
|
722
753
|
}
|
|
723
754
|
notifyAllListeners(atom$1) {
|
|
724
755
|
const entry = this.cache.get(atom$1);
|
|
725
756
|
if (!entry) return;
|
|
726
757
|
const allListeners = entry.listeners.get("*");
|
|
727
|
-
if (allListeners) for (const listener of allListeners) listener();
|
|
758
|
+
if (allListeners?.size) for (const listener of [...allListeners]) listener();
|
|
728
759
|
}
|
|
729
760
|
emitStateChange(state, atom$1) {
|
|
730
761
|
const stateMap = this.stateListeners.get(state);
|
|
731
762
|
if (stateMap) {
|
|
732
763
|
const listeners = stateMap.get(atom$1);
|
|
733
|
-
if (listeners) for (const listener of listeners) listener();
|
|
764
|
+
if (listeners?.size) for (const listener of [...listeners]) listener();
|
|
734
765
|
}
|
|
735
766
|
}
|
|
736
767
|
on(event, atom$1, listener) {
|
|
@@ -756,14 +787,15 @@ var ScopeImpl = class {
|
|
|
756
787
|
};
|
|
757
788
|
}
|
|
758
789
|
async resolve(atom$1) {
|
|
790
|
+
if (this.disposed) throw new Error("Scope is disposed");
|
|
759
791
|
if (!this.initialized) await this.ready;
|
|
760
792
|
const entry = this.cache.get(atom$1);
|
|
761
793
|
if (entry?.state === "resolved") return entry.value;
|
|
762
794
|
const pendingPromise = this.pending.get(atom$1);
|
|
763
795
|
if (pendingPromise) return pendingPromise;
|
|
764
796
|
if (this.resolving.has(atom$1)) throw new Error("Circular dependency detected");
|
|
765
|
-
|
|
766
|
-
|
|
797
|
+
if (this.presets.has(atom$1)) {
|
|
798
|
+
const presetValue = this.presets.get(atom$1);
|
|
767
799
|
if (isAtom(presetValue)) return this.resolve(presetValue);
|
|
768
800
|
const newEntry = this.getOrCreateEntry(atom$1);
|
|
769
801
|
newEntry.state = "resolved";
|
|
@@ -786,7 +818,9 @@ var ScopeImpl = class {
|
|
|
786
818
|
async doResolve(atom$1) {
|
|
787
819
|
const entry = this.getOrCreateEntry(atom$1);
|
|
788
820
|
if (!(entry.state === "resolving")) {
|
|
789
|
-
for (let i = entry.cleanups.length - 1; i >= 0; i--)
|
|
821
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
|
|
822
|
+
await entry.cleanups[i]?.();
|
|
823
|
+
} catch {}
|
|
790
824
|
entry.cleanups = [];
|
|
791
825
|
entry.state = "resolving";
|
|
792
826
|
this.emitStateChange("resolving", atom$1);
|
|
@@ -806,7 +840,7 @@ var ScopeImpl = class {
|
|
|
806
840
|
};
|
|
807
841
|
const factory = atom$1.factory;
|
|
808
842
|
const doResolve = async () => {
|
|
809
|
-
if (atom$1.deps
|
|
843
|
+
if (atom$1.deps) return factory(ctx, resolvedDeps);
|
|
810
844
|
else return factory(ctx);
|
|
811
845
|
};
|
|
812
846
|
try {
|
|
@@ -842,88 +876,103 @@ var ScopeImpl = class {
|
|
|
842
876
|
entry.pendingInvalidate = false;
|
|
843
877
|
this.invalidationChain?.delete(atom$1);
|
|
844
878
|
this.scheduleInvalidation(atom$1);
|
|
845
|
-
}
|
|
879
|
+
} else if (entry.pendingSet && "value" in entry.pendingSet) {
|
|
880
|
+
this.invalidationChain?.delete(atom$1);
|
|
881
|
+
this.scheduleInvalidation(atom$1);
|
|
882
|
+
} else entry.pendingSet = void 0;
|
|
846
883
|
throw entry.error;
|
|
847
884
|
}
|
|
848
885
|
}
|
|
849
886
|
async applyResolveExtensions(event, doResolve) {
|
|
850
887
|
let next = doResolve;
|
|
851
|
-
for (let i = this.
|
|
852
|
-
const ext = this.
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
next = ext.wrapResolve.bind(ext, currentNext, event);
|
|
856
|
-
}
|
|
888
|
+
for (let i = this.resolveExts.length - 1; i >= 0; i--) {
|
|
889
|
+
const ext = this.resolveExts[i];
|
|
890
|
+
const currentNext = next;
|
|
891
|
+
next = ext.wrapResolve.bind(ext, currentNext, event);
|
|
857
892
|
}
|
|
858
893
|
return next();
|
|
859
894
|
}
|
|
860
895
|
async resolveDeps(deps, ctx, dependentAtom) {
|
|
861
896
|
if (!deps) return {};
|
|
862
897
|
const result = {};
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
|
|
898
|
+
const parallel = [];
|
|
899
|
+
const deferredResources = [];
|
|
900
|
+
for (const key in deps) {
|
|
901
|
+
const dep = deps[key];
|
|
902
|
+
if (isAtom(dep)) parallel.push(this.resolve(dep).then((value) => {
|
|
903
|
+
result[key] = value;
|
|
904
|
+
if (dependentAtom) {
|
|
905
|
+
const depEntry = this.getEntry(dep);
|
|
906
|
+
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
907
|
+
}
|
|
908
|
+
}));
|
|
909
|
+
else if (isControllerDep(dep)) {
|
|
910
|
+
if (dep.watch) {
|
|
911
|
+
if (!dependentAtom) throw new Error("controller({ watch: true }) is only supported in atom dependencies");
|
|
912
|
+
if (!dep.resolve) throw new Error("controller({ watch: true }) requires resolve: true");
|
|
913
|
+
}
|
|
914
|
+
const ctrl = this.controller(dep.atom);
|
|
915
|
+
if (dep.resolve) parallel.push(ctrl.resolve().then(() => {
|
|
916
|
+
result[key] = ctrl;
|
|
917
|
+
if (dependentAtom) {
|
|
918
|
+
const depEntry = this.getEntry(dep.atom);
|
|
919
|
+
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
920
|
+
}
|
|
921
|
+
if (dep.watch) {
|
|
922
|
+
const eq = dep.eq ?? Object.is;
|
|
923
|
+
let prev = ctrl.get();
|
|
924
|
+
const unsub = this.on("resolved", dep.atom, () => {
|
|
925
|
+
const next = ctrl.get();
|
|
926
|
+
if (!eq(prev, next)) {
|
|
927
|
+
prev = next;
|
|
928
|
+
this.scheduleInvalidation(dependentAtom);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
const depEntry = this.getEntry(dependentAtom);
|
|
932
|
+
if (depEntry) depEntry.cleanups.push(unsub);
|
|
933
|
+
else unsub();
|
|
934
|
+
}
|
|
935
|
+
}));
|
|
936
|
+
else {
|
|
937
|
+
result[key] = ctrl;
|
|
938
|
+
if (dependentAtom) {
|
|
939
|
+
const depEntry = this.getEntry(dep.atom);
|
|
940
|
+
if (depEntry) depEntry.dependents.add(dependentAtom);
|
|
889
941
|
}
|
|
890
|
-
});
|
|
891
|
-
const depEntry = this.getEntry(dependentAtom);
|
|
892
|
-
if (depEntry) depEntry.cleanups.push(unsub);
|
|
893
|
-
else unsub();
|
|
894
|
-
}
|
|
895
|
-
} else if (tagExecutorSymbol in dep) {
|
|
896
|
-
const tagExecutor = dep;
|
|
897
|
-
switch (tagExecutor.mode) {
|
|
898
|
-
case "required": {
|
|
899
|
-
const value = ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags);
|
|
900
|
-
if (value !== void 0) result[key] = value;
|
|
901
|
-
else if (tagExecutor.tag.hasDefault) result[key] = tagExecutor.tag.defaultValue;
|
|
902
|
-
else throw new Error(`Tag "${tagExecutor.tag.label}" not found`);
|
|
903
|
-
break;
|
|
904
942
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
943
|
+
} else if (tagExecutorSymbol in dep) {
|
|
944
|
+
const tagExecutor = dep;
|
|
945
|
+
switch (tagExecutor.mode) {
|
|
946
|
+
case "required": {
|
|
947
|
+
const value = ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags);
|
|
948
|
+
if (value !== void 0) result[key] = value;
|
|
949
|
+
else if (tagExecutor.tag.hasDefault) result[key] = tagExecutor.tag.defaultValue;
|
|
950
|
+
else throw new Error(`Tag "${tagExecutor.tag.label}" not found`);
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case "optional":
|
|
954
|
+
result[key] = (ctx ? ctx.data.seekTag(tagExecutor.tag) : tagExecutor.tag.find(this.tags)) ?? tagExecutor.tag.defaultValue;
|
|
955
|
+
break;
|
|
956
|
+
case "all":
|
|
957
|
+
result[key] = ctx ? this.collectFromHierarchy(ctx, tagExecutor.tag) : tagExecutor.tag.collect(this.tags);
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
} else if (isResource(dep)) deferredResources.push([key, dep]);
|
|
961
|
+
}
|
|
962
|
+
if (parallel.length === 1) await parallel[0];
|
|
963
|
+
else if (parallel.length > 1) await Promise.all(parallel);
|
|
964
|
+
for (const [key, resource$1] of deferredResources) {
|
|
913
965
|
if (!ctx) throw new Error("Resource deps require an ExecutionContext");
|
|
914
|
-
const resource$1 = dep;
|
|
915
966
|
const resourceKey = getResourceKey(resource$1);
|
|
916
967
|
const storeCtx = ctx.parent ?? ctx;
|
|
917
968
|
if (storeCtx.data.has(resourceKey)) {
|
|
918
969
|
result[key] = storeCtx.data.get(resourceKey);
|
|
919
970
|
continue;
|
|
920
971
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
result[key] = existingSeek;
|
|
972
|
+
if (ctx.data.seekHas(resourceKey)) {
|
|
973
|
+
result[key] = ctx.data.seek(resourceKey);
|
|
924
974
|
continue;
|
|
925
975
|
}
|
|
926
|
-
if (resolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
|
|
927
976
|
let flights = inflightResources.get(storeCtx.data);
|
|
928
977
|
if (!flights) {
|
|
929
978
|
flights = /* @__PURE__ */ new Map();
|
|
@@ -934,8 +983,14 @@ var ScopeImpl = class {
|
|
|
934
983
|
result[key] = await inflight;
|
|
935
984
|
continue;
|
|
936
985
|
}
|
|
986
|
+
let localResolvingResources = resolvingResourcesMap.get(storeCtx.data);
|
|
987
|
+
if (!localResolvingResources) {
|
|
988
|
+
localResolvingResources = /* @__PURE__ */ new Set();
|
|
989
|
+
resolvingResourcesMap.set(storeCtx.data, localResolvingResources);
|
|
990
|
+
}
|
|
991
|
+
if (localResolvingResources.has(resourceKey)) throw new Error(`Circular resource dependency detected: ${resource$1.name ?? "anonymous"}`);
|
|
937
992
|
const resolve = async () => {
|
|
938
|
-
|
|
993
|
+
localResolvingResources.add(resourceKey);
|
|
939
994
|
try {
|
|
940
995
|
const resourceDeps = await this.resolveDeps(resource$1.deps, ctx);
|
|
941
996
|
const event = {
|
|
@@ -945,14 +1000,14 @@ var ScopeImpl = class {
|
|
|
945
1000
|
};
|
|
946
1001
|
const doResolve = async () => {
|
|
947
1002
|
const factory = resource$1.factory;
|
|
948
|
-
if (resource$1.deps
|
|
1003
|
+
if (resource$1.deps) return factory(storeCtx, resourceDeps);
|
|
949
1004
|
return factory(storeCtx);
|
|
950
1005
|
};
|
|
951
1006
|
const value = await this.applyResolveExtensions(event, doResolve);
|
|
952
1007
|
storeCtx.data.set(resourceKey, value);
|
|
953
1008
|
return value;
|
|
954
1009
|
} finally {
|
|
955
|
-
|
|
1010
|
+
localResolvingResources.delete(resourceKey);
|
|
956
1011
|
}
|
|
957
1012
|
};
|
|
958
1013
|
const promise = resolve();
|
|
@@ -976,6 +1031,7 @@ var ScopeImpl = class {
|
|
|
976
1031
|
return results;
|
|
977
1032
|
}
|
|
978
1033
|
controller(atom$1, options) {
|
|
1034
|
+
if (this.disposed) throw new Error("Scope is disposed");
|
|
979
1035
|
let ctrl = this.controllers.get(atom$1);
|
|
980
1036
|
if (!ctrl) {
|
|
981
1037
|
ctrl = new ControllerImpl(atom$1, this);
|
|
@@ -985,7 +1041,7 @@ var ScopeImpl = class {
|
|
|
985
1041
|
return ctrl;
|
|
986
1042
|
}
|
|
987
1043
|
select(atom$1, selector, options) {
|
|
988
|
-
return new SelectHandleImpl(this.controller(atom$1), selector, options?.eq ??
|
|
1044
|
+
return new SelectHandleImpl(this.controller(atom$1), selector, options?.eq ?? Object.is);
|
|
989
1045
|
}
|
|
990
1046
|
getFlowPreset(flow$1) {
|
|
991
1047
|
return this.presets.get(flow$1);
|
|
@@ -1029,10 +1085,27 @@ var ScopeImpl = class {
|
|
|
1029
1085
|
const previousValue = entry.value;
|
|
1030
1086
|
const pendingSet = entry.pendingSet;
|
|
1031
1087
|
entry.pendingSet = void 0;
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1088
|
+
if (pendingSet) {
|
|
1089
|
+
entry.state = "resolving";
|
|
1090
|
+
entry.value = previousValue;
|
|
1091
|
+
entry.error = void 0;
|
|
1092
|
+
entry.pendingInvalidate = false;
|
|
1093
|
+
this.pending.delete(atom$1);
|
|
1094
|
+
this.resolving.delete(atom$1);
|
|
1095
|
+
this.emitStateChange("resolving", atom$1);
|
|
1096
|
+
this.notifyListeners(atom$1, "resolving");
|
|
1097
|
+
if ("value" in pendingSet) entry.value = pendingSet.value;
|
|
1098
|
+
else entry.value = pendingSet.fn(previousValue);
|
|
1099
|
+
entry.state = "resolved";
|
|
1100
|
+
entry.hasValue = true;
|
|
1101
|
+
this.emitStateChange("resolved", atom$1);
|
|
1102
|
+
this.notifyListeners(atom$1, "resolved");
|
|
1103
|
+
this.invalidationChain?.delete(atom$1);
|
|
1104
|
+
return;
|
|
1035
1105
|
}
|
|
1106
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
|
|
1107
|
+
await entry.cleanups[i]?.();
|
|
1108
|
+
} catch {}
|
|
1036
1109
|
entry.cleanups = [];
|
|
1037
1110
|
entry.state = "resolving";
|
|
1038
1111
|
entry.value = previousValue;
|
|
@@ -1042,16 +1115,11 @@ var ScopeImpl = class {
|
|
|
1042
1115
|
this.resolving.delete(atom$1);
|
|
1043
1116
|
this.emitStateChange("resolving", atom$1);
|
|
1044
1117
|
this.notifyListeners(atom$1, "resolving");
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
entry.
|
|
1049
|
-
entry.hasValue = true;
|
|
1050
|
-
this.emitStateChange("resolved", atom$1);
|
|
1051
|
-
this.notifyListeners(atom$1, "resolved");
|
|
1052
|
-
return;
|
|
1118
|
+
try {
|
|
1119
|
+
await this.resolve(atom$1);
|
|
1120
|
+
} catch (e) {
|
|
1121
|
+
if (!entry.pendingSet && !entry.pendingInvalidate) throw e;
|
|
1053
1122
|
}
|
|
1054
|
-
await this.resolve(atom$1);
|
|
1055
1123
|
}
|
|
1056
1124
|
async release(atom$1) {
|
|
1057
1125
|
const entry = this.cache.get(atom$1);
|
|
@@ -1060,14 +1128,33 @@ var ScopeImpl = class {
|
|
|
1060
1128
|
clearTimeout(entry.gcScheduled);
|
|
1061
1129
|
entry.gcScheduled = null;
|
|
1062
1130
|
}
|
|
1063
|
-
for (let i = entry.cleanups.length - 1; i >= 0; i--) {
|
|
1064
|
-
|
|
1065
|
-
|
|
1131
|
+
for (let i = entry.cleanups.length - 1; i >= 0; i--) try {
|
|
1132
|
+
await entry.cleanups[i]?.();
|
|
1133
|
+
} catch {}
|
|
1134
|
+
if (atom$1.deps) for (const dep of Object.values(atom$1.deps)) {
|
|
1135
|
+
const depAtom = isAtom(dep) ? dep : isControllerDep(dep) ? dep.atom : null;
|
|
1136
|
+
if (!depAtom) continue;
|
|
1137
|
+
const depEntry = this.cache.get(depAtom);
|
|
1138
|
+
if (depEntry) {
|
|
1139
|
+
depEntry.dependents.delete(atom$1);
|
|
1140
|
+
this.maybeScheduleGC(depAtom);
|
|
1141
|
+
}
|
|
1066
1142
|
}
|
|
1067
1143
|
this.cache.delete(atom$1);
|
|
1068
1144
|
this.controllers.delete(atom$1);
|
|
1145
|
+
for (const [state, stateMap] of this.stateListeners) {
|
|
1146
|
+
stateMap.delete(atom$1);
|
|
1147
|
+
if (stateMap.size === 0) this.stateListeners.delete(state);
|
|
1148
|
+
}
|
|
1069
1149
|
}
|
|
1070
1150
|
async dispose() {
|
|
1151
|
+
if (this.chainPromise) try {
|
|
1152
|
+
await this.chainPromise;
|
|
1153
|
+
} catch {}
|
|
1154
|
+
this.disposed = true;
|
|
1155
|
+
this.invalidationQueue.clear();
|
|
1156
|
+
this.invalidationChain = null;
|
|
1157
|
+
this.chainPromise = null;
|
|
1071
1158
|
for (const ext of this.extensions) if (ext.dispose) await ext.dispose(this);
|
|
1072
1159
|
for (const entry of this.cache.values()) if (entry.gcScheduled) {
|
|
1073
1160
|
clearTimeout(entry.gcScheduled);
|
|
@@ -1078,8 +1165,14 @@ var ScopeImpl = class {
|
|
|
1078
1165
|
}
|
|
1079
1166
|
async flush() {
|
|
1080
1167
|
if (this.chainPromise) await this.chainPromise;
|
|
1168
|
+
if (this.chainError !== null) {
|
|
1169
|
+
const error = this.chainError;
|
|
1170
|
+
this.chainError = null;
|
|
1171
|
+
throw error;
|
|
1172
|
+
}
|
|
1081
1173
|
}
|
|
1082
1174
|
createContext(options) {
|
|
1175
|
+
if (this.disposed) throw new Error("Scope is disposed");
|
|
1083
1176
|
const ctx = new ExecutionContextImpl(this, options);
|
|
1084
1177
|
for (const tagged of options?.tags ?? []) ctx.data.set(tagged.key, tagged.value);
|
|
1085
1178
|
for (const tagged of this.tags) if (!ctx.data.has(tagged.key)) ctx.data.set(tagged.key, tagged.value);
|
|
@@ -1173,7 +1266,7 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1173
1266
|
const resolvedDeps = await this.scope.resolveDeps(flow$1.deps, this);
|
|
1174
1267
|
const factory = flow$1.factory;
|
|
1175
1268
|
const doExec = async () => {
|
|
1176
|
-
if (flow$1.deps
|
|
1269
|
+
if (flow$1.deps) return factory(this, resolvedDeps);
|
|
1177
1270
|
else return factory(this);
|
|
1178
1271
|
};
|
|
1179
1272
|
return this.applyExecExtensions(flow$1, doExec);
|
|
@@ -1189,12 +1282,10 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1189
1282
|
}
|
|
1190
1283
|
async applyExecExtensions(target, doExec) {
|
|
1191
1284
|
let next = doExec;
|
|
1192
|
-
for (let i = this.scope.
|
|
1193
|
-
const ext = this.scope.
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
next = ext.wrapExec.bind(ext, currentNext, target, this);
|
|
1197
|
-
}
|
|
1285
|
+
for (let i = this.scope.execExts.length - 1; i >= 0; i--) {
|
|
1286
|
+
const ext = this.scope.execExts[i];
|
|
1287
|
+
const currentNext = next;
|
|
1288
|
+
next = ext.wrapExec.bind(ext, currentNext, target, this);
|
|
1198
1289
|
}
|
|
1199
1290
|
return next();
|
|
1200
1291
|
}
|
|
@@ -1204,10 +1295,9 @@ var ExecutionContextImpl = class ExecutionContextImpl {
|
|
|
1204
1295
|
async close(result = { ok: true }) {
|
|
1205
1296
|
if (this.closed) return;
|
|
1206
1297
|
this.closed = true;
|
|
1207
|
-
for (let i = this.cleanups.length - 1; i >= 0; i--) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1298
|
+
for (let i = this.cleanups.length - 1; i >= 0; i--) try {
|
|
1299
|
+
await this.cleanups[i]?.(result);
|
|
1300
|
+
} catch {}
|
|
1211
1301
|
}
|
|
1212
1302
|
};
|
|
1213
1303
|
/**
|
package/dist/index.d.cts
CHANGED
|
@@ -89,6 +89,10 @@ declare namespace Lite {
|
|
|
89
89
|
* Returns first match or undefined (ignores tag defaults).
|
|
90
90
|
*/
|
|
91
91
|
seekTag<T>(tag: Tag<T, boolean>): T | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Check if key exists locally or in parent chain.
|
|
94
|
+
*/
|
|
95
|
+
seekHas(key: string | symbol): boolean;
|
|
92
96
|
/** Get value by tag, returns undefined if not stored */
|
|
93
97
|
getTag<T>(tag: Tag<T, boolean>): T | undefined;
|
|
94
98
|
/** Set value by tag */
|
|
@@ -198,6 +202,7 @@ declare namespace Lite {
|
|
|
198
202
|
interface SelectHandle<S> {
|
|
199
203
|
get(): S;
|
|
200
204
|
subscribe(listener: () => void): () => void;
|
|
205
|
+
dispose(): void;
|
|
201
206
|
}
|
|
202
207
|
interface Tag<T, HasDefault extends boolean = false> {
|
|
203
208
|
readonly [tagSymbol]: true;
|
|
@@ -236,11 +241,23 @@ declare namespace Lite {
|
|
|
236
241
|
interface ControllerOptions {
|
|
237
242
|
resolve?: boolean;
|
|
238
243
|
}
|
|
239
|
-
|
|
240
|
-
resolve
|
|
241
|
-
watch
|
|
242
|
-
eq
|
|
243
|
-
}
|
|
244
|
+
type ControllerDepOptions<T> = {
|
|
245
|
+
resolve: true;
|
|
246
|
+
watch: true;
|
|
247
|
+
eq: (a: T, b: T) => boolean;
|
|
248
|
+
} | {
|
|
249
|
+
resolve: true;
|
|
250
|
+
watch: true;
|
|
251
|
+
eq?: never;
|
|
252
|
+
} | {
|
|
253
|
+
resolve: true;
|
|
254
|
+
watch?: never;
|
|
255
|
+
eq?: never;
|
|
256
|
+
} | {
|
|
257
|
+
resolve?: never;
|
|
258
|
+
watch?: never;
|
|
259
|
+
eq?: never;
|
|
260
|
+
};
|
|
244
261
|
interface Typed<T> {
|
|
245
262
|
readonly [typedSymbol]: true;
|
|
246
263
|
}
|