@typeonce/effect-machine 0.12.0 → 0.13.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 +56 -4
- package/dist/Machine.d.ts +353 -84
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +12 -10
- 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 +43 -18
- 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 +1 -2
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +21 -6
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +15 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +117 -44
- 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 +13 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +9 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +3 -3
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +9 -0
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +9 -0
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +102 -9
- package/package.json +5 -5
- package/src/Machine.ts +505 -174
- 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 +50 -17
- 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 +31 -6
- package/src/internal/machine/planner.ts +136 -42
- 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 +27 -0
- package/src/internal/testing/machine/finiteModel.ts +8 -7
- package/src/unstable/reactivity/AtomMachine.ts +12 -0
|
@@ -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"
|
|
@@ -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>
|
|
@@ -530,13 +532,22 @@ const collectIndexedEvaluatedTransition = (
|
|
|
530
532
|
selection: IndexedSelectedTransition
|
|
531
533
|
): IndexedEvaluatedTransition => {
|
|
532
534
|
const transitionResult = collectIndexedTransition(machine, selection.transition.transition, selection.context)
|
|
533
|
-
const
|
|
535
|
+
const unresolvedTarget = transitionResult.state
|
|
534
536
|
validateDeclaredTransitionTarget(
|
|
535
537
|
selection.sourcePath,
|
|
536
538
|
selection.trigger,
|
|
537
539
|
selection.transition.targets,
|
|
538
|
-
|
|
540
|
+
unresolvedTarget
|
|
539
541
|
)
|
|
542
|
+
const initialResolution = unresolvedTarget !== undefined && isInitialTarget(unresolvedTarget)
|
|
543
|
+
? resolveInitialTarget(
|
|
544
|
+
machine,
|
|
545
|
+
activeConfigurationFromIndexedState(descriptor, state),
|
|
546
|
+
unresolvedTarget,
|
|
547
|
+
(selection.context as any).event
|
|
548
|
+
)
|
|
549
|
+
: undefined
|
|
550
|
+
const target = initialResolution?.target ?? unresolvedTarget
|
|
540
551
|
if (target !== undefined && !isTarget(target) && !isSnapshot(target)) {
|
|
541
552
|
throw new Error("Machine expected indexed transition target to be a snapshot or target builder result")
|
|
542
553
|
}
|
|
@@ -547,16 +558,16 @@ const collectIndexedEvaluatedTransition = (
|
|
|
547
558
|
if (!changed) {
|
|
548
559
|
return {
|
|
549
560
|
selection,
|
|
550
|
-
unresolvedTarget:
|
|
561
|
+
unresolvedTarget: unresolvedTarget as any,
|
|
551
562
|
target: target as any,
|
|
552
563
|
next,
|
|
553
|
-
commands: transitionResult.commands,
|
|
554
|
-
raisedEvents: transitionResult.raisedEvents,
|
|
555
|
-
emittedEvents: transitionResult.emittedEvents,
|
|
564
|
+
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
565
|
+
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
566
|
+
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
556
567
|
changed: false,
|
|
557
568
|
exitPaths: [],
|
|
558
569
|
entryPaths: [],
|
|
559
|
-
choiceTransitions: []
|
|
570
|
+
choiceTransitions: initialResolution?.transitions ?? []
|
|
560
571
|
}
|
|
561
572
|
}
|
|
562
573
|
|
|
@@ -570,16 +581,16 @@ const collectIndexedEvaluatedTransition = (
|
|
|
570
581
|
: naturalBoundary
|
|
571
582
|
return {
|
|
572
583
|
selection,
|
|
573
|
-
unresolvedTarget:
|
|
584
|
+
unresolvedTarget: unresolvedTarget as any,
|
|
574
585
|
target: target as any,
|
|
575
586
|
next,
|
|
576
|
-
commands: transitionResult.commands,
|
|
577
|
-
raisedEvents: transitionResult.raisedEvents,
|
|
578
|
-
emittedEvents: transitionResult.emittedEvents,
|
|
587
|
+
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
588
|
+
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
589
|
+
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
579
590
|
changed: true,
|
|
580
591
|
exitPaths: getExitPaths(machine, activeConfigurationFromIndexedState(descriptor, state), boundary),
|
|
581
592
|
entryPaths: getEntryPaths(machine, activeConfigurationFromIndexedState(descriptor, next), boundary),
|
|
582
|
-
choiceTransitions: []
|
|
593
|
+
choiceTransitions: initialResolution?.transitions ?? []
|
|
583
594
|
}
|
|
584
595
|
}
|
|
585
596
|
|
|
@@ -588,13 +599,22 @@ const indexedMicrostep = (
|
|
|
588
599
|
descriptor: IndexedExecutionDescriptor,
|
|
589
600
|
state: OwnedIndexedState,
|
|
590
601
|
event: any,
|
|
591
|
-
selections: ReadonlyArray<IndexedSelectedTransition
|
|
602
|
+
selections: ReadonlyArray<IndexedSelectedTransition>,
|
|
603
|
+
retainTransitions: boolean
|
|
592
604
|
): ExecutionMicrostep<OwnedIndexedState> => {
|
|
605
|
+
const retain = (transition: IndexedEvaluatedTransition): Machine.RetainedTransition => ({
|
|
606
|
+
source: transition.selection.sourcePath,
|
|
607
|
+
trigger: transition.selection.trigger,
|
|
608
|
+
reenter: transition.selection.transition.reenter,
|
|
609
|
+
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
610
|
+
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
611
|
+
})
|
|
593
612
|
if (selections.length === 1) {
|
|
594
613
|
const transition = collectIndexedEvaluatedTransition(machine, descriptor, state, selections[0]!)
|
|
595
614
|
return {
|
|
596
615
|
next: transition.next,
|
|
597
616
|
event,
|
|
617
|
+
...(retainTransitions ? { transitions: [retain(transition)] } : undefined),
|
|
598
618
|
commands: transition.commands,
|
|
599
619
|
raisedEvents: transition.raisedEvents,
|
|
600
620
|
emittedEvents: transition.emittedEvents,
|
|
@@ -645,6 +665,7 @@ const indexedMicrostep = (
|
|
|
645
665
|
return {
|
|
646
666
|
next,
|
|
647
667
|
event,
|
|
668
|
+
...(retainTransitions ? { transitions: transitions.map(retain) } : undefined),
|
|
648
669
|
commands,
|
|
649
670
|
raisedEvents,
|
|
650
671
|
emittedEvents,
|
|
@@ -762,6 +783,17 @@ const planIndexedFlatState = (
|
|
|
762
783
|
const step: ExecutionMicrostep<OwnedIndexedState> = {
|
|
763
784
|
next,
|
|
764
785
|
event,
|
|
786
|
+
...(retainMicrosteps
|
|
787
|
+
? {
|
|
788
|
+
transitions: [{
|
|
789
|
+
source: sourcePath,
|
|
790
|
+
trigger: { type: "event", event: event._tag },
|
|
791
|
+
reenter: transition.reenter,
|
|
792
|
+
target: target === undefined ? undefined : getTargetNodePath(target as any),
|
|
793
|
+
resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any)
|
|
794
|
+
}]
|
|
795
|
+
}
|
|
796
|
+
: undefined),
|
|
765
797
|
commands: transitionResult.commands,
|
|
766
798
|
raisedEvents: transitionResult.raisedEvents,
|
|
767
799
|
emittedEvents: transitionResult.emittedEvents,
|
|
@@ -828,6 +860,7 @@ const planIndexedState = (
|
|
|
828
860
|
microsteps: planned.microsteps.map((step) => ({
|
|
829
861
|
next: ownedIndexedStateFromActive(descriptor, step.next),
|
|
830
862
|
event: step.event,
|
|
863
|
+
transitions: step.transitions,
|
|
831
864
|
commands: step.commands,
|
|
832
865
|
raisedEvents: step.raisedEvents,
|
|
833
866
|
emittedEvents: step.emittedEvents,
|
|
@@ -874,7 +907,7 @@ const planIndexedState = (
|
|
|
874
907
|
}
|
|
875
908
|
}
|
|
876
909
|
|
|
877
|
-
const first = indexedMicrostep(machine, descriptor, configuration, decoded, selections)
|
|
910
|
+
const first = indexedMicrostep(machine, descriptor, configuration, decoded, selections, retainMicrosteps)
|
|
878
911
|
let current = first.next
|
|
879
912
|
let currentEvent: any = decoded
|
|
880
913
|
const commands = [...first.commands]
|
|
@@ -932,7 +965,7 @@ const planIndexedState = (
|
|
|
932
965
|
currentEvent = raised
|
|
933
966
|
const raisedSelections = selectIndexedEventTransitions(machine, descriptor, current, raised, machineReferences)
|
|
934
967
|
if (raisedSelections.length === 0) continue
|
|
935
|
-
const step = indexedMicrostep(machine, descriptor, current, raised, raisedSelections)
|
|
968
|
+
const step = indexedMicrostep(machine, descriptor, current, raised, raisedSelections, retainMicrosteps)
|
|
936
969
|
current = step.next
|
|
937
970
|
commands.push(...step.commands)
|
|
938
971
|
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
|
|