@typeonce/effect-machine 0.14.1 → 0.16.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 +99 -48
- package/dist/Machine.d.ts +436 -156
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +114 -57
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +2 -0
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +4 -0
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +6 -1
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +25 -3
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +8 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
- package/dist/internal/machine/invocationEvent.js +8 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +9 -4
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +131 -55
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +7 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +20 -11
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +1 -0
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +25 -16
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts +3 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +35 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +11 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +17 -6
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.js +6 -2
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +6 -0
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +9 -8
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +7 -7
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
- package/dist/unstable/reactivity/AtomMachine.js +3 -3
- package/docs/agent-guide.md +207 -88
- package/package.json +1 -1
- package/src/Machine.ts +871 -256
- package/src/internal/machine/activities.ts +7 -0
- package/src/internal/machine/executionPlan.ts +6 -1
- package/src/internal/machine/invocation.ts +39 -4
- package/src/internal/machine/invocationEvent.ts +16 -0
- package/src/internal/machine/machine.ts +187 -67
- package/src/internal/machine/planner.ts +17 -3
- package/src/internal/machine/runtime.ts +61 -25
- package/src/internal/machine/stateDefinition.ts +41 -1
- package/src/internal/machine/topology.ts +31 -2
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/internal/testing/machine/transitionCoverage.ts +6 -2
- package/src/internal/testing/machine/verification.ts +11 -0
- package/src/testing/MachineTest.ts +9 -7
- package/src/unstable/cluster/ClusterMachine.ts +1 -1
- package/src/unstable/reactivity/AtomMachine.ts +3 -3
|
@@ -22,6 +22,9 @@ export type StaticActivityMetadata =
|
|
|
22
22
|
readonly type: "timer"
|
|
23
23
|
readonly duration: string | "dynamic"
|
|
24
24
|
}
|
|
25
|
+
| {
|
|
26
|
+
readonly type: "stream"
|
|
27
|
+
}
|
|
25
28
|
| {
|
|
26
29
|
readonly type: "machine"
|
|
27
30
|
readonly child: {
|
|
@@ -94,6 +97,10 @@ const appendStaticDefinition = (
|
|
|
94
97
|
})
|
|
95
98
|
return
|
|
96
99
|
}
|
|
100
|
+
if (Reflect.has(descriptor, "stream")) {
|
|
101
|
+
definitions.push({ source, id, type: "stream" })
|
|
102
|
+
return
|
|
103
|
+
}
|
|
97
104
|
if (Reflect.has(descriptor, "logic")) {
|
|
98
105
|
definitions.push({ source, id, type: "process" })
|
|
99
106
|
}
|
|
@@ -445,11 +445,12 @@ const collectIndexedTransition = (
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
const evaluated = evaluate === undefined
|
|
448
|
-
? { result: transition(context, enqueue), branchIndex: 0 }
|
|
448
|
+
? { result: transition(context, enqueue), branchIndex: 0, branchKey: undefined }
|
|
449
449
|
: evaluate(context, enqueue)
|
|
450
450
|
return {
|
|
451
451
|
state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
|
|
452
452
|
branchIndex: evaluated.branchIndex,
|
|
453
|
+
branchKey: evaluated.branchKey,
|
|
453
454
|
commands: commands ?? emptyExecutionValues,
|
|
454
455
|
raisedEvents: raisedEvents ?? emptyExecutionValues,
|
|
455
456
|
emittedEvents: emittedEvents ?? emptyExecutionValues
|
|
@@ -569,6 +570,7 @@ const collectIndexedEvaluatedTransition = (
|
|
|
569
570
|
return {
|
|
570
571
|
selection,
|
|
571
572
|
branchIndex: transitionResult.branchIndex,
|
|
573
|
+
branchKey: transitionResult.branchKey,
|
|
572
574
|
unresolvedTarget: unresolvedTarget as any,
|
|
573
575
|
target: target as any,
|
|
574
576
|
next,
|
|
@@ -593,6 +595,7 @@ const collectIndexedEvaluatedTransition = (
|
|
|
593
595
|
return {
|
|
594
596
|
selection,
|
|
595
597
|
branchIndex: transitionResult.branchIndex,
|
|
598
|
+
branchKey: transitionResult.branchKey,
|
|
596
599
|
unresolvedTarget: unresolvedTarget as any,
|
|
597
600
|
target: target as any,
|
|
598
601
|
next,
|
|
@@ -619,6 +622,7 @@ const indexedMicrostep = (
|
|
|
619
622
|
trigger: transition.selection.trigger,
|
|
620
623
|
reenter: transition.selection.transition.reenter,
|
|
621
624
|
branchIndex: transition.branchIndex,
|
|
625
|
+
branchKey: transition.branchKey,
|
|
622
626
|
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
623
627
|
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
624
628
|
})
|
|
@@ -804,6 +808,7 @@ const planIndexedFlatState = (
|
|
|
804
808
|
trigger: { type: "event", event: event._tag },
|
|
805
809
|
reenter: transition.reenter,
|
|
806
810
|
branchIndex: transitionResult.branchIndex,
|
|
811
|
+
branchKey: transitionResult.branchKey,
|
|
807
812
|
target: target === undefined ? undefined : getTargetNodePath(target as any),
|
|
808
813
|
resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any)
|
|
809
814
|
}]
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Cause from "effect/Cause"
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as Stream from "effect/Stream"
|
|
9
10
|
import type { ChildMachine, Inspection, Logic, Machine } from "../../Machine.js"
|
|
10
11
|
import * as Configuration from "./configuration.js"
|
|
11
12
|
import { InfiniteTransitionError, MachineSchemaDecodeError, StoppedError } from "./errors.js"
|
|
12
13
|
import * as InvocationEvent from "./invocationEvent.js"
|
|
13
14
|
import * as Planner from "./planner.js"
|
|
14
|
-
import
|
|
15
|
+
import * as Runtime from "./runtime.js"
|
|
15
16
|
import { ChildMachineLogicTypeId } from "./symbols.js"
|
|
16
17
|
|
|
17
18
|
/** @internal */
|
|
@@ -37,12 +38,35 @@ const oneShot = (effect: Effect.Effect<any, any, any>): Logic<void, never, any,
|
|
|
37
38
|
run: () => effect
|
|
38
39
|
})
|
|
39
40
|
|
|
41
|
+
const streamLogic = (
|
|
42
|
+
stream: Stream.Stream<any, any, any>,
|
|
43
|
+
path: string,
|
|
44
|
+
id: string
|
|
45
|
+
): Runtime.ProcessLogic<void, never, any, any, void> => ({
|
|
46
|
+
initial: () => Effect.void,
|
|
47
|
+
run: ({ parent }) => {
|
|
48
|
+
const send = parent?.[Runtime.acknowledgedSend]
|
|
49
|
+
if (send === undefined) {
|
|
50
|
+
return Effect.die(new Error("Stream invocation requires an acknowledged parent machine"))
|
|
51
|
+
}
|
|
52
|
+
return Stream.runForEach(
|
|
53
|
+
stream,
|
|
54
|
+
(element) =>
|
|
55
|
+
send(InvocationEvent.element(path, id, element)).pipe(
|
|
56
|
+
Effect.asVoid,
|
|
57
|
+
Effect.catchCause(() => Effect.interrupt)
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
40
63
|
const resolveValue = (value: unknown, context: Machine.InvokeContext<any, any, any, any>): unknown =>
|
|
41
64
|
typeof value === "function" ? value(context) : value
|
|
42
65
|
|
|
43
66
|
const resolveOne = (
|
|
44
67
|
raw: Record<PropertyKey, any>,
|
|
45
|
-
context: Machine.InvokeContext<any, any, any, any
|
|
68
|
+
context: Machine.InvokeContext<any, any, any, any>,
|
|
69
|
+
path: string
|
|
46
70
|
): AnyConfig => {
|
|
47
71
|
if ("effect" in raw) {
|
|
48
72
|
return {
|
|
@@ -78,6 +102,17 @@ const resolveOne = (
|
|
|
78
102
|
activityKind: "Timer"
|
|
79
103
|
}
|
|
80
104
|
}
|
|
105
|
+
if ("stream" in raw) {
|
|
106
|
+
const id = String(raw.id)
|
|
107
|
+
return {
|
|
108
|
+
id,
|
|
109
|
+
src: () =>
|
|
110
|
+
streamLogic(raw.stream(context), path, id) as unknown as Runtime.ProcessLogic<any, any, any, any, any, any>,
|
|
111
|
+
onDone: raw.onDone,
|
|
112
|
+
onFailure: raw.onFailure,
|
|
113
|
+
activityKind: "Stream"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
81
116
|
if ("logic" in raw) {
|
|
82
117
|
return {
|
|
83
118
|
id: String(raw.id),
|
|
@@ -103,7 +138,7 @@ const resolveOne = (
|
|
|
103
138
|
onSnapshot: raw.onSnapshot
|
|
104
139
|
}
|
|
105
140
|
}
|
|
106
|
-
throw new Error("Machine invoke must define exactly one of effect, after, logic, or child")
|
|
141
|
+
throw new Error("Machine invoke must define exactly one of effect, stream, after, logic, or child")
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
/** @internal */
|
|
@@ -269,7 +304,7 @@ export const startAll = (
|
|
|
269
304
|
return InvocationEvent.definitions(Configuration.getStateConfigByPath(machine, path)?.invoke).map((definition) =>
|
|
270
305
|
"child" in definition && !("input" in definition)
|
|
271
306
|
? startStaticChild(scope, ownedChildren, path, definition)
|
|
272
|
-
: start(scope, ownedChildren, path, resolveOne(definition, context))
|
|
307
|
+
: start(scope, ownedChildren, path, resolveOne(definition, context, path))
|
|
273
308
|
)
|
|
274
309
|
})
|
|
275
310
|
return effects.length === 0 ? undefined : runSequentialDiscard(effects)
|
|
@@ -10,6 +10,13 @@ export const InvocationEventTypeId: unique symbol = Symbol("effect/Machine/Invoc
|
|
|
10
10
|
|
|
11
11
|
/** @internal */
|
|
12
12
|
export type InvocationEvent =
|
|
13
|
+
| {
|
|
14
|
+
readonly [InvocationEventTypeId]: true
|
|
15
|
+
readonly path: string
|
|
16
|
+
readonly id: string
|
|
17
|
+
readonly type: "element"
|
|
18
|
+
readonly element: unknown
|
|
19
|
+
}
|
|
13
20
|
| {
|
|
14
21
|
readonly [InvocationEventTypeId]: true
|
|
15
22
|
readonly path: string
|
|
@@ -41,6 +48,15 @@ export const done = (path: string, id: string, output: unknown): InvocationEvent
|
|
|
41
48
|
output
|
|
42
49
|
})
|
|
43
50
|
|
|
51
|
+
/** @internal */
|
|
52
|
+
export const element = (path: string, id: string, value: unknown): InvocationEvent => ({
|
|
53
|
+
[InvocationEventTypeId]: true,
|
|
54
|
+
path,
|
|
55
|
+
id,
|
|
56
|
+
type: "element",
|
|
57
|
+
element: value
|
|
58
|
+
})
|
|
59
|
+
|
|
44
60
|
/** @internal */
|
|
45
61
|
export const failure = (path: string, id: string, error: unknown): InvocationEvent => ({
|
|
46
62
|
[InvocationEventTypeId]: true,
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ChildAddress,
|
|
12
12
|
ChildMachine,
|
|
13
13
|
Command,
|
|
14
|
+
Definition,
|
|
14
15
|
ExecutionServices,
|
|
15
16
|
InitialEvent as InitialEventModel,
|
|
16
17
|
Logic,
|
|
@@ -79,7 +80,14 @@ type ValidateDefinedStates<States extends Machine.StateSchemas> = [States] exten
|
|
|
79
80
|
type InvalidDefinedStateTreeInput<States extends Machine.StateSchemas> = [States] extends
|
|
80
81
|
[Machine.ValidateStateSchemas<States>] ? never
|
|
81
82
|
: States & Machine.ValidateStateSchemas<States>
|
|
82
|
-
|
|
83
|
+
type ReusableStateNodeConfig =
|
|
84
|
+
| Machine.AtomicStateNodeConfig
|
|
85
|
+
| Machine.CompoundStateNodeConfig
|
|
86
|
+
| Machine.ParallelStateNodeConfig
|
|
87
|
+
interface StateConstructor {
|
|
88
|
+
<const Node extends ReusableStateNodeConfig>(node: Node): Node
|
|
89
|
+
}
|
|
90
|
+
interface StatesConstructor {
|
|
83
91
|
<const States extends Machine.StateSchemas>(
|
|
84
92
|
states: States,
|
|
85
93
|
..._validation: ValidateDefinedStates<NoInfer<States>>
|
|
@@ -106,8 +114,8 @@ const Proto = {
|
|
|
106
114
|
|
|
107
115
|
const makeBoundInvoke = (config: unknown): unknown => config
|
|
108
116
|
|
|
109
|
-
const
|
|
110
|
-
self:
|
|
117
|
+
const makeWithHandlers = (
|
|
118
|
+
self: Definition.Any,
|
|
111
119
|
handlers: Machine.StateConfigs<any, any, any, any, any, any, any>
|
|
112
120
|
): Machine.Any => {
|
|
113
121
|
const machine = Object.create(Proto)
|
|
@@ -123,15 +131,12 @@ const cloneWithHandlers = (
|
|
|
123
131
|
machine.stateNodes = self.stateNodes
|
|
124
132
|
machine.makeTargetBuilder = self.makeTargetBuilder
|
|
125
133
|
machine.handlers = handlers
|
|
126
|
-
machine.handle = makeHandle(machine)
|
|
127
134
|
machine.invoke = makeBoundInvoke
|
|
128
135
|
Protocol.copyProtocol(self, machine)
|
|
129
136
|
return machine
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
type DefinitionBranch = {
|
|
133
|
-
readonly title?: string
|
|
134
|
-
readonly when?: (context: any) => Option.Option<unknown>
|
|
135
140
|
readonly target: (selector: unknown) => unknown
|
|
136
141
|
readonly resolve?: (context: any, enqueue: unknown) => unknown
|
|
137
142
|
}
|
|
@@ -140,6 +145,17 @@ type CapturedBranch = DefinitionBranch & {
|
|
|
140
145
|
readonly selection: Topology.TargetSelection
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
type BranchDeclaration = {
|
|
149
|
+
readonly title?: string
|
|
150
|
+
readonly target: unknown
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type CapturedNamedBranch = {
|
|
154
|
+
readonly key: string
|
|
155
|
+
readonly title: string
|
|
156
|
+
readonly selection: Topology.TargetSelection
|
|
157
|
+
}
|
|
158
|
+
|
|
143
159
|
const transitionTargetSelection = (
|
|
144
160
|
selection: Topology.TargetSelection
|
|
145
161
|
): Machine.TransitionTargetSelection =>
|
|
@@ -329,20 +345,134 @@ const runCapturedBranch = (
|
|
|
329
345
|
context: Record<string, any>,
|
|
330
346
|
enqueue: unknown,
|
|
331
347
|
stateNodes: Machine.StateNodes,
|
|
332
|
-
source: string
|
|
333
|
-
match?: { readonly value: unknown }
|
|
348
|
+
source: string
|
|
334
349
|
): unknown => {
|
|
335
350
|
const selectedTarget = getSelectionBuilder(context.target, branch.selection, stateNodes, source)
|
|
336
351
|
if (branch.resolve === undefined) return constructSelectedTarget(selectedTarget)
|
|
337
352
|
const resolverContext = { ...context }
|
|
338
353
|
if (branch.selection.kind === "none") delete resolverContext.target
|
|
339
354
|
else resolverContext.target = selectedTarget
|
|
340
|
-
if (match !== undefined) resolverContext.match = match.value
|
|
341
355
|
const resolved = branch.resolve(resolverContext, enqueue)
|
|
342
356
|
validateResolvedSelection(resolved, branch.selection, stateNodes)
|
|
343
357
|
return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
|
|
344
358
|
}
|
|
345
359
|
|
|
360
|
+
const isArrayIndexKey = (key: string): boolean => {
|
|
361
|
+
const index = Number(key)
|
|
362
|
+
return Number.isInteger(index) && index >= 0 && index < 0xffff_ffff && String(index) === key
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const captureNamedBranches = (
|
|
366
|
+
declarations: unknown,
|
|
367
|
+
path: string,
|
|
368
|
+
trigger: PropertyKey
|
|
369
|
+
): ReadonlyArray<CapturedNamedBranch> => {
|
|
370
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations)) {
|
|
371
|
+
throw new Error(`Machine branching transition for state "${path}" on "${String(trigger)}" requires a branch record`)
|
|
372
|
+
}
|
|
373
|
+
if (Object.getOwnPropertySymbols(declarations).length > 0) {
|
|
374
|
+
throw new Error(`Machine branching transition for state "${path}" on "${String(trigger)}" cannot use symbol keys`)
|
|
375
|
+
}
|
|
376
|
+
const keys = Object.keys(declarations)
|
|
377
|
+
if (keys.length === 0) {
|
|
378
|
+
throw new Error(`Machine branching transition for state "${path}" on "${String(trigger)}" requires a branch`)
|
|
379
|
+
}
|
|
380
|
+
return Object.freeze(keys.map((key) => {
|
|
381
|
+
if (key.length === 0 || isArrayIndexKey(key)) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Machine branching transition for state "${path}" on "${String(trigger)}" requires non-index string branch keys`
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
const declaration = (declarations as Record<string, unknown>)[key]
|
|
387
|
+
if (typeof declaration !== "object" || declaration === null || !hasProperty(declaration, "target")) {
|
|
388
|
+
throw new Error(`Machine transition branch "${key}" requires a target selection`)
|
|
389
|
+
}
|
|
390
|
+
const { target, title } = declaration as BranchDeclaration
|
|
391
|
+
if (!Topology.isTargetSelection(target)) {
|
|
392
|
+
throw new Error(`Machine transition branch "${key}" must select exactly one target`)
|
|
393
|
+
}
|
|
394
|
+
if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
|
|
395
|
+
throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
|
|
396
|
+
}
|
|
397
|
+
return Object.freeze({ key, title: title ?? key, selection: target })
|
|
398
|
+
}))
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const wrapSelectedBranchBuilder = (
|
|
402
|
+
builder: unknown,
|
|
403
|
+
owner: object,
|
|
404
|
+
branchIndex: number,
|
|
405
|
+
branchKey: string
|
|
406
|
+
): unknown => {
|
|
407
|
+
if (typeof builder === "function") {
|
|
408
|
+
const wrapped = (...args: ReadonlyArray<unknown>) =>
|
|
409
|
+
Topology.makeSelectedBranch(owner, branchIndex, branchKey, builder(...args))
|
|
410
|
+
for (const property of Reflect.ownKeys(builder)) {
|
|
411
|
+
if (
|
|
412
|
+
property === "length" || property === "name" || property === "prototype" || property === "caller" ||
|
|
413
|
+
property === "arguments"
|
|
414
|
+
) continue
|
|
415
|
+
const descriptor = Object.getOwnPropertyDescriptor(builder, property)
|
|
416
|
+
if (descriptor === undefined) continue
|
|
417
|
+
if ("value" in descriptor && typeof descriptor.value === "function") {
|
|
418
|
+
descriptor.value = wrapSelectedBranchBuilder(descriptor.value, owner, branchIndex, branchKey)
|
|
419
|
+
}
|
|
420
|
+
Object.defineProperty(wrapped, property, descriptor)
|
|
421
|
+
}
|
|
422
|
+
return wrapped
|
|
423
|
+
}
|
|
424
|
+
if (typeof builder === "object" && builder !== null) {
|
|
425
|
+
const wrapped: Record<PropertyKey, unknown> = {}
|
|
426
|
+
for (const property of Reflect.ownKeys(builder)) {
|
|
427
|
+
const descriptor = Object.getOwnPropertyDescriptor(builder, property)
|
|
428
|
+
if (descriptor === undefined) continue
|
|
429
|
+
if ("value" in descriptor && typeof descriptor.value === "function") {
|
|
430
|
+
descriptor.value = wrapSelectedBranchBuilder(descriptor.value, owner, branchIndex, branchKey)
|
|
431
|
+
}
|
|
432
|
+
Object.defineProperty(wrapped, property, descriptor)
|
|
433
|
+
}
|
|
434
|
+
return wrapped
|
|
435
|
+
}
|
|
436
|
+
throw new Error(`Machine could not construct transition branch "${branchKey}"`)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const makeBranchSelectors = (
|
|
440
|
+
context: Record<string, any>,
|
|
441
|
+
branches: ReadonlyArray<CapturedNamedBranch>,
|
|
442
|
+
owner: object,
|
|
443
|
+
stateNodes: Machine.StateNodes,
|
|
444
|
+
source: string
|
|
445
|
+
): Readonly<Record<string, unknown>> => {
|
|
446
|
+
const select: Record<string, unknown> = Object.create(null)
|
|
447
|
+
for (let branchIndex = 0; branchIndex < branches.length; branchIndex++) {
|
|
448
|
+
const branch = branches[branchIndex]!
|
|
449
|
+
select[branch.key] = wrapSelectedBranchBuilder(
|
|
450
|
+
getSelectionBuilder(context.target, branch.selection, stateNodes, source),
|
|
451
|
+
owner,
|
|
452
|
+
branchIndex,
|
|
453
|
+
branch.key
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
return Object.freeze(select)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const validateSelectedBranchResult = (
|
|
460
|
+
result: unknown,
|
|
461
|
+
selection: Topology.TargetSelection,
|
|
462
|
+
stateNodes: Machine.StateNodes
|
|
463
|
+
): void => {
|
|
464
|
+
if (selection.kind === "none") {
|
|
465
|
+
if (!Topology.isNoTarget(result)) {
|
|
466
|
+
throw new Error("Machine targetless branch must return its selected targetless builder")
|
|
467
|
+
}
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
if (result === undefined) {
|
|
471
|
+
throw new Error(`Machine transition branch selected "${selection.path}" without constructing its target`)
|
|
472
|
+
}
|
|
473
|
+
validateResolvedSelection(result, selection, stateNodes)
|
|
474
|
+
}
|
|
475
|
+
|
|
346
476
|
const captureTransition = (
|
|
347
477
|
transition: unknown,
|
|
348
478
|
stateNodes: Machine.StateNodes,
|
|
@@ -355,64 +485,53 @@ const captureTransition = (
|
|
|
355
485
|
const definition = transition as Record<PropertyKey, unknown>
|
|
356
486
|
const selector = makeTargetSelector(stateNodes, path)
|
|
357
487
|
const reenter = definition.reenter === true
|
|
358
|
-
if (
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
488
|
+
if (hasProperty(definition, "branches")) {
|
|
489
|
+
const branching = definition as { readonly branches: unknown; readonly resolve?: unknown }
|
|
490
|
+
if (typeof branching.branches !== "function" || typeof branching.resolve !== "function") {
|
|
361
491
|
throw new Error(
|
|
362
|
-
`Machine
|
|
492
|
+
`Machine branching transition for state "${path}" on "${String(trigger)}" requires branches and resolve`
|
|
363
493
|
)
|
|
364
494
|
}
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
495
|
+
const resolve = branching.resolve
|
|
496
|
+
const branches = captureNamedBranches(branching.branches(selector), path, trigger)
|
|
497
|
+
const owner = Object.freeze({})
|
|
498
|
+
const evaluate = (context: Record<string, any>, enqueue: unknown) => {
|
|
499
|
+
const resolverContext = { ...context }
|
|
500
|
+
delete resolverContext.target
|
|
501
|
+
resolverContext.select = makeBranchSelectors(context, branches, owner, stateNodes, path)
|
|
502
|
+
const selected = resolve(resolverContext, enqueue)
|
|
503
|
+
if (!Topology.isSelectedBranch(selected) || selected.owner !== owner) {
|
|
368
504
|
throw new Error(
|
|
369
|
-
`Machine
|
|
505
|
+
`Machine branching transition for state "${path}" on "${String(trigger)}" must select one declared branch`
|
|
370
506
|
)
|
|
371
507
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const evaluate = (context: Record<string, any>, enqueue: unknown) => {
|
|
376
|
-
const predicateContext = { ...context }
|
|
377
|
-
delete predicateContext.target
|
|
378
|
-
for (let branchIndex = 0; branchIndex < cases.length; branchIndex++) {
|
|
379
|
-
const branch = cases[branchIndex]!
|
|
380
|
-
const result = branch.when!(predicateContext)
|
|
381
|
-
if (!Option.isOption(result)) {
|
|
382
|
-
throw new Error(`Machine conditional transition case "${branch.title}" must return Option`)
|
|
383
|
-
}
|
|
384
|
-
if (Option.isSome(result)) {
|
|
385
|
-
return {
|
|
386
|
-
result: runCapturedBranch(branch, context, enqueue, stateNodes, path, { value: result.value }),
|
|
387
|
-
branchIndex
|
|
388
|
-
}
|
|
389
|
-
}
|
|
508
|
+
const branch = branches[selected.branchIndex]
|
|
509
|
+
if (branch === undefined || selected.branchKey !== branch.key) {
|
|
510
|
+
throw new Error(`Machine branching transition returned invalid branch evidence`)
|
|
390
511
|
}
|
|
512
|
+
validateSelectedBranchResult(selected.result, branch.selection, stateNodes)
|
|
391
513
|
return {
|
|
392
|
-
result:
|
|
393
|
-
branchIndex:
|
|
514
|
+
result: selected.result,
|
|
515
|
+
branchIndex: selected.branchIndex,
|
|
516
|
+
branchKey: selected.branchKey
|
|
394
517
|
}
|
|
395
518
|
}
|
|
396
519
|
return {
|
|
397
520
|
reenter,
|
|
398
521
|
targets: [
|
|
399
522
|
...new Set(
|
|
400
|
-
|
|
523
|
+
branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
|
|
401
524
|
)
|
|
402
525
|
],
|
|
403
|
-
branches:
|
|
404
|
-
|
|
405
|
-
type: "
|
|
406
|
-
|
|
526
|
+
branches: branches.map((branch) =>
|
|
527
|
+
Object.freeze({
|
|
528
|
+
type: "branch" as const,
|
|
529
|
+
key: branch.key,
|
|
530
|
+
title: branch.title,
|
|
407
531
|
target: branch.selection.path,
|
|
408
532
|
selection: transitionTargetSelection(branch.selection)
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
type: "otherwise" as const,
|
|
412
|
-
target: otherwise.selection.path,
|
|
413
|
-
selection: transitionTargetSelection(otherwise.selection)
|
|
414
|
-
}
|
|
415
|
-
],
|
|
533
|
+
})
|
|
534
|
+
),
|
|
416
535
|
evaluate,
|
|
417
536
|
transition: (context: Record<string, any>, enqueue: unknown) => evaluate(context, enqueue).result
|
|
418
537
|
}
|
|
@@ -420,7 +539,8 @@ const captureTransition = (
|
|
|
420
539
|
const branch = captureDefinitionBranch(transition, selector, path, trigger)
|
|
421
540
|
const evaluate = (context: Record<string, any>, enqueue: unknown) => ({
|
|
422
541
|
result: runCapturedBranch(branch, context, enqueue, stateNodes, path),
|
|
423
|
-
branchIndex: 0
|
|
542
|
+
branchIndex: 0,
|
|
543
|
+
branchKey: undefined
|
|
424
544
|
})
|
|
425
545
|
return {
|
|
426
546
|
reenter,
|
|
@@ -458,7 +578,7 @@ const captureInvokeDefinition = (
|
|
|
458
578
|
if (Array.isArray(invoke)) return invoke.map((item) => captureInvokeDefinition(item, stateNodes, path))
|
|
459
579
|
if (typeof invoke !== "object" || invoke === null) return invoke
|
|
460
580
|
const captured = { ...(invoke as Record<PropertyKey, unknown>) }
|
|
461
|
-
for (const key of ["onDone", "onFailure", "onSnapshot"] as const) {
|
|
581
|
+
for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
|
|
462
582
|
if (captured[key] !== undefined) {
|
|
463
583
|
captured[key] = captureTransition(captured[key], stateNodes, path, key)
|
|
464
584
|
}
|
|
@@ -525,15 +645,12 @@ const flattenHandlers = (
|
|
|
525
645
|
}
|
|
526
646
|
}
|
|
527
647
|
|
|
528
|
-
const makeHandle = (self:
|
|
648
|
+
const makeHandle = (self: Definition.Any): Definition.Any["handle"] =>
|
|
529
649
|
((config: Record<string, unknown>) => {
|
|
530
|
-
const handlers: Record<PropertyKey, Machine.AnyStateConfig> = Object.
|
|
531
|
-
Object.create(null),
|
|
532
|
-
self.handlers
|
|
533
|
-
)
|
|
650
|
+
const handlers: Record<PropertyKey, Machine.AnyStateConfig> = Object.create(null)
|
|
534
651
|
flattenHandlers(handlers, self.stateNodes, self.states, "", config)
|
|
535
|
-
return
|
|
536
|
-
}) as
|
|
652
|
+
return makeWithHandlers(self, handlers)
|
|
653
|
+
}) as Definition.Any["handle"]
|
|
537
654
|
|
|
538
655
|
export const isMachine = (
|
|
539
656
|
u: unknown
|
|
@@ -1141,12 +1258,19 @@ const compileInitial = (
|
|
|
1141
1258
|
}
|
|
1142
1259
|
}
|
|
1143
1260
|
|
|
1144
|
-
export const
|
|
1261
|
+
export const state: StateConstructor = (<const Node extends ReusableStateNodeConfig>(node: Node): Node => {
|
|
1262
|
+
StateDefinition.validateStateDefinitions({ state: node }, "Machine.state")
|
|
1263
|
+
return StateDefinition.captureStateDefinitions({ state: node }).state
|
|
1264
|
+
}) as StateConstructor
|
|
1265
|
+
|
|
1266
|
+
export const states: StatesConstructor = (<const States extends Machine.StateSchemas>(
|
|
1145
1267
|
states: States
|
|
1146
1268
|
): Machine.DefinedStates<States> => {
|
|
1147
|
-
StateDefinition.validateStateDefinitions(states, "Machine.
|
|
1269
|
+
StateDefinition.validateStateDefinitions(states, "Machine.states")
|
|
1270
|
+
const captured = StateDefinition.captureStateDefinitions(states)
|
|
1148
1271
|
return {
|
|
1149
|
-
states,
|
|
1272
|
+
states: captured,
|
|
1273
|
+
path: ((path: string) => path) as Machine.DefinedStates<States>["path"],
|
|
1150
1274
|
get:
|
|
1151
1275
|
((snapshot: Machine.AtomicSnapshot<string, unknown>, path: string) =>
|
|
1152
1276
|
Topology.getSnapshotByPath(snapshot, path).pipe(
|
|
@@ -1163,7 +1287,7 @@ export const defineStates: DefineStates = (<const States extends Machine.StateSc
|
|
|
1163
1287
|
((snapshot: Machine.AtomicSnapshot<string, unknown>, path: string) =>
|
|
1164
1288
|
Option.isSome(Topology.getSnapshotByPath(snapshot, path))) as Machine.DefinedStates<States>["matches"]
|
|
1165
1289
|
}
|
|
1166
|
-
}) as
|
|
1290
|
+
}) as StatesConstructor
|
|
1167
1291
|
|
|
1168
1292
|
type MakeConfig<
|
|
1169
1293
|
States extends Machine.StateSchemas,
|
|
@@ -1201,19 +1325,15 @@ type MakeResult<
|
|
|
1201
1325
|
InitialR,
|
|
1202
1326
|
InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1203
1327
|
ParentEvents extends ReadonlyArray<Machine.TaggedSchema>
|
|
1204
|
-
> =
|
|
1328
|
+
> = Definition<
|
|
1205
1329
|
States,
|
|
1206
1330
|
readonly [...InputEvents, ...InternalEvents],
|
|
1207
1331
|
Input,
|
|
1208
|
-
Machine.StateIdentifier<States>,
|
|
1209
|
-
never,
|
|
1210
|
-
never,
|
|
1211
1332
|
InitialE,
|
|
1212
1333
|
InitialR,
|
|
1213
1334
|
Machine.FinalStateFromDefinition<States>,
|
|
1214
1335
|
Machine.TerminalOutput<States>,
|
|
1215
1336
|
Emits,
|
|
1216
|
-
never,
|
|
1217
1337
|
InputEvents,
|
|
1218
1338
|
ParentEvents
|
|
1219
1339
|
>
|
|
@@ -71,6 +71,7 @@ export type MicrostepPlan<State, Event, E, R> = {
|
|
|
71
71
|
readonly trigger: Machine.TransitionTrigger
|
|
72
72
|
readonly reenter: boolean
|
|
73
73
|
readonly branchIndex: number
|
|
74
|
+
readonly branchKey: string | undefined
|
|
74
75
|
readonly target: string | undefined
|
|
75
76
|
readonly resolvedTarget: string | undefined
|
|
76
77
|
}>
|
|
@@ -108,6 +109,7 @@ export type TransitionHandler<States extends Machine.StateSchemas, E, R, Context
|
|
|
108
109
|
type TransitionEvaluation<States extends Machine.StateSchemas, E, R> = {
|
|
109
110
|
readonly result: Machine.HandlerResult<States, E, R>
|
|
110
111
|
readonly branchIndex: number
|
|
112
|
+
readonly branchKey: string | undefined
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
type TransitionEvaluator<States extends Machine.StateSchemas, E, R, Context> = (
|
|
@@ -214,11 +216,12 @@ const collectTransition = <
|
|
|
214
216
|
) => {
|
|
215
217
|
const collected = makeCollector<Event>(machine)
|
|
216
218
|
const evaluated = evaluate === undefined
|
|
217
|
-
? { result: transition(context, collected.enqueue), branchIndex: 0 }
|
|
219
|
+
? { result: transition(context, collected.enqueue), branchIndex: 0, branchKey: undefined }
|
|
218
220
|
: evaluate(context, collected.enqueue)
|
|
219
221
|
return {
|
|
220
222
|
state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
|
|
221
223
|
branchIndex: evaluated.branchIndex,
|
|
224
|
+
branchKey: evaluated.branchKey,
|
|
222
225
|
commands: collected.commands,
|
|
223
226
|
raisedEvents: collected.raisedEvents,
|
|
224
227
|
emittedEvents: collected.emittedEvents
|
|
@@ -523,6 +526,7 @@ export type SelectedTransition<States extends Machine.StateSchemas, E, R, Contex
|
|
|
523
526
|
export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R, Context> = {
|
|
524
527
|
readonly selection: SelectedTransition<States, E, R, Context>
|
|
525
528
|
readonly branchIndex: number
|
|
529
|
+
readonly branchKey: string | undefined
|
|
526
530
|
readonly unresolvedTarget:
|
|
527
531
|
| Machine.Snapshot<States>
|
|
528
532
|
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
@@ -544,6 +548,7 @@ export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R
|
|
|
544
548
|
readonly trigger: Machine.TransitionTrigger
|
|
545
549
|
readonly reenter: false
|
|
546
550
|
readonly branchIndex: number
|
|
551
|
+
readonly branchKey: string | undefined
|
|
547
552
|
readonly target: string
|
|
548
553
|
readonly resolvedTarget: string
|
|
549
554
|
}>
|
|
@@ -952,7 +957,9 @@ const selectInvocationTransition = <
|
|
|
952
957
|
return String(id) === event.id
|
|
953
958
|
})
|
|
954
959
|
if (invoke === undefined) return []
|
|
955
|
-
const handler = event.type === "
|
|
960
|
+
const handler = event.type === "element"
|
|
961
|
+
? invoke.onElement
|
|
962
|
+
: event.type === "done"
|
|
956
963
|
? invoke.onDone
|
|
957
964
|
: event.type === "failure"
|
|
958
965
|
? invoke.onFailure
|
|
@@ -968,7 +975,9 @@ const selectInvocationTransition = <
|
|
|
968
975
|
snapshot,
|
|
969
976
|
target: getTargetBuilder(machine, event.path),
|
|
970
977
|
id: event.id,
|
|
971
|
-
...(event.type === "
|
|
978
|
+
...(event.type === "element"
|
|
979
|
+
? { element: event.element }
|
|
980
|
+
: event.type === "done"
|
|
972
981
|
? { output: event.output }
|
|
973
982
|
: event.type === "failure"
|
|
974
983
|
? { error: event.error }
|
|
@@ -1150,6 +1159,7 @@ interface ResolvedChoiceTransition {
|
|
|
1150
1159
|
readonly trigger: Machine.TransitionTrigger
|
|
1151
1160
|
readonly reenter: false
|
|
1152
1161
|
readonly branchIndex: number
|
|
1162
|
+
readonly branchKey: string | undefined
|
|
1153
1163
|
readonly target: string
|
|
1154
1164
|
readonly resolvedTarget: string
|
|
1155
1165
|
}
|
|
@@ -1228,6 +1238,7 @@ function resolveChoiceTarget(
|
|
|
1228
1238
|
trigger: { type: "choice" },
|
|
1229
1239
|
reenter: false,
|
|
1230
1240
|
branchIndex: collected.branchIndex,
|
|
1241
|
+
branchKey: collected.branchKey,
|
|
1231
1242
|
target: returnedPath,
|
|
1232
1243
|
resolvedTarget: nested?.target.path ?? returnedPath
|
|
1233
1244
|
})
|
|
@@ -1398,6 +1409,7 @@ const collectEvaluatedTransition = <
|
|
|
1398
1409
|
return {
|
|
1399
1410
|
selection,
|
|
1400
1411
|
branchIndex: transitionResult.branchIndex,
|
|
1412
|
+
branchKey: transitionResult.branchKey,
|
|
1401
1413
|
unresolvedTarget,
|
|
1402
1414
|
target,
|
|
1403
1415
|
commands: [
|
|
@@ -1444,6 +1456,7 @@ const collectEvaluatedTransition = <
|
|
|
1444
1456
|
return {
|
|
1445
1457
|
selection,
|
|
1446
1458
|
branchIndex: transitionResult.branchIndex,
|
|
1459
|
+
branchKey: transitionResult.branchKey,
|
|
1447
1460
|
unresolvedTarget,
|
|
1448
1461
|
target,
|
|
1449
1462
|
commands: [
|
|
@@ -1799,6 +1812,7 @@ const microstep = <
|
|
|
1799
1812
|
trigger: transition.selection.trigger,
|
|
1800
1813
|
reenter: transition.selection.transition.reenter,
|
|
1801
1814
|
branchIndex: transition.branchIndex,
|
|
1815
|
+
branchKey: transition.branchKey,
|
|
1802
1816
|
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
1803
1817
|
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
1804
1818
|
},
|