@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.
Files changed (80) hide show
  1. package/README.md +56 -4
  2. package/dist/Machine.d.ts +353 -84
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +12 -10
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +1 -0
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +23 -0
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  11. package/dist/internal/machine/commandRuntime.js +2 -5
  12. package/dist/internal/machine/commandRuntime.js.map +1 -1
  13. package/dist/internal/machine/configuration.d.ts +5 -0
  14. package/dist/internal/machine/configuration.d.ts.map +1 -1
  15. package/dist/internal/machine/configuration.js +6 -2
  16. package/dist/internal/machine/configuration.js.map +1 -1
  17. package/dist/internal/machine/executionPlan.d.ts +1 -0
  18. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  19. package/dist/internal/machine/executionPlan.js +43 -18
  20. package/dist/internal/machine/executionPlan.js.map +1 -1
  21. package/dist/internal/machine/initialization.d.ts +5 -0
  22. package/dist/internal/machine/initialization.d.ts.map +1 -0
  23. package/dist/internal/machine/initialization.js +61 -0
  24. package/dist/internal/machine/initialization.js.map +1 -0
  25. package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
  26. package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
  27. package/dist/internal/machine/inspectionRuntime.js +79 -0
  28. package/dist/internal/machine/inspectionRuntime.js.map +1 -0
  29. package/dist/internal/machine/invocation.d.ts +2 -1
  30. package/dist/internal/machine/invocation.d.ts.map +1 -1
  31. package/dist/internal/machine/invocation.js +17 -10
  32. package/dist/internal/machine/invocation.js.map +1 -1
  33. package/dist/internal/machine/machine.d.ts +1 -2
  34. package/dist/internal/machine/machine.d.ts.map +1 -1
  35. package/dist/internal/machine/machine.js +21 -6
  36. package/dist/internal/machine/machine.js.map +1 -1
  37. package/dist/internal/machine/planner.d.ts +15 -0
  38. package/dist/internal/machine/planner.d.ts.map +1 -1
  39. package/dist/internal/machine/planner.js +117 -44
  40. package/dist/internal/machine/planner.js.map +1 -1
  41. package/dist/internal/machine/process.d.ts.map +1 -1
  42. package/dist/internal/machine/process.js +4 -2
  43. package/dist/internal/machine/process.js.map +1 -1
  44. package/dist/internal/machine/runtime.d.ts +27 -3
  45. package/dist/internal/machine/runtime.d.ts.map +1 -1
  46. package/dist/internal/machine/runtime.js +440 -64
  47. package/dist/internal/machine/runtime.js.map +1 -1
  48. package/dist/internal/machine/symbols.d.ts +2 -0
  49. package/dist/internal/machine/symbols.d.ts.map +1 -1
  50. package/dist/internal/machine/symbols.js +2 -0
  51. package/dist/internal/machine/symbols.js.map +1 -1
  52. package/dist/internal/machine/topology.d.ts +13 -0
  53. package/dist/internal/machine/topology.d.ts.map +1 -1
  54. package/dist/internal/machine/topology.js +9 -0
  55. package/dist/internal/machine/topology.js.map +1 -1
  56. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  57. package/dist/internal/testing/machine/finiteModel.js +3 -3
  58. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  59. package/dist/unstable/reactivity/AtomMachine.d.ts +9 -0
  60. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  61. package/dist/unstable/reactivity/AtomMachine.js +9 -0
  62. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  63. package/docs/agent-guide.md +102 -9
  64. package/package.json +5 -5
  65. package/src/Machine.ts +505 -174
  66. package/src/internal/machine/atom.ts +26 -0
  67. package/src/internal/machine/commandRuntime.ts +2 -9
  68. package/src/internal/machine/configuration.ts +28 -3
  69. package/src/internal/machine/executionPlan.ts +50 -17
  70. package/src/internal/machine/initialization.ts +73 -0
  71. package/src/internal/machine/inspectionRuntime.ts +102 -0
  72. package/src/internal/machine/invocation.ts +37 -12
  73. package/src/internal/machine/machine.ts +31 -6
  74. package/src/internal/machine/planner.ts +136 -42
  75. package/src/internal/machine/process.ts +4 -2
  76. package/src/internal/machine/runtime.ts +661 -89
  77. package/src/internal/machine/symbols.ts +3 -0
  78. package/src/internal/machine/topology.ts +27 -0
  79. package/src/internal/testing/machine/finiteModel.ts +8 -7
  80. 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.send(event as Events))
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
- ? isMachineTarget(command.target)
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 { getNode, getStateNodeSchema, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
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
- "initial",
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 target = transitionResult.state
535
+ const unresolvedTarget = transitionResult.state
534
536
  validateDeclaredTransitionTarget(
535
537
  selection.sourcePath,
536
538
  selection.trigger,
537
539
  selection.transition.targets,
538
- target
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: target as any,
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: target as any,
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(resolveValue(raw.effect, context) as Effect.Effect<any, any, any>) as unknown as Runtime.ProcessLogic<
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
- ): Effect.Effect<void> => scope.self.send(event).pipe(Effect.catchTag("StoppedError", () => Effect.void))
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(scope, InvocationEvent.done(path, invokeId, outcome.output))
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(scope, InvocationEvent.failure(path, invokeId, outcome.error))
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