@typeonce/effect-machine 0.12.0 → 0.14.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 +130 -30
- package/dist/Machine.d.ts +722 -262
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +130 -62
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +1 -0
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +23 -0
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
- package/dist/internal/machine/commandRuntime.js +2 -5
- package/dist/internal/machine/commandRuntime.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts +5 -0
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +6 -2
- package/dist/internal/machine/configuration.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts +1 -0
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +57 -24
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/initialization.d.ts +5 -0
- package/dist/internal/machine/initialization.d.ts.map +1 -0
- package/dist/internal/machine/initialization.js +61 -0
- package/dist/internal/machine/initialization.js.map +1 -0
- package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
- package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
- package/dist/internal/machine/inspectionRuntime.js +79 -0
- package/dist/internal/machine/inspectionRuntime.js.map +1 -0
- package/dist/internal/machine/invocation.d.ts +2 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +17 -10
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +4 -3
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +327 -32
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +28 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +132 -51
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/process.d.ts.map +1 -1
- package/dist/internal/machine/process.js +4 -2
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +27 -3
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +440 -64
- package/dist/internal/machine/runtime.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 +25 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +26 -10
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/exploration.d.ts.map +1 -1
- package/dist/internal/testing/machine/exploration.js +11 -2
- package/dist/internal/testing/machine/exploration.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +75 -69
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts +20 -0
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -0
- package/dist/internal/testing/machine/transitionCoverage.js +80 -0
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +171 -33
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +97 -22
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +58 -16
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +4 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +4 -1
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +21 -3
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +21 -3
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +240 -65
- package/package.json +5 -5
- package/src/Machine.ts +1240 -495
- package/src/internal/machine/atom.ts +26 -0
- package/src/internal/machine/commandRuntime.ts +2 -9
- package/src/internal/machine/configuration.ts +28 -3
- package/src/internal/machine/executionPlan.ts +72 -24
- package/src/internal/machine/initialization.ts +73 -0
- package/src/internal/machine/inspectionRuntime.ts +102 -0
- package/src/internal/machine/invocation.ts +37 -12
- package/src/internal/machine/machine.ts +438 -39
- package/src/internal/machine/planner.ts +180 -55
- package/src/internal/machine/process.ts +4 -2
- package/src/internal/machine/runtime.ts +661 -89
- package/src/internal/machine/symbols.ts +3 -0
- package/src/internal/machine/topology.ts +65 -12
- package/src/internal/testing/machine/exploration.ts +10 -2
- package/src/internal/testing/machine/finiteModel.ts +114 -80
- package/src/internal/testing/machine/transitionCoverage.ts +116 -0
- package/src/internal/testing/machine/verification.ts +216 -58
- package/src/testing/MachineTest.ts +118 -28
- package/src/unstable/cluster/ClusterMachine.ts +4 -1
- package/src/unstable/reactivity/AtomMachine.ts +24 -3
|
@@ -139,6 +139,32 @@ export const emissions = <State, Event, Error, Output, StartError, Emitted>(
|
|
|
139
139
|
)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
export const inspection = <State, Event, Error, Output, StartError, Emitted>(
|
|
143
|
+
self: MachineAtom<State, Event, Error, Output, StartError, Emitted>
|
|
144
|
+
): Stream.Stream<Machine.Inspection.Event, StartError, AtomRegistry.AtomRegistry> => {
|
|
145
|
+
const prepared = preparedByMachineAtom.get(self as object)
|
|
146
|
+
if (prepared === undefined) return Stream.empty
|
|
147
|
+
return Stream.unwrap(
|
|
148
|
+
Effect.gen(function*() {
|
|
149
|
+
const registry = yield* AtomRegistry.AtomRegistry
|
|
150
|
+
const releasePrepared = yield* Effect.sync(() => registry.mount(prepared))
|
|
151
|
+
yield* Effect.addFinalizer(() => Effect.sync(releasePrepared))
|
|
152
|
+
const machine = yield* Atom.getResult(prepared)
|
|
153
|
+
const pull = yield* Stream.toPull(machine.inspection)
|
|
154
|
+
const firstPull = yield* pull.pipe(Effect.forkScoped({ startImmediately: true }))
|
|
155
|
+
const releaseRef = yield* Effect.sync(() => registry.mount(self.ref))
|
|
156
|
+
yield* Effect.addFinalizer(() => Effect.sync(releaseRef))
|
|
157
|
+
yield* Atom.getResult(self.ref)
|
|
158
|
+
let first = true
|
|
159
|
+
return Stream.fromPull(Effect.succeed(Effect.suspend(() => {
|
|
160
|
+
if (!first) return pull
|
|
161
|
+
first = false
|
|
162
|
+
return Fiber.join(firstPull)
|
|
163
|
+
})))
|
|
164
|
+
})
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
export const childEmissions = <Child extends Machine.ChildMachine.Any, StartError>(
|
|
143
169
|
self: ChildMachineAtom<Child, StartError>
|
|
144
170
|
): Stream.Stream<RefEmitted<Machine.ChildMachine.Ref<Child>>, StartError, AtomRegistry.AtomRegistry> =>
|
|
@@ -10,18 +10,13 @@ import type { RuntimeCommand } from "./command.js"
|
|
|
10
10
|
import { decodeEmit, decodeEvent } from "./protocol.js"
|
|
11
11
|
import type { ProcessScope } from "./runtime.js"
|
|
12
12
|
|
|
13
|
-
const isMachineTarget = (
|
|
14
|
-
target: unknown
|
|
15
|
-
): target is { readonly send: (event: unknown) => Effect.Effect<void, unknown> } =>
|
|
16
|
-
typeof target === "object" && target !== null && "send" in target && typeof target.send === "function"
|
|
17
|
-
|
|
18
13
|
export const makeLiveRuntime = <Events, Emits>(
|
|
19
14
|
machine: Machine.Any,
|
|
20
15
|
scope: ProcessScope<Events>
|
|
21
16
|
): Runtime<Events, Emits> => ({
|
|
22
17
|
raise: (event) =>
|
|
23
18
|
decodeEvent(machine, event).pipe(
|
|
24
|
-
Effect.flatMap((event) => scope.self
|
|
19
|
+
Effect.flatMap((event) => scope.sendTo(scope.self, event as Events))
|
|
25
20
|
),
|
|
26
21
|
emit: (event) =>
|
|
27
22
|
decodeEmit(machine, event).pipe(
|
|
@@ -35,9 +30,7 @@ export const runCommands = <Event>(
|
|
|
35
30
|
) =>
|
|
36
31
|
Effect.forEach(commands, (command) =>
|
|
37
32
|
command._tag === "SendTo"
|
|
38
|
-
?
|
|
39
|
-
? command.target.send(command.event)
|
|
40
|
-
: scope.sendTo(command.target as never, command.event)
|
|
33
|
+
? scope.sendTo(command.target as never, command.event)
|
|
41
34
|
: scope.stopChild(command.child as never), { discard: true })
|
|
42
35
|
|
|
43
36
|
export const runEmittedEvents = <Events, Emits>(
|
|
@@ -17,7 +17,14 @@ import {
|
|
|
17
17
|
decodeStateValue,
|
|
18
18
|
decodeStateValueSync
|
|
19
19
|
} from "./protocol.js"
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
getNode,
|
|
22
|
+
getStateNodeSchema,
|
|
23
|
+
type InitialTarget as InitialTargetInstruction,
|
|
24
|
+
isSnapshot,
|
|
25
|
+
isTarget,
|
|
26
|
+
TargetSnapshotTypeId
|
|
27
|
+
} from "./topology.js"
|
|
21
28
|
|
|
22
29
|
export interface HistoryRecord {
|
|
23
30
|
readonly mode: "shallow" | "deep"
|
|
@@ -952,7 +959,8 @@ const configurationFromTargetPathSync = (
|
|
|
952
959
|
current: ActiveConfiguration,
|
|
953
960
|
path: string,
|
|
954
961
|
value: unknown,
|
|
955
|
-
providedValues: Readonly<Record<string, unknown>> | undefined
|
|
962
|
+
providedValues: Readonly<Record<string, unknown>> | undefined,
|
|
963
|
+
allowIncomplete = false
|
|
956
964
|
): ActiveConfiguration => {
|
|
957
965
|
const node = getNode(machine, path)
|
|
958
966
|
const active = new Set<string>()
|
|
@@ -998,12 +1006,29 @@ const configurationFromTargetPathSync = (
|
|
|
998
1006
|
}
|
|
999
1007
|
}
|
|
1000
1008
|
|
|
1001
|
-
if (node.type === "compound" || node.type === "parallel") {
|
|
1009
|
+
if (!allowIncomplete && (node.type === "compound" || node.type === "parallel")) {
|
|
1002
1010
|
throw new Error(`Machine target "${node.path}" must include an active child state`)
|
|
1003
1011
|
}
|
|
1004
1012
|
return { active, values, outputs, history: current.history }
|
|
1005
1013
|
}
|
|
1006
1014
|
|
|
1015
|
+
/** Builds the selected path and its ancestors without requiring nested active
|
|
1016
|
+
* children. The planner completes those children through declared initial
|
|
1017
|
+
* topology and `initialize` handlers before normalization continues. */
|
|
1018
|
+
export const configurationFromInitialTargetSync = (
|
|
1019
|
+
machine: Machine.Any,
|
|
1020
|
+
current: ActiveConfiguration,
|
|
1021
|
+
target: InitialTargetInstruction
|
|
1022
|
+
): ActiveConfiguration =>
|
|
1023
|
+
configurationFromTargetPathSync(
|
|
1024
|
+
machine,
|
|
1025
|
+
current,
|
|
1026
|
+
target.path,
|
|
1027
|
+
target.value,
|
|
1028
|
+
target.values as Readonly<Record<string, unknown>> | undefined,
|
|
1029
|
+
true
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1007
1032
|
const configurationFromTargetSnapshotSync = (
|
|
1008
1033
|
machine: Machine.Any,
|
|
1009
1034
|
current: ActiveConfiguration,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
normalizeTransition,
|
|
39
39
|
planConfiguration,
|
|
40
40
|
removeConflictingTransitions,
|
|
41
|
+
resolveInitialTarget,
|
|
41
42
|
type SelectedTransition,
|
|
42
43
|
sortEntryPaths,
|
|
43
44
|
sortEvaluatedTransitions,
|
|
@@ -46,7 +47,7 @@ import {
|
|
|
46
47
|
validateDeclaredTransitionTarget
|
|
47
48
|
} from "./planner.js"
|
|
48
49
|
import { decodeEmitSync, decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js"
|
|
49
|
-
import { isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
50
|
+
import { isInitialTarget, isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
50
51
|
|
|
51
52
|
interface IndexedExecutionDescriptor {
|
|
52
53
|
readonly flat: boolean
|
|
@@ -91,7 +92,7 @@ const resolveMachineReferences = (
|
|
|
91
92
|
: { self: machineReferences.self, parent: machineReferences.parent }
|
|
92
93
|
|
|
93
94
|
const indexedStateConfigKeys: ReadonlySet<PropertyKey> = new Set([
|
|
94
|
-
"
|
|
95
|
+
"initialize",
|
|
95
96
|
"invoke",
|
|
96
97
|
"on",
|
|
97
98
|
"output"
|
|
@@ -149,7 +150,7 @@ const compileIndexedExecutionDescriptor = (
|
|
|
149
150
|
}
|
|
150
151
|
const byEvent = new Map<PropertyKey, MicrostepTransition<any, any, any, any>>()
|
|
151
152
|
for (const tag of Reflect.ownKeys(config.on)) {
|
|
152
|
-
const transition = normalizeTransition(config.on[tag])
|
|
153
|
+
const transition = normalizeTransition(config.on[tag] as Parameters<typeof normalizeTransition>[0])
|
|
153
154
|
if (transition !== undefined) {
|
|
154
155
|
byEvent.set(tag, transition)
|
|
155
156
|
}
|
|
@@ -402,6 +403,7 @@ const emptyExecutionValues: ReadonlyArray<never> = Object.freeze([])
|
|
|
402
403
|
export interface ExecutionMicrostep<State = unknown> {
|
|
403
404
|
readonly next: State
|
|
404
405
|
readonly event: unknown
|
|
406
|
+
readonly transitions?: ReadonlyArray<Machine.RetainedTransition>
|
|
405
407
|
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
406
408
|
readonly raisedEvents: ReadonlyArray<unknown>
|
|
407
409
|
readonly emittedEvents: ReadonlyArray<unknown>
|
|
@@ -422,12 +424,13 @@ export interface ExecutionMacrostep<State = unknown> {
|
|
|
422
424
|
const collectIndexedTransition = (
|
|
423
425
|
machine: Machine.Any,
|
|
424
426
|
transition: TransitionHandler<any, any, any, any>,
|
|
425
|
-
context: any
|
|
427
|
+
context: any,
|
|
428
|
+
evaluate?: NonNullable<MicrostepTransition<any, any, any, any>["evaluate"]>
|
|
426
429
|
) => {
|
|
427
430
|
let commands: Array<RuntimeCommand> | undefined
|
|
428
431
|
let raisedEvents: Array<any> | undefined
|
|
429
432
|
let emittedEvents: Array<unknown> | undefined
|
|
430
|
-
const
|
|
433
|
+
const enqueue = {
|
|
431
434
|
raise: (event: unknown) => {
|
|
432
435
|
;(raisedEvents ??= []).push(decodeEventSync(machine, event))
|
|
433
436
|
},
|
|
@@ -440,9 +443,13 @@ const collectIndexedTransition = (
|
|
|
440
443
|
stop: (child: unknown) => {
|
|
441
444
|
;(commands ??= []).push({ _tag: "Stop", child: child as any })
|
|
442
445
|
}
|
|
443
|
-
}
|
|
446
|
+
}
|
|
447
|
+
const evaluated = evaluate === undefined
|
|
448
|
+
? { result: transition(context, enqueue), branchIndex: 0 }
|
|
449
|
+
: evaluate(context, enqueue)
|
|
444
450
|
return {
|
|
445
|
-
state: isNoTarget(result) ? undefined : result,
|
|
451
|
+
state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
|
|
452
|
+
branchIndex: evaluated.branchIndex,
|
|
446
453
|
commands: commands ?? emptyExecutionValues,
|
|
447
454
|
raisedEvents: raisedEvents ?? emptyExecutionValues,
|
|
448
455
|
emittedEvents: emittedEvents ?? emptyExecutionValues
|
|
@@ -529,14 +536,28 @@ const collectIndexedEvaluatedTransition = (
|
|
|
529
536
|
state: OwnedIndexedState,
|
|
530
537
|
selection: IndexedSelectedTransition
|
|
531
538
|
): IndexedEvaluatedTransition => {
|
|
532
|
-
const transitionResult = collectIndexedTransition(
|
|
533
|
-
|
|
539
|
+
const transitionResult = collectIndexedTransition(
|
|
540
|
+
machine,
|
|
541
|
+
selection.transition.transition,
|
|
542
|
+
selection.context,
|
|
543
|
+
selection.transition.evaluate
|
|
544
|
+
)
|
|
545
|
+
const unresolvedTarget = transitionResult.state
|
|
534
546
|
validateDeclaredTransitionTarget(
|
|
535
547
|
selection.sourcePath,
|
|
536
548
|
selection.trigger,
|
|
537
549
|
selection.transition.targets,
|
|
538
|
-
|
|
550
|
+
unresolvedTarget
|
|
539
551
|
)
|
|
552
|
+
const initialResolution = unresolvedTarget !== undefined && isInitialTarget(unresolvedTarget)
|
|
553
|
+
? resolveInitialTarget(
|
|
554
|
+
machine,
|
|
555
|
+
activeConfigurationFromIndexedState(descriptor, state),
|
|
556
|
+
unresolvedTarget,
|
|
557
|
+
(selection.context as any).event
|
|
558
|
+
)
|
|
559
|
+
: undefined
|
|
560
|
+
const target = initialResolution?.target ?? unresolvedTarget
|
|
540
561
|
if (target !== undefined && !isTarget(target) && !isSnapshot(target)) {
|
|
541
562
|
throw new Error("Machine expected indexed transition target to be a snapshot or target builder result")
|
|
542
563
|
}
|
|
@@ -547,16 +568,17 @@ const collectIndexedEvaluatedTransition = (
|
|
|
547
568
|
if (!changed) {
|
|
548
569
|
return {
|
|
549
570
|
selection,
|
|
550
|
-
|
|
571
|
+
branchIndex: transitionResult.branchIndex,
|
|
572
|
+
unresolvedTarget: unresolvedTarget as any,
|
|
551
573
|
target: target as any,
|
|
552
574
|
next,
|
|
553
|
-
commands: transitionResult.commands,
|
|
554
|
-
raisedEvents: transitionResult.raisedEvents,
|
|
555
|
-
emittedEvents: transitionResult.emittedEvents,
|
|
575
|
+
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
576
|
+
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
577
|
+
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
556
578
|
changed: false,
|
|
557
579
|
exitPaths: [],
|
|
558
580
|
entryPaths: [],
|
|
559
|
-
choiceTransitions: []
|
|
581
|
+
choiceTransitions: initialResolution?.transitions ?? []
|
|
560
582
|
}
|
|
561
583
|
}
|
|
562
584
|
|
|
@@ -570,16 +592,17 @@ const collectIndexedEvaluatedTransition = (
|
|
|
570
592
|
: naturalBoundary
|
|
571
593
|
return {
|
|
572
594
|
selection,
|
|
573
|
-
|
|
595
|
+
branchIndex: transitionResult.branchIndex,
|
|
596
|
+
unresolvedTarget: unresolvedTarget as any,
|
|
574
597
|
target: target as any,
|
|
575
598
|
next,
|
|
576
|
-
commands: transitionResult.commands,
|
|
577
|
-
raisedEvents: transitionResult.raisedEvents,
|
|
578
|
-
emittedEvents: transitionResult.emittedEvents,
|
|
599
|
+
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
600
|
+
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
601
|
+
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
579
602
|
changed: true,
|
|
580
603
|
exitPaths: getExitPaths(machine, activeConfigurationFromIndexedState(descriptor, state), boundary),
|
|
581
604
|
entryPaths: getEntryPaths(machine, activeConfigurationFromIndexedState(descriptor, next), boundary),
|
|
582
|
-
choiceTransitions: []
|
|
605
|
+
choiceTransitions: initialResolution?.transitions ?? []
|
|
583
606
|
}
|
|
584
607
|
}
|
|
585
608
|
|
|
@@ -588,13 +611,23 @@ const indexedMicrostep = (
|
|
|
588
611
|
descriptor: IndexedExecutionDescriptor,
|
|
589
612
|
state: OwnedIndexedState,
|
|
590
613
|
event: any,
|
|
591
|
-
selections: ReadonlyArray<IndexedSelectedTransition
|
|
614
|
+
selections: ReadonlyArray<IndexedSelectedTransition>,
|
|
615
|
+
retainTransitions: boolean
|
|
592
616
|
): ExecutionMicrostep<OwnedIndexedState> => {
|
|
617
|
+
const retain = (transition: IndexedEvaluatedTransition): Machine.RetainedTransition => ({
|
|
618
|
+
source: transition.selection.sourcePath,
|
|
619
|
+
trigger: transition.selection.trigger,
|
|
620
|
+
reenter: transition.selection.transition.reenter,
|
|
621
|
+
branchIndex: transition.branchIndex,
|
|
622
|
+
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
623
|
+
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
624
|
+
})
|
|
593
625
|
if (selections.length === 1) {
|
|
594
626
|
const transition = collectIndexedEvaluatedTransition(machine, descriptor, state, selections[0]!)
|
|
595
627
|
return {
|
|
596
628
|
next: transition.next,
|
|
597
629
|
event,
|
|
630
|
+
...(retainTransitions ? { transitions: [retain(transition)] } : undefined),
|
|
598
631
|
commands: transition.commands,
|
|
599
632
|
raisedEvents: transition.raisedEvents,
|
|
600
633
|
emittedEvents: transition.emittedEvents,
|
|
@@ -645,6 +678,7 @@ const indexedMicrostep = (
|
|
|
645
678
|
return {
|
|
646
679
|
next,
|
|
647
680
|
event,
|
|
681
|
+
...(retainTransitions ? { transitions: transitions.map(retain) } : undefined),
|
|
648
682
|
commands,
|
|
649
683
|
raisedEvents,
|
|
650
684
|
emittedEvents,
|
|
@@ -719,7 +753,8 @@ const planIndexedFlatState = (
|
|
|
719
753
|
event,
|
|
720
754
|
snapshot: snapshotFromIndexedState(descriptor, current),
|
|
721
755
|
target: getTargetBuilder(machine, sourcePath)
|
|
722
|
-
}
|
|
756
|
+
},
|
|
757
|
+
transition.evaluate
|
|
723
758
|
)
|
|
724
759
|
const target = transitionResult.state
|
|
725
760
|
validateDeclaredTransitionTarget(
|
|
@@ -762,6 +797,18 @@ const planIndexedFlatState = (
|
|
|
762
797
|
const step: ExecutionMicrostep<OwnedIndexedState> = {
|
|
763
798
|
next,
|
|
764
799
|
event,
|
|
800
|
+
...(retainMicrosteps
|
|
801
|
+
? {
|
|
802
|
+
transitions: [{
|
|
803
|
+
source: sourcePath,
|
|
804
|
+
trigger: { type: "event", event: event._tag },
|
|
805
|
+
reenter: transition.reenter,
|
|
806
|
+
branchIndex: transitionResult.branchIndex,
|
|
807
|
+
target: target === undefined ? undefined : getTargetNodePath(target as any),
|
|
808
|
+
resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any)
|
|
809
|
+
}]
|
|
810
|
+
}
|
|
811
|
+
: undefined),
|
|
765
812
|
commands: transitionResult.commands,
|
|
766
813
|
raisedEvents: transitionResult.raisedEvents,
|
|
767
814
|
emittedEvents: transitionResult.emittedEvents,
|
|
@@ -828,6 +875,7 @@ const planIndexedState = (
|
|
|
828
875
|
microsteps: planned.microsteps.map((step) => ({
|
|
829
876
|
next: ownedIndexedStateFromActive(descriptor, step.next),
|
|
830
877
|
event: step.event,
|
|
878
|
+
transitions: step.transitions,
|
|
831
879
|
commands: step.commands,
|
|
832
880
|
raisedEvents: step.raisedEvents,
|
|
833
881
|
emittedEvents: step.emittedEvents,
|
|
@@ -874,7 +922,7 @@ const planIndexedState = (
|
|
|
874
922
|
}
|
|
875
923
|
}
|
|
876
924
|
|
|
877
|
-
const first = indexedMicrostep(machine, descriptor, configuration, decoded, selections)
|
|
925
|
+
const first = indexedMicrostep(machine, descriptor, configuration, decoded, selections, retainMicrosteps)
|
|
878
926
|
let current = first.next
|
|
879
927
|
let currentEvent: any = decoded
|
|
880
928
|
const commands = [...first.commands]
|
|
@@ -932,7 +980,7 @@ const planIndexedState = (
|
|
|
932
980
|
currentEvent = raised
|
|
933
981
|
const raisedSelections = selectIndexedEventTransitions(machine, descriptor, current, raised, machineReferences)
|
|
934
982
|
if (raisedSelections.length === 0) continue
|
|
935
|
-
const step = indexedMicrostep(machine, descriptor, current, raised, raisedSelections)
|
|
983
|
+
const step = indexedMicrostep(machine, descriptor, current, raised, raisedSelections, retainMicrosteps)
|
|
936
984
|
current = step.next
|
|
937
985
|
commands.push(...step.commands)
|
|
938
986
|
raisedEvents.push(...step.raisedEvents)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Internal builders for schema-valued default state entry. */
|
|
2
|
+
|
|
3
|
+
import { hasProperty } from "effect/Predicate"
|
|
4
|
+
import type { Machine } from "../../Machine.js"
|
|
5
|
+
import { SnapshotBuilderStateTypeId } from "./symbols.js"
|
|
6
|
+
import * as Topology from "./topology.js"
|
|
7
|
+
|
|
8
|
+
const getNode = (machine: Machine.Any, path: string): Machine.StateNode => {
|
|
9
|
+
const node = machine.stateNodes.byPath.get(path)
|
|
10
|
+
if (node === undefined) {
|
|
11
|
+
throw new Error(`Machine expected state path "${path}" to exist`)
|
|
12
|
+
}
|
|
13
|
+
return node
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const withFrom = <Method extends (value: unknown) => unknown>(method: Method) => {
|
|
17
|
+
Object.defineProperty(method, "from", {
|
|
18
|
+
value: (...args: ReadonlyArray<unknown>) => method(Topology.makeStateInput(args.length === 0 ? {} : args[0])),
|
|
19
|
+
enumerable: false
|
|
20
|
+
})
|
|
21
|
+
return method as Method & { readonly from: (...args: ReadonlyArray<unknown>) => unknown }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const makeCompletion = (values: Readonly<Record<string, unknown>>): object => {
|
|
25
|
+
const completion = {}
|
|
26
|
+
Object.defineProperty(completion, SnapshotBuilderStateTypeId, {
|
|
27
|
+
value: values,
|
|
28
|
+
enumerable: false
|
|
29
|
+
})
|
|
30
|
+
return completion
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const makeParallelBuilder = (
|
|
34
|
+
machine: Machine.Any,
|
|
35
|
+
node: Machine.StateNode,
|
|
36
|
+
values: Readonly<Record<string, unknown>>
|
|
37
|
+
): object => {
|
|
38
|
+
const builder = makeCompletion(values) as Record<string | symbol, unknown>
|
|
39
|
+
for (const childPath of node.children) {
|
|
40
|
+
const child = getNode(machine, childPath)
|
|
41
|
+
if (child.schema === undefined || hasProperty(values, child.key)) continue
|
|
42
|
+
builder[child.key] = withFrom((value: unknown) => {
|
|
43
|
+
const nextValues: Record<string, unknown> = Object.assign({}, values)
|
|
44
|
+
nextValues[child.key] = value
|
|
45
|
+
return makeParallelBuilder(machine, node, nextValues)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
return builder
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const makeStateInitializeBuilder = (machine: Machine.Any, path: string): object => {
|
|
52
|
+
const node = getNode(machine, path)
|
|
53
|
+
if (node.type === "parallel") {
|
|
54
|
+
return makeParallelBuilder(machine, node, {})
|
|
55
|
+
}
|
|
56
|
+
if (node.type !== "compound") {
|
|
57
|
+
throw new Error(`Machine state "${path}" cannot initialize child states`)
|
|
58
|
+
}
|
|
59
|
+
const child = getNode(machine, node.initial)
|
|
60
|
+
if (child.schema === undefined) {
|
|
61
|
+
throw new Error(`Machine state "${path}" has no schema-valued initial child`)
|
|
62
|
+
}
|
|
63
|
+
return withFrom((value: unknown) => makeCompletion({ [child.key]: value }))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const getStateInitializeValues = (path: string, result: unknown): Readonly<Record<string, unknown>> => {
|
|
67
|
+
if (typeof result !== "object" || result === null || !hasProperty(result, SnapshotBuilderStateTypeId)) {
|
|
68
|
+
throw new Error(`Machine initialize handler for "${path}" must return its builder result`)
|
|
69
|
+
}
|
|
70
|
+
return (result as { readonly [SnapshotBuilderStateTypeId]: Readonly<Record<string, unknown>> })[
|
|
71
|
+
SnapshotBuilderStateTypeId
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root-scoped live inspection publication and evidence projection.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.13.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as PubSub from "effect/PubSub"
|
|
9
|
+
import * as Stream from "effect/Stream"
|
|
10
|
+
import type { Inspection } from "../../Machine.js"
|
|
11
|
+
|
|
12
|
+
export type Draft<Event extends Inspection.Event = Inspection.Event> = Event extends Inspection.Event
|
|
13
|
+
? Omit<Event, "sequence" | "rootSessionId">
|
|
14
|
+
: never
|
|
15
|
+
|
|
16
|
+
export interface Runtime {
|
|
17
|
+
readonly rootSessionId: string
|
|
18
|
+
readonly stream: Stream.Stream<Inspection.Event>
|
|
19
|
+
readonly isActive: () => boolean
|
|
20
|
+
readonly nextDeliveryId: () => number
|
|
21
|
+
readonly nextMacrostepId: () => number
|
|
22
|
+
readonly publishUnsafe: (event: Draft) => void
|
|
23
|
+
readonly close: Effect.Effect<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const make = (rootSessionId: string): Effect.Effect<Runtime> =>
|
|
27
|
+
Effect.gen(function*() {
|
|
28
|
+
const pubsub = yield* PubSub.unbounded<Inspection.Event>()
|
|
29
|
+
let subscribers = 0
|
|
30
|
+
let sequence = 0
|
|
31
|
+
let deliveryId = 0
|
|
32
|
+
let macrostepId = 0
|
|
33
|
+
return {
|
|
34
|
+
rootSessionId,
|
|
35
|
+
stream: Stream.fromPubSub(pubsub).pipe(
|
|
36
|
+
Stream.onStart(Effect.sync(() => {
|
|
37
|
+
subscribers += 1
|
|
38
|
+
})),
|
|
39
|
+
Stream.ensuring(Effect.sync(() => {
|
|
40
|
+
subscribers -= 1
|
|
41
|
+
}))
|
|
42
|
+
),
|
|
43
|
+
isActive: () => subscribers > 0,
|
|
44
|
+
nextDeliveryId: () => deliveryId++,
|
|
45
|
+
nextMacrostepId: () => macrostepId++,
|
|
46
|
+
publishUnsafe: (event) => {
|
|
47
|
+
if (subscribers === 0) return
|
|
48
|
+
PubSub.publishUnsafe(pubsub, {
|
|
49
|
+
...event,
|
|
50
|
+
sequence: sequence++,
|
|
51
|
+
rootSessionId
|
|
52
|
+
} as Inspection.Event)
|
|
53
|
+
},
|
|
54
|
+
close: PubSub.shutdown(pubsub)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export const endpoint = (
|
|
59
|
+
target: { readonly id: string; readonly sessionId: string }
|
|
60
|
+
): Inspection.Endpoint => ({
|
|
61
|
+
id: target.id,
|
|
62
|
+
sessionId: target.sessionId
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const commands = (values: ReadonlyArray<unknown>): ReadonlyArray<Inspection.Command> =>
|
|
66
|
+
values.flatMap((command): ReadonlyArray<Inspection.Command> => {
|
|
67
|
+
if (typeof command !== "object" || command === null || !("_tag" in command)) return []
|
|
68
|
+
if (command._tag === "SendTo" && "target" in command && "event" in command) {
|
|
69
|
+
const target = command.target
|
|
70
|
+
return typeof target === "object" && target !== null && "id" in target
|
|
71
|
+
? [{
|
|
72
|
+
_tag: "SendTo",
|
|
73
|
+
target: "sessionId" in target ? endpoint(target as any) : { id: String(target.id) },
|
|
74
|
+
event: command.event
|
|
75
|
+
}]
|
|
76
|
+
: []
|
|
77
|
+
}
|
|
78
|
+
if (command._tag === "Stop" && "child" in command) {
|
|
79
|
+
const child = command.child
|
|
80
|
+
if (typeof child === "string") return [{ _tag: "Stop", target: { id: child } }]
|
|
81
|
+
return typeof child === "object" && child !== null && "id" in child
|
|
82
|
+
? [{ _tag: "Stop", target: "sessionId" in child ? endpoint(child as any) : { id: String(child.id) } }]
|
|
83
|
+
: []
|
|
84
|
+
}
|
|
85
|
+
return []
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export const microsteps = (plan: unknown): ReadonlyArray<Inspection.Microstep> => {
|
|
89
|
+
if (typeof plan !== "object" || plan === null || !("microsteps" in plan) || !Array.isArray(plan.microsteps)) {
|
|
90
|
+
return []
|
|
91
|
+
}
|
|
92
|
+
return plan.microsteps.map((step: any) => ({
|
|
93
|
+
event: step.event,
|
|
94
|
+
transitions: Array.isArray(step.transitions) ? step.transitions : [],
|
|
95
|
+
raisedEvents: Array.isArray(step.raisedEvents) ? step.raisedEvents : [],
|
|
96
|
+
emittedEvents: Array.isArray(step.emittedEvents) ? step.emittedEvents : [],
|
|
97
|
+
commands: commands(Array.isArray(step.commands) ? step.commands : []),
|
|
98
|
+
exitPaths: Array.isArray(step.exitPaths) ? step.exitPaths : [],
|
|
99
|
+
entryPaths: Array.isArray(step.entryPaths) ? step.entryPaths : [],
|
|
100
|
+
changed: step.changed === true
|
|
101
|
+
}))
|
|
102
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Cause from "effect/Cause"
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
|
-
import type { ChildMachine, Logic, Machine } from "../../Machine.js"
|
|
9
|
+
import type { ChildMachine, Inspection, Logic, Machine } from "../../Machine.js"
|
|
10
10
|
import * as Configuration from "./configuration.js"
|
|
11
11
|
import { InfiniteTransitionError, MachineSchemaDecodeError, StoppedError } from "./errors.js"
|
|
12
12
|
import * as InvocationEvent from "./invocationEvent.js"
|
|
@@ -23,6 +23,7 @@ export interface AnyConfig {
|
|
|
23
23
|
readonly onDone?: unknown
|
|
24
24
|
readonly onFailure?: unknown
|
|
25
25
|
readonly onSnapshot?: unknown
|
|
26
|
+
readonly activityKind?: Inspection.Activity["kind"]
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/** @internal */
|
|
@@ -47,7 +48,7 @@ const resolveOne = (
|
|
|
47
48
|
return {
|
|
48
49
|
id: String(raw.id),
|
|
49
50
|
src: () =>
|
|
50
|
-
oneShot(
|
|
51
|
+
oneShot(raw.effect(context) as Effect.Effect<any, any, any>) as unknown as Runtime.ProcessLogic<
|
|
51
52
|
any,
|
|
52
53
|
any,
|
|
53
54
|
any,
|
|
@@ -57,7 +58,8 @@ const resolveOne = (
|
|
|
57
58
|
>,
|
|
58
59
|
onDone: raw.onDone,
|
|
59
60
|
onFailure: raw.onFailure,
|
|
60
|
-
onSnapshot: raw.onSnapshot
|
|
61
|
+
onSnapshot: raw.onSnapshot,
|
|
62
|
+
activityKind: "Effect"
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
if ("after" in raw) {
|
|
@@ -72,7 +74,8 @@ const resolveOne = (
|
|
|
72
74
|
any,
|
|
73
75
|
any
|
|
74
76
|
>,
|
|
75
|
-
onDone: raw.onDone
|
|
77
|
+
onDone: raw.onDone,
|
|
78
|
+
activityKind: "Timer"
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
if ("logic" in raw) {
|
|
@@ -115,8 +118,18 @@ const runSequentialDiscard = <E, R>(
|
|
|
115
118
|
|
|
116
119
|
const sendLifecycle = (
|
|
117
120
|
scope: Runtime.ProcessScope<any>,
|
|
118
|
-
event: InvocationEvent.InvocationEvent
|
|
119
|
-
|
|
121
|
+
event: InvocationEvent.InvocationEvent,
|
|
122
|
+
activitySessionId?: string
|
|
123
|
+
): Effect.Effect<void> =>
|
|
124
|
+
(activitySessionId === undefined
|
|
125
|
+
? scope.self.send(event)
|
|
126
|
+
: scope.self.sendInspected === undefined
|
|
127
|
+
? scope.self.send(event)
|
|
128
|
+
: scope.self.sendInspected(
|
|
129
|
+
event,
|
|
130
|
+
scope.self.inspectionSubject,
|
|
131
|
+
{ _tag: "Activity", activitySessionId }
|
|
132
|
+
)).pipe(Effect.catchTag("StoppedError", () => Effect.void))
|
|
120
133
|
|
|
121
134
|
const isFrameworkFailure = (error: unknown): boolean =>
|
|
122
135
|
error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError || error instanceof StoppedError
|
|
@@ -131,7 +144,8 @@ const startResolved = (
|
|
|
131
144
|
src: () => Runtime.ProcessLogic<any, any, any, any, any, any>,
|
|
132
145
|
onDone: unknown,
|
|
133
146
|
onFailure: unknown,
|
|
134
|
-
onSnapshot: unknown
|
|
147
|
+
onSnapshot: unknown,
|
|
148
|
+
activityKind?: Inspection.Activity["kind"]
|
|
135
149
|
): Effect.Effect<void, any, any> =>
|
|
136
150
|
Effect.suspend(() => {
|
|
137
151
|
const key = makeKey(path, invokeId)
|
|
@@ -141,8 +155,9 @@ const startResolved = (
|
|
|
141
155
|
id: childId,
|
|
142
156
|
duplicateId: invokeId,
|
|
143
157
|
...(descriptor === undefined ? undefined : { descriptor }),
|
|
158
|
+
...(activityKind === undefined ? undefined : { activityKind }),
|
|
144
159
|
sendParent: (isCurrent, event) => isCurrent() ? scope.self.send(event) : Effect.void,
|
|
145
|
-
onOutcome: (isCurrent, outcome) => {
|
|
160
|
+
onOutcome: (isCurrent, outcome, activitySessionId) => {
|
|
146
161
|
if (outcome._tag === "Stopped" || !isCurrent()) return Effect.void
|
|
147
162
|
if (outcome._tag === "Done") {
|
|
148
163
|
if (onDone === undefined) {
|
|
@@ -152,14 +167,22 @@ const startResolved = (
|
|
|
152
167
|
)
|
|
153
168
|
))
|
|
154
169
|
}
|
|
155
|
-
return sendLifecycle(
|
|
170
|
+
return sendLifecycle(
|
|
171
|
+
scope,
|
|
172
|
+
InvocationEvent.done(path, invokeId, outcome.output),
|
|
173
|
+
activityKind === undefined ? undefined : activitySessionId
|
|
174
|
+
)
|
|
156
175
|
}
|
|
157
176
|
if (
|
|
158
177
|
outcome._tag === "Failure" &&
|
|
159
178
|
onFailure !== undefined &&
|
|
160
179
|
(descriptor === undefined || !isFrameworkFailure(outcome.error))
|
|
161
180
|
) {
|
|
162
|
-
return sendLifecycle(
|
|
181
|
+
return sendLifecycle(
|
|
182
|
+
scope,
|
|
183
|
+
InvocationEvent.failure(path, invokeId, outcome.error),
|
|
184
|
+
activityKind === undefined ? undefined : activitySessionId
|
|
185
|
+
)
|
|
163
186
|
}
|
|
164
187
|
return scope.failCause(outcome.cause)
|
|
165
188
|
},
|
|
@@ -189,7 +212,8 @@ const start = (
|
|
|
189
212
|
config.src,
|
|
190
213
|
config.onDone,
|
|
191
214
|
config.onFailure,
|
|
192
|
-
config.onSnapshot
|
|
215
|
+
config.onSnapshot,
|
|
216
|
+
config.activityKind
|
|
193
217
|
)
|
|
194
218
|
}
|
|
195
219
|
|
|
@@ -213,7 +237,8 @@ const startStaticChild = (
|
|
|
213
237
|
descriptor[ChildMachineLogicTypeId],
|
|
214
238
|
raw.onDone,
|
|
215
239
|
raw.onFailure,
|
|
216
|
-
raw.onSnapshot
|
|
240
|
+
raw.onSnapshot,
|
|
241
|
+
undefined
|
|
217
242
|
)
|
|
218
243
|
}
|
|
219
244
|
|