@typeonce/effect-machine 0.8.0 → 0.9.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 +64 -24
- package/dist/Machine.d.ts +386 -304
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +66 -171
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +5 -12
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +47 -17
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +2 -2
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +25 -0
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +10 -2
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +93 -34
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +39 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
- package/dist/internal/machine/invocationEvent.js +43 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +6 -50
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +8 -65
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +2 -1
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +45 -2
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +9 -0
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +114 -0
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/symbols.d.ts +2 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -1
- package/dist/internal/machine/symbols.js +2 -0
- package/dist/internal/machine/symbols.js.map +1 -1
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +23 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/docs/agent-guide.md +118 -93
- package/package.json +2 -2
- package/src/Machine.ts +1040 -651
- package/src/internal/machine/activities.ts +48 -33
- package/src/internal/machine/atom.ts +3 -3
- package/src/internal/machine/executionPlan.ts +29 -0
- package/src/internal/machine/invocation.ts +179 -48
- package/src/internal/machine/invocationEvent.ts +72 -0
- package/src/internal/machine/machine.ts +20 -346
- package/src/internal/machine/planner.ts +61 -10
- package/src/internal/machine/protocol.ts +149 -0
- package/src/internal/machine/symbols.ts +3 -0
- package/src/internal/machine/topology.ts +23 -0
- package/src/unstable/reactivity/AtomMachine.ts +3 -3
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* @since 0.4.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export const ActivityMetadataTypeId: unique symbol = Symbol.for("effect/Machine/ActivityMetadata")
|
|
7
|
+
import * as Duration from "effect/Duration"
|
|
9
8
|
|
|
10
9
|
/** @internal */
|
|
11
10
|
export type StaticActivityMetadata =
|
|
@@ -21,8 +20,7 @@ export type StaticActivityMetadata =
|
|
|
21
20
|
}
|
|
22
21
|
| {
|
|
23
22
|
readonly type: "timer"
|
|
24
|
-
readonly duration: string
|
|
25
|
-
readonly event: string
|
|
23
|
+
readonly duration: string | "dynamic"
|
|
26
24
|
}
|
|
27
25
|
| {
|
|
28
26
|
readonly type: "machine"
|
|
@@ -33,20 +31,10 @@ export type StaticActivityMetadata =
|
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
/** @internal */
|
|
36
|
-
export type ActivityDefinition<Source extends string = string> =
|
|
37
|
-
|
|
38
|
-
readonly source: Source
|
|
39
|
-
readonly id: string
|
|
40
|
-
} & StaticActivityMetadata)
|
|
41
|
-
| {
|
|
42
|
-
readonly source: Source
|
|
43
|
-
readonly type: "dynamic"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface ActivityDescriptor {
|
|
34
|
+
export type ActivityDefinition<Source extends string = string> = {
|
|
35
|
+
readonly source: Source
|
|
47
36
|
readonly id: string
|
|
48
|
-
|
|
49
|
-
}
|
|
37
|
+
} & StaticActivityMetadata
|
|
50
38
|
|
|
51
39
|
interface InspectableMachine {
|
|
52
40
|
readonly handlers: unknown
|
|
@@ -62,31 +50,60 @@ const isObject = (value: unknown): value is object =>
|
|
|
62
50
|
|
|
63
51
|
const getProperty = (value: unknown, key: PropertyKey): unknown => isObject(value) ? Reflect.get(value, key) : undefined
|
|
64
52
|
|
|
65
|
-
const hasActivityMetadata = (value: unknown): value is ActivityDescriptor =>
|
|
66
|
-
isObject(value) && typeof getProperty(value, "id") === "string" &&
|
|
67
|
-
isObject(getProperty(value, ActivityMetadataTypeId))
|
|
68
|
-
|
|
69
53
|
const appendStaticDefinition = (
|
|
70
54
|
definitions: Array<ActivityDefinition>,
|
|
71
55
|
source: string,
|
|
72
56
|
descriptor: unknown
|
|
73
57
|
): void => {
|
|
74
|
-
if (!
|
|
58
|
+
if (!isObject(descriptor)) return
|
|
59
|
+
const child = getProperty(descriptor, "child")
|
|
60
|
+
if (isObject(child)) {
|
|
61
|
+
const id = getProperty(child, "id")
|
|
62
|
+
if (typeof id !== "string") return
|
|
63
|
+
const machine = getProperty(child, "machine")
|
|
64
|
+
const machineId = getProperty(machine, "id")
|
|
65
|
+
definitions.push({
|
|
66
|
+
source,
|
|
67
|
+
id,
|
|
68
|
+
type: "machine",
|
|
69
|
+
child: { id, machineId: typeof machineId === "string" ? machineId : null }
|
|
70
|
+
})
|
|
75
71
|
return
|
|
76
72
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
const id = getProperty(descriptor, "id")
|
|
74
|
+
if (typeof id !== "string") return
|
|
75
|
+
if (Reflect.has(descriptor, "effect")) {
|
|
76
|
+
definitions.push({
|
|
77
|
+
source,
|
|
78
|
+
id,
|
|
79
|
+
type: "effect",
|
|
80
|
+
outcomes: {
|
|
81
|
+
success: "dynamic",
|
|
82
|
+
failure: Reflect.has(descriptor, "onFailure") ? "dynamic" : "none"
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
if (Reflect.has(descriptor, "after")) {
|
|
88
|
+
const after = getProperty(descriptor, "after")
|
|
89
|
+
definitions.push({
|
|
90
|
+
source,
|
|
91
|
+
id,
|
|
92
|
+
type: "timer",
|
|
93
|
+
duration: typeof after === "function" ? "dynamic" : Duration.format(Duration.fromInputUnsafe(after as any))
|
|
94
|
+
})
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
if (Reflect.has(descriptor, "logic")) {
|
|
98
|
+
definitions.push({ source, id, type: "process" })
|
|
99
|
+
}
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
/**
|
|
85
103
|
* Collects state-owned activity descriptions without executing user code.
|
|
86
104
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* definition order and descriptor array order.
|
|
105
|
+
* Source factories are inspected without execution. Static descriptors are
|
|
106
|
+
* returned in state definition order and descriptor array order.
|
|
90
107
|
*
|
|
91
108
|
* @internal
|
|
92
109
|
*/
|
|
@@ -94,9 +111,7 @@ export const activityDefinitions = (machine: InspectableMachine): ReadonlyArray<
|
|
|
94
111
|
const definitions: Array<ActivityDefinition> = []
|
|
95
112
|
for (const node of machine.stateNodes.byPath.values()) {
|
|
96
113
|
const invoke = getProperty(getProperty(machine.handlers, node.path), "invoke")
|
|
97
|
-
if (
|
|
98
|
-
definitions.push({ source: node.path, type: "dynamic" })
|
|
99
|
-
} else if (Array.isArray(invoke)) {
|
|
114
|
+
if (Array.isArray(invoke)) {
|
|
100
115
|
for (const descriptor of invoke) {
|
|
101
116
|
appendStaticDefinition(definitions, node.path, descriptor)
|
|
102
117
|
}
|
|
@@ -120,7 +120,7 @@ const startMachineAtomEffect = <
|
|
|
120
120
|
): Effect.Effect<
|
|
121
121
|
Machine.MachineRef<
|
|
122
122
|
Machine.Machine.Snapshot<States>,
|
|
123
|
-
Machine.Machine.
|
|
123
|
+
Machine.Machine.EventInputOf<InputEvents>,
|
|
124
124
|
MachineRuntimeError<E, R>,
|
|
125
125
|
Output
|
|
126
126
|
>,
|
|
@@ -585,7 +585,7 @@ type EnsureMachineExecutable<M extends Machine.Machine.Any> = IsAny<Machine.Mach
|
|
|
585
585
|
|
|
586
586
|
type ResumedMachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
|
|
587
587
|
Machine.Machine.Snapshot<Machine.Machine.States<M>>,
|
|
588
|
-
Machine.Machine.InputEvent<M
|
|
588
|
+
Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
|
|
589
589
|
MachineRuntimeError<Machine.Machine.Error<M>, Machine.Machine.Services<M>>,
|
|
590
590
|
Machine.Machine.Output<M>,
|
|
591
591
|
Machine.MachineSchemaDecodeError | RuntimeError
|
|
@@ -635,7 +635,7 @@ export const make: {
|
|
|
635
635
|
...args: [...Machine.Machine.InputArgs<Input>]
|
|
636
636
|
): MachineAtom<
|
|
637
637
|
Machine.Machine.Snapshot<States>,
|
|
638
|
-
Machine.Machine.
|
|
638
|
+
Machine.Machine.EventInputOf<InputEvents>,
|
|
639
639
|
MachineRuntimeError<E, R>,
|
|
640
640
|
Output,
|
|
641
641
|
MachineStartError<InitialE, E, InitialR, R>
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
validateInitialConfiguration
|
|
22
22
|
} from "./configuration.js"
|
|
23
23
|
import { InfiniteTransitionError } from "./errors.js"
|
|
24
|
+
import * as InvocationEvent from "./invocationEvent.js"
|
|
24
25
|
import {
|
|
25
26
|
broadenTransitionBoundary,
|
|
26
27
|
type EvaluatedTransition,
|
|
@@ -775,6 +776,34 @@ const planIndexedState = (
|
|
|
775
776
|
input: unknown,
|
|
776
777
|
retainMicrosteps: boolean
|
|
777
778
|
): ExecutionMacrostep<OwnedIndexedState> => {
|
|
779
|
+
if (InvocationEvent.isInvocationEvent(input)) {
|
|
780
|
+
// Invocation lifecycle transitions retain the generic planner as their
|
|
781
|
+
// semantic reference. Ordinary machine events and initialization stay on
|
|
782
|
+
// the indexed representation; only the private lifecycle macrostep crosses
|
|
783
|
+
// the representation boundary.
|
|
784
|
+
const planned = planConfiguration(
|
|
785
|
+
machine as any,
|
|
786
|
+
activeConfigurationFromIndexedState(descriptor, configuration),
|
|
787
|
+
input
|
|
788
|
+
)
|
|
789
|
+
return {
|
|
790
|
+
next: ownedIndexedStateFromActive(descriptor, planned.next),
|
|
791
|
+
commands: planned.commands,
|
|
792
|
+
emittedEvents: planned.emittedEvents,
|
|
793
|
+
microsteps: planned.microsteps.map((step) => ({
|
|
794
|
+
next: ownedIndexedStateFromActive(descriptor, step.next),
|
|
795
|
+
event: step.event,
|
|
796
|
+
commands: step.commands,
|
|
797
|
+
raisedEvents: step.raisedEvents,
|
|
798
|
+
emittedEvents: step.emittedEvents,
|
|
799
|
+
exitPaths: step.exitPaths,
|
|
800
|
+
entryPaths: step.entryPaths,
|
|
801
|
+
changed: step.changed
|
|
802
|
+
})),
|
|
803
|
+
done: planned.done,
|
|
804
|
+
output: planned.output
|
|
805
|
+
}
|
|
806
|
+
}
|
|
778
807
|
const decoded = decodeEventSync(machine, input)
|
|
779
808
|
if (descriptor.flat) {
|
|
780
809
|
return planIndexedFlatState(machine, descriptor, configuration, decoded, retainMicrosteps)
|
|
@@ -4,28 +4,26 @@
|
|
|
4
4
|
* @since 0.4.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import * as Cause from "effect/Cause"
|
|
7
8
|
import * as Effect from "effect/Effect"
|
|
8
|
-
import type { Machine } from "../../Machine.js"
|
|
9
|
+
import type { ChildMachine, Logic, Machine } from "../../Machine.js"
|
|
9
10
|
import * as Configuration from "./configuration.js"
|
|
11
|
+
import { InfiniteTransitionError, MachineSchemaDecodeError, StoppedError } from "./errors.js"
|
|
12
|
+
import * as InvocationEvent from "./invocationEvent.js"
|
|
10
13
|
import * as Planner from "./planner.js"
|
|
11
14
|
import type * as Runtime from "./runtime.js"
|
|
15
|
+
import { ChildMachineLogicTypeId } from "./symbols.js"
|
|
12
16
|
|
|
13
17
|
/** @internal */
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
any,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
any,
|
|
24
|
-
any,
|
|
25
|
-
any,
|
|
26
|
-
any,
|
|
27
|
-
any
|
|
28
|
-
>
|
|
18
|
+
export interface AnyConfig {
|
|
19
|
+
readonly id: string
|
|
20
|
+
readonly address?: string
|
|
21
|
+
readonly descriptor?: ChildMachine.Any
|
|
22
|
+
readonly src: () => Runtime.ProcessLogic<any, any, any, any, any, any>
|
|
23
|
+
readonly onDone?: unknown
|
|
24
|
+
readonly onFailure?: unknown
|
|
25
|
+
readonly onSnapshot?: unknown
|
|
26
|
+
}
|
|
29
27
|
|
|
30
28
|
/** @internal */
|
|
31
29
|
export const makeKey = (path: string, id: string): string => `${path.length}:${path}${id}`
|
|
@@ -33,16 +31,79 @@ export const makeKey = (path: string, id: string): string => `${path.length}:${p
|
|
|
33
31
|
/** @internal */
|
|
34
32
|
export const makeChildId = (path: string, id: string): string => `Machine.invoke:${makeKey(path, id)}`
|
|
35
33
|
|
|
36
|
-
const
|
|
37
|
-
|
|
34
|
+
const oneShot = (effect: Effect.Effect<any, any, any>): Logic<void, never, any, any, any> => ({
|
|
35
|
+
initial: () => Effect.void,
|
|
36
|
+
run: () => effect
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const resolveValue = (value: unknown, context: Machine.InvokeContext<any, any, any, any>): unknown =>
|
|
40
|
+
typeof value === "function" ? value(context) : value
|
|
41
|
+
|
|
42
|
+
const resolveOne = (
|
|
43
|
+
raw: Record<PropertyKey, any>,
|
|
38
44
|
context: Machine.InvokeContext<any, any, any, any>
|
|
39
|
-
):
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
): AnyConfig => {
|
|
46
|
+
if ("effect" in raw) {
|
|
47
|
+
return {
|
|
48
|
+
id: String(raw.id),
|
|
49
|
+
src: () =>
|
|
50
|
+
oneShot(resolveValue(raw.effect, context) as Effect.Effect<any, any, any>) as unknown as Runtime.ProcessLogic<
|
|
51
|
+
any,
|
|
52
|
+
any,
|
|
53
|
+
any,
|
|
54
|
+
any,
|
|
55
|
+
any,
|
|
56
|
+
any
|
|
57
|
+
>,
|
|
58
|
+
onDone: raw.onDone,
|
|
59
|
+
onFailure: raw.onFailure,
|
|
60
|
+
onSnapshot: raw.onSnapshot
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if ("after" in raw) {
|
|
64
|
+
return {
|
|
65
|
+
id: String(raw.id),
|
|
66
|
+
src: () =>
|
|
67
|
+
oneShot(Effect.sleep(resolveValue(raw.after, context) as any)) as unknown as Runtime.ProcessLogic<
|
|
68
|
+
any,
|
|
69
|
+
any,
|
|
70
|
+
any,
|
|
71
|
+
any,
|
|
72
|
+
any,
|
|
73
|
+
any
|
|
74
|
+
>,
|
|
75
|
+
onDone: raw.onDone
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if ("logic" in raw) {
|
|
79
|
+
return {
|
|
80
|
+
id: String(raw.id),
|
|
81
|
+
address: String(raw.address),
|
|
82
|
+
src: () => resolveValue(raw.logic, context) as Runtime.ProcessLogic<any, any, any, any, any, any>,
|
|
83
|
+
onDone: raw.onDone,
|
|
84
|
+
onFailure: raw.onFailure,
|
|
85
|
+
onSnapshot: raw.onSnapshot
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if ("child" in raw) {
|
|
89
|
+
const descriptor = raw.child as ChildMachine.Any
|
|
90
|
+
return {
|
|
91
|
+
id: descriptor.id,
|
|
92
|
+
address: descriptor.id,
|
|
93
|
+
descriptor,
|
|
94
|
+
src: () =>
|
|
95
|
+
descriptor[ChildMachineLogicTypeId](
|
|
96
|
+
"input" in raw ? resolveValue(raw.input, context) : undefined
|
|
97
|
+
) as Runtime.ProcessLogic<any, any, any, any, any, any>,
|
|
98
|
+
onDone: raw.onDone,
|
|
99
|
+
onFailure: raw.onFailure,
|
|
100
|
+
onSnapshot: raw.onSnapshot
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error("Machine invoke must define exactly one of effect, after, logic, or child")
|
|
44
104
|
}
|
|
45
105
|
|
|
106
|
+
/** @internal */
|
|
46
107
|
const runSequentialDiscard = <E, R>(
|
|
47
108
|
effects: ReadonlyArray<Effect.Effect<void, E, R>>
|
|
48
109
|
): Effect.Effect<void, E, R> =>
|
|
@@ -52,45 +113,110 @@ const runSequentialDiscard = <E, R>(
|
|
|
52
113
|
? effects[0]!
|
|
53
114
|
: Effect.all(effects, { discard: true })
|
|
54
115
|
|
|
55
|
-
const
|
|
116
|
+
const sendLifecycle = (
|
|
117
|
+
scope: Runtime.ProcessScope<any>,
|
|
118
|
+
event: InvocationEvent.InvocationEvent
|
|
119
|
+
): Effect.Effect<void> => scope.self.send(event).pipe(Effect.catchTag("StoppedError", () => Effect.void))
|
|
120
|
+
|
|
121
|
+
const isFrameworkFailure = (error: unknown): boolean =>
|
|
122
|
+
error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError || error instanceof StoppedError
|
|
123
|
+
|
|
124
|
+
const startResolved = (
|
|
56
125
|
scope: Runtime.ProcessScope<any>,
|
|
57
126
|
ownedChildren: Runtime.OwnedChildRuntime,
|
|
58
127
|
path: string,
|
|
59
|
-
|
|
128
|
+
invokeId: string,
|
|
129
|
+
childId: string,
|
|
130
|
+
descriptor: ChildMachine.Any | undefined,
|
|
131
|
+
src: () => Runtime.ProcessLogic<any, any, any, any, any, any>,
|
|
132
|
+
onDone: unknown,
|
|
133
|
+
onFailure: unknown,
|
|
134
|
+
onSnapshot: unknown
|
|
60
135
|
): Effect.Effect<void, any, any> =>
|
|
61
136
|
Effect.suspend(() => {
|
|
62
|
-
const invokeId = String(config.id)
|
|
63
137
|
const key = makeKey(path, invokeId)
|
|
64
|
-
|
|
65
|
-
return ownedChildren.spawn(config.src as () => Runtime.ProcessLogic<any, any, any, any, any, any>, {
|
|
138
|
+
return ownedChildren.spawn(src, {
|
|
66
139
|
key,
|
|
67
140
|
path,
|
|
68
141
|
id: childId,
|
|
69
142
|
duplicateId: invokeId,
|
|
70
|
-
...(
|
|
143
|
+
...(descriptor === undefined ? undefined : { descriptor }),
|
|
71
144
|
sendParent: (isCurrent, event) => isCurrent() ? scope.self.send(event) : Effect.void,
|
|
72
145
|
onOutcome: (isCurrent, outcome) => {
|
|
73
146
|
if (outcome._tag === "Stopped" || !isCurrent()) return Effect.void
|
|
74
|
-
if (outcome._tag
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
onSnapshot: (isCurrent: () => boolean, snapshot: any) => {
|
|
84
|
-
if (!isCurrent()) return Effect.void
|
|
85
|
-
const mappedEvent = config.snapshot!({ id: config.id, snapshot })
|
|
86
|
-
return mappedEvent === undefined
|
|
87
|
-
? Effect.void
|
|
88
|
-
: scope.self.send(mappedEvent).pipe(Effect.catchTag("StoppedError", () => Effect.void))
|
|
147
|
+
if (outcome._tag === "Done") {
|
|
148
|
+
if (onDone === undefined) {
|
|
149
|
+
return scope.failCause(Cause.die(
|
|
150
|
+
new Error(
|
|
151
|
+
`Invocation "${invokeId}" completed without the required onDone handler`
|
|
152
|
+
)
|
|
153
|
+
))
|
|
154
|
+
}
|
|
155
|
+
return sendLifecycle(scope, InvocationEvent.done(path, invokeId, outcome.output))
|
|
89
156
|
}
|
|
157
|
+
if (
|
|
158
|
+
outcome._tag === "Failure" &&
|
|
159
|
+
onFailure !== undefined &&
|
|
160
|
+
(descriptor === undefined || !isFrameworkFailure(outcome.error))
|
|
161
|
+
) {
|
|
162
|
+
return sendLifecycle(scope, InvocationEvent.failure(path, invokeId, outcome.error))
|
|
163
|
+
}
|
|
164
|
+
return scope.failCause(outcome.cause)
|
|
165
|
+
},
|
|
166
|
+
...(onSnapshot === undefined ? undefined : {
|
|
167
|
+
onSnapshot: (isCurrent: () => boolean, snapshot: any) =>
|
|
168
|
+
isCurrent()
|
|
169
|
+
? sendLifecycle(scope, InvocationEvent.snapshot(path, invokeId, snapshot))
|
|
170
|
+
: Effect.void
|
|
90
171
|
})
|
|
91
172
|
})
|
|
92
173
|
})
|
|
93
174
|
|
|
175
|
+
const start = (
|
|
176
|
+
scope: Runtime.ProcessScope<any>,
|
|
177
|
+
ownedChildren: Runtime.OwnedChildRuntime,
|
|
178
|
+
path: string,
|
|
179
|
+
config: AnyConfig
|
|
180
|
+
): Effect.Effect<void, any, any> => {
|
|
181
|
+
const invokeId = String(config.id)
|
|
182
|
+
return startResolved(
|
|
183
|
+
scope,
|
|
184
|
+
ownedChildren,
|
|
185
|
+
path,
|
|
186
|
+
invokeId,
|
|
187
|
+
config.address === undefined ? makeChildId(path, invokeId) : String(config.address),
|
|
188
|
+
config.descriptor,
|
|
189
|
+
config.src,
|
|
190
|
+
config.onDone,
|
|
191
|
+
config.onFailure,
|
|
192
|
+
config.onSnapshot
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const startStaticChild = (
|
|
197
|
+
scope: Runtime.ProcessScope<any>,
|
|
198
|
+
ownedChildren: Runtime.OwnedChildRuntime,
|
|
199
|
+
path: string,
|
|
200
|
+
raw: Record<PropertyKey, any>
|
|
201
|
+
): Effect.Effect<void, any, any> => {
|
|
202
|
+
// A zero-input child has no entry-context dependency. Reuse the descriptor's
|
|
203
|
+
// source function so each running parent does not retain a resolved config
|
|
204
|
+
// object and an otherwise redundant source closure.
|
|
205
|
+
const descriptor = raw.child as ChildMachine.Any
|
|
206
|
+
return startResolved(
|
|
207
|
+
scope,
|
|
208
|
+
ownedChildren,
|
|
209
|
+
path,
|
|
210
|
+
descriptor.id,
|
|
211
|
+
descriptor.id,
|
|
212
|
+
descriptor,
|
|
213
|
+
descriptor[ChildMachineLogicTypeId],
|
|
214
|
+
raw.onDone,
|
|
215
|
+
raw.onFailure,
|
|
216
|
+
raw.onSnapshot
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
94
220
|
/**
|
|
95
221
|
* Starts every invocation owned by active entry paths in deterministic entry
|
|
96
222
|
* order. `undefined` keeps the compiled drain free of empty Effect nodes.
|
|
@@ -107,13 +233,18 @@ export const startAll = (
|
|
|
107
233
|
): Effect.Effect<void, any, any> | undefined => {
|
|
108
234
|
const effects = Planner.sortEntryPaths(machine, paths)
|
|
109
235
|
.filter((path) => configuration.active.has(path))
|
|
110
|
-
.flatMap((path) =>
|
|
111
|
-
|
|
236
|
+
.flatMap((path) => {
|
|
237
|
+
const context = {
|
|
112
238
|
state: configuration.values.get(path),
|
|
113
239
|
parent: Configuration.getParentValue(machine, configuration, path),
|
|
114
240
|
parents: Configuration.getParentValues(machine, configuration, path),
|
|
115
241
|
event
|
|
116
|
-
}
|
|
117
|
-
|
|
242
|
+
}
|
|
243
|
+
return InvocationEvent.definitions(Configuration.getStateConfigByPath(machine, path)?.invoke).map((definition) =>
|
|
244
|
+
"child" in definition && !("input" in definition)
|
|
245
|
+
? startStaticChild(scope, ownedChildren, path, definition)
|
|
246
|
+
: start(scope, ownedChildren, path, resolveOne(definition, context))
|
|
247
|
+
)
|
|
248
|
+
})
|
|
118
249
|
return effects.length === 0 ? undefined : runSequentialDiscard(effects)
|
|
119
250
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private mailbox messages used to route invocation lifecycle changes through
|
|
3
|
+
* the owning machine planner.
|
|
4
|
+
*
|
|
5
|
+
* @since 0.9.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** @internal */
|
|
9
|
+
export const InvocationEventTypeId: unique symbol = Symbol("effect/Machine/InvocationEvent")
|
|
10
|
+
|
|
11
|
+
/** @internal */
|
|
12
|
+
export type InvocationEvent =
|
|
13
|
+
| {
|
|
14
|
+
readonly [InvocationEventTypeId]: true
|
|
15
|
+
readonly path: string
|
|
16
|
+
readonly id: string
|
|
17
|
+
readonly type: "done"
|
|
18
|
+
readonly output: unknown
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
readonly [InvocationEventTypeId]: true
|
|
22
|
+
readonly path: string
|
|
23
|
+
readonly id: string
|
|
24
|
+
readonly type: "failure"
|
|
25
|
+
readonly error: unknown
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
readonly [InvocationEventTypeId]: true
|
|
29
|
+
readonly path: string
|
|
30
|
+
readonly id: string
|
|
31
|
+
readonly type: "snapshot"
|
|
32
|
+
readonly snapshot: unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @internal */
|
|
36
|
+
export const done = (path: string, id: string, output: unknown): InvocationEvent => ({
|
|
37
|
+
[InvocationEventTypeId]: true,
|
|
38
|
+
path,
|
|
39
|
+
id,
|
|
40
|
+
type: "done",
|
|
41
|
+
output
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/** @internal */
|
|
45
|
+
export const failure = (path: string, id: string, error: unknown): InvocationEvent => ({
|
|
46
|
+
[InvocationEventTypeId]: true,
|
|
47
|
+
path,
|
|
48
|
+
id,
|
|
49
|
+
type: "failure",
|
|
50
|
+
error
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
/** @internal */
|
|
54
|
+
export const snapshot = (path: string, id: string, value: unknown): InvocationEvent => ({
|
|
55
|
+
[InvocationEventTypeId]: true,
|
|
56
|
+
path,
|
|
57
|
+
id,
|
|
58
|
+
type: "snapshot",
|
|
59
|
+
snapshot: value
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/** @internal */
|
|
63
|
+
export const isInvocationEvent = (value: unknown): value is InvocationEvent =>
|
|
64
|
+
typeof value === "object" && value !== null && InvocationEventTypeId in value
|
|
65
|
+
|
|
66
|
+
/** @internal */
|
|
67
|
+
export const definitions = (invoke: unknown): ReadonlyArray<Record<PropertyKey, any>> => {
|
|
68
|
+
if (invoke === undefined) return []
|
|
69
|
+
return Array.isArray(invoke)
|
|
70
|
+
? invoke as ReadonlyArray<Record<PropertyKey, any>>
|
|
71
|
+
: [invoke as Record<PropertyKey, any>]
|
|
72
|
+
}
|