@typeonce/effect-machine 0.27.1 → 0.29.0
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/README.md +41 -4
- package/dist/internal/machine/atom.d.ts +9 -0
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +80 -7
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +144 -17
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +77 -17
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/effect-atom-react.md +218 -155
- package/docs/machine-review.md +41 -23
- package/package.json +1 -1
- package/src/internal/machine/atom.ts +185 -19
- package/src/unstable/reactivity/AtomMachine.ts +479 -70
|
@@ -8,6 +8,7 @@ import * as Data from "effect/Data"
|
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
9
|
import * as Equal from "effect/Equal"
|
|
10
10
|
import * as Fiber from "effect/Fiber"
|
|
11
|
+
import * as MutableHashMap from "effect/MutableHashMap"
|
|
11
12
|
import * as Option from "effect/Option"
|
|
12
13
|
import type * as Schema from "effect/Schema"
|
|
13
14
|
import type * as Scope from "effect/Scope"
|
|
@@ -78,6 +79,47 @@ const preparedByMachineAtom = new WeakMap<
|
|
|
78
79
|
Atom.Atom<AsyncResult.AsyncResult<Machine.Prepared<any, any, any, any, any, any, any>, any>>
|
|
79
80
|
>()
|
|
80
81
|
|
|
82
|
+
type WeakFamilyEntry<Value extends object> = {
|
|
83
|
+
readonly ref: WeakRef<Value>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// This follows Atom.family, but cleanup is generation-aware. A finalizer for a
|
|
87
|
+
// collected value must not remove a newer value installed for the same key.
|
|
88
|
+
const retainedFamily = <Key, Value extends object>(
|
|
89
|
+
makeValue: (key: Key) => Value
|
|
90
|
+
): (key: Key) => Value => {
|
|
91
|
+
if (typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined") {
|
|
92
|
+
return Atom.family(makeValue)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const values = MutableHashMap.empty<Key, WeakFamilyEntry<Value>>()
|
|
96
|
+
const registry = new FinalizationRegistry<{
|
|
97
|
+
readonly key: Key
|
|
98
|
+
readonly entry: WeakFamilyEntry<Value>
|
|
99
|
+
}>(({ entry, key }) => {
|
|
100
|
+
const current = MutableHashMap.get(values, key)
|
|
101
|
+
if (Option.isSome(current) && current.value === entry) {
|
|
102
|
+
MutableHashMap.remove(values, key)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return (key) => {
|
|
107
|
+
const current = MutableHashMap.get(values, key)
|
|
108
|
+
if (Option.isSome(current)) {
|
|
109
|
+
const value = current.value.ref.deref()
|
|
110
|
+
if (value !== undefined) {
|
|
111
|
+
return value
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const value = makeValue(key)
|
|
116
|
+
const entry = { ref: new WeakRef(value) }
|
|
117
|
+
MutableHashMap.set(values, key, entry)
|
|
118
|
+
registry.register(value, { key, entry })
|
|
119
|
+
return value
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
81
123
|
const runMachineAtomEffect = <State, Event, Error, Output, Emitted, StartError, Requirements>(
|
|
82
124
|
get: Atom.AtomContext,
|
|
83
125
|
start: Effect.Effect<Machine.MachineRef<State, Event, Error, Output, Emitted>, StartError, Requirements>
|
|
@@ -515,6 +557,41 @@ const selectSnapshotByPath = <
|
|
|
515
557
|
): Option.Option<SnapshotByIdentifier<State, Path>> =>
|
|
516
558
|
Topology.getSnapshotByPath(snapshot, path) as Option.Option<SnapshotByIdentifier<State, Path>>
|
|
517
559
|
|
|
560
|
+
type SelectorKind =
|
|
561
|
+
| "matches"
|
|
562
|
+
| "matchesChild"
|
|
563
|
+
| "select"
|
|
564
|
+
| "selectChild"
|
|
565
|
+
| "selectSnapshot"
|
|
566
|
+
| "selectSnapshotChild"
|
|
567
|
+
|
|
568
|
+
const selectorsByBridge = new WeakMap<object, Map<SelectorKind, Map<string, Atom.Atom<any>>>>()
|
|
569
|
+
|
|
570
|
+
const cachedSelector = <A>(
|
|
571
|
+
bridge: object,
|
|
572
|
+
kind: SelectorKind,
|
|
573
|
+
path: string,
|
|
574
|
+
make: () => Atom.Atom<A>
|
|
575
|
+
): Atom.Atom<A> => {
|
|
576
|
+
let selectorsByKind = selectorsByBridge.get(bridge)
|
|
577
|
+
if (selectorsByKind === undefined) {
|
|
578
|
+
selectorsByKind = new Map()
|
|
579
|
+
selectorsByBridge.set(bridge, selectorsByKind)
|
|
580
|
+
}
|
|
581
|
+
let selectorsByPath = selectorsByKind.get(kind)
|
|
582
|
+
if (selectorsByPath === undefined) {
|
|
583
|
+
selectorsByPath = new Map()
|
|
584
|
+
selectorsByKind.set(kind, selectorsByPath)
|
|
585
|
+
}
|
|
586
|
+
const cached = selectorsByPath.get(path)
|
|
587
|
+
if (cached !== undefined) {
|
|
588
|
+
return cached as Atom.Atom<A>
|
|
589
|
+
}
|
|
590
|
+
const selector = make()
|
|
591
|
+
selectorsByPath.set(path, selector)
|
|
592
|
+
return selector
|
|
593
|
+
}
|
|
594
|
+
|
|
518
595
|
export const select = <
|
|
519
596
|
State extends Machine.Machine.AtomicSnapshot<string, unknown>,
|
|
520
597
|
Event,
|
|
@@ -529,8 +606,14 @@ export const select = <
|
|
|
529
606
|
): Atom.Atom<
|
|
530
607
|
AsyncResult.AsyncResult<Option.Option<SnapshotValueByIdentifier<State, Path>>, StartError | Error>
|
|
531
608
|
> =>
|
|
532
|
-
|
|
533
|
-
|
|
609
|
+
cachedSelector(
|
|
610
|
+
self,
|
|
611
|
+
"select",
|
|
612
|
+
path,
|
|
613
|
+
() =>
|
|
614
|
+
Atom.mapResult(self.result, (snapshot) => selectValueByPath(snapshot, path)).pipe(
|
|
615
|
+
Atom.withEquality(Equal.equals)
|
|
616
|
+
)
|
|
534
617
|
)
|
|
535
618
|
|
|
536
619
|
export const selectSnapshot = <
|
|
@@ -547,8 +630,14 @@ export const selectSnapshot = <
|
|
|
547
630
|
): Atom.Atom<
|
|
548
631
|
AsyncResult.AsyncResult<Option.Option<SnapshotByIdentifier<State, Path>>, StartError | Error>
|
|
549
632
|
> =>
|
|
550
|
-
|
|
551
|
-
|
|
633
|
+
cachedSelector(
|
|
634
|
+
self,
|
|
635
|
+
"selectSnapshot",
|
|
636
|
+
path,
|
|
637
|
+
() =>
|
|
638
|
+
Atom.mapResult(self.result, (snapshot) => selectSnapshotByPath(snapshot, path)).pipe(
|
|
639
|
+
Atom.withEquality(Equal.equals)
|
|
640
|
+
)
|
|
552
641
|
)
|
|
553
642
|
|
|
554
643
|
export const selectChild = <
|
|
@@ -564,10 +653,11 @@ export const selectChild = <
|
|
|
564
653
|
StartError | RefError<Machine.ChildMachine.Ref<Child>>
|
|
565
654
|
>
|
|
566
655
|
> =>
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
656
|
+
cachedSelector(self, "selectChild", path, () =>
|
|
657
|
+
Atom.mapResult(
|
|
658
|
+
self.result,
|
|
659
|
+
Option.flatMap((snapshot) => selectValueByPath(snapshot, path))
|
|
660
|
+
).pipe(Atom.withEquality(Equal.equals)))
|
|
571
661
|
|
|
572
662
|
export const selectSnapshotChild = <
|
|
573
663
|
Child extends Machine.ChildMachine.Any,
|
|
@@ -582,10 +672,11 @@ export const selectSnapshotChild = <
|
|
|
582
672
|
StartError | RefError<Machine.ChildMachine.Ref<Child>>
|
|
583
673
|
>
|
|
584
674
|
> =>
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
675
|
+
cachedSelector(self, "selectSnapshotChild", path, () =>
|
|
676
|
+
Atom.mapResult(
|
|
677
|
+
self.result,
|
|
678
|
+
Option.flatMap((snapshot) => selectSnapshotByPath(snapshot, path))
|
|
679
|
+
).pipe(Atom.withEquality(Equal.equals)))
|
|
589
680
|
|
|
590
681
|
export const matches = <
|
|
591
682
|
State extends Machine.Machine.AtomicSnapshot<string, unknown>,
|
|
@@ -599,8 +690,14 @@ export const matches = <
|
|
|
599
690
|
self: MachineAtom<State, Event, Error, Output, StartError, Emitted>,
|
|
600
691
|
path: Path
|
|
601
692
|
): Atom.Atom<AsyncResult.AsyncResult<boolean, StartError | Error>> =>
|
|
602
|
-
|
|
603
|
-
|
|
693
|
+
cachedSelector(
|
|
694
|
+
self,
|
|
695
|
+
"matches",
|
|
696
|
+
path,
|
|
697
|
+
() =>
|
|
698
|
+
Atom.mapResult(self.result, (snapshot) => Option.isSome(Topology.getSnapshotByPath(snapshot, path))).pipe(
|
|
699
|
+
Atom.withEquality(Equal.equals)
|
|
700
|
+
)
|
|
604
701
|
)
|
|
605
702
|
|
|
606
703
|
export const matchesChild = <
|
|
@@ -613,10 +710,11 @@ export const matchesChild = <
|
|
|
613
710
|
): Atom.Atom<
|
|
614
711
|
AsyncResult.AsyncResult<boolean, StartError | RefError<Machine.ChildMachine.Ref<Child>>>
|
|
615
712
|
> =>
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
713
|
+
cachedSelector(self, "matchesChild", path, () =>
|
|
714
|
+
Atom.mapResult(
|
|
715
|
+
self.result,
|
|
716
|
+
Option.exists((snapshot) => Option.isSome(Topology.getSnapshotByPath(snapshot, path)))
|
|
717
|
+
).pipe(Atom.withEquality(Equal.equals)))
|
|
620
718
|
|
|
621
719
|
type MachineResumeRequirementsOf<M extends Machine.Machine.Any> = MachineResumeRequirements<
|
|
622
720
|
Machine.Machine.Services<M>,
|
|
@@ -738,6 +836,69 @@ const resumeWithRuntime = (
|
|
|
738
836
|
return makeFromRefAtom(ref as any)
|
|
739
837
|
}
|
|
740
838
|
|
|
839
|
+
type FamilyBridge = MachineAtom<any, never, any, any, any, any> | ChildMachineAtom<any, any>
|
|
840
|
+
|
|
841
|
+
type FamilyOptions = {
|
|
842
|
+
readonly atoms: Readonly<Record<string, (bridge: FamilyBridge) => Atom.Atom<any>>>
|
|
843
|
+
readonly label?: (key: any, atomName: string) => string | undefined
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const retainFamilyOwner = <Source extends Atom.Atom<any>>(
|
|
847
|
+
owner: { readonly bridge: FamilyBridge },
|
|
848
|
+
source: Source,
|
|
849
|
+
label: string | undefined
|
|
850
|
+
): Atom.WithoutSerializable<Source> => {
|
|
851
|
+
const retained = { owner, source }
|
|
852
|
+
let atom: Atom.Atom<any> = Atom.transform(
|
|
853
|
+
source,
|
|
854
|
+
(get) => get(retained.source),
|
|
855
|
+
{ initialValueTarget: source }
|
|
856
|
+
).pipe(
|
|
857
|
+
Atom.withEquality((value, next) => retained.source.equals(value, next))
|
|
858
|
+
)
|
|
859
|
+
if (label !== undefined) {
|
|
860
|
+
atom = atom.pipe(Atom.withLabel(label))
|
|
861
|
+
}
|
|
862
|
+
return atom as unknown as Atom.WithoutSerializable<Source>
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const makeFamily = (
|
|
866
|
+
makeBridge: (key: any) => FamilyBridge,
|
|
867
|
+
options: FamilyOptions
|
|
868
|
+
): Readonly<Record<string, (key: any) => Atom.Atom<any>>> => {
|
|
869
|
+
const owners = retainedFamily((key: any) => ({ bridge: makeBridge(key) }))
|
|
870
|
+
const atoms: Record<string, (key: any) => Atom.Atom<any>> = {}
|
|
871
|
+
|
|
872
|
+
for (const atomName of Object.keys(options.atoms)) {
|
|
873
|
+
const project = options.atoms[atomName]!
|
|
874
|
+
atoms[atomName] = retainedFamily((key: any) => {
|
|
875
|
+
const owner = owners(key)
|
|
876
|
+
const source = project(owner.bridge)
|
|
877
|
+
return retainFamilyOwner(owner, source, options.label?.(key, atomName))
|
|
878
|
+
})
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return atoms
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
export const family = (
|
|
885
|
+
machine: Machine.Machine.Any,
|
|
886
|
+
options: FamilyOptions
|
|
887
|
+
): Readonly<Record<string, (key: any) => Atom.Atom<any>>> =>
|
|
888
|
+
makeFamily(
|
|
889
|
+
(input) => (make as any)(machine, input),
|
|
890
|
+
options
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
export const familyChild = (
|
|
894
|
+
parent: MachineAtom<any, never, any, any, any, any> | ChildMachineAtom<any, any>,
|
|
895
|
+
options: FamilyOptions & { readonly child: (key: any) => Machine.ChildMachine.Any }
|
|
896
|
+
): Readonly<Record<string, (key: any) => Atom.Atom<any>>> =>
|
|
897
|
+
makeFamily(
|
|
898
|
+
(key) => parent.child(options.child(key)),
|
|
899
|
+
options
|
|
900
|
+
)
|
|
901
|
+
|
|
741
902
|
export const bind = <Services, RuntimeError>(
|
|
742
903
|
runtime: Atom.AtomRuntime<Services, RuntimeError>
|
|
743
904
|
): Bound<Services, RuntimeError> => ({
|
|
@@ -749,5 +910,10 @@ export const bind = <Services, RuntimeError>(
|
|
|
749
910
|
>["make"],
|
|
750
911
|
resume:
|
|
751
912
|
((machine: Machine.Machine.Any, snapshot: Machine.Machine.Snapshot<any>) =>
|
|
752
|
-
resumeWithRuntime(runtime, machine, snapshot)) as Bound<Services, RuntimeError>["resume"]
|
|
913
|
+
resumeWithRuntime(runtime, machine, snapshot)) as Bound<Services, RuntimeError>["resume"],
|
|
914
|
+
family: ((machine: Machine.Machine.Any, options: FamilyOptions) =>
|
|
915
|
+
makeFamily(
|
|
916
|
+
(input) => makeWithRuntime(runtime, machine, [input]),
|
|
917
|
+
options
|
|
918
|
+
)) as Bound<Services, RuntimeError>["family"]
|
|
753
919
|
})
|