@typeonce/effect-machine 0.11.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 (83) hide show
  1. package/README.md +89 -13
  2. package/dist/Machine.d.ts +396 -127
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +21 -19
  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/cluster.js +2 -2
  11. package/dist/internal/machine/cluster.js.map +1 -1
  12. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  13. package/dist/internal/machine/commandRuntime.js +2 -5
  14. package/dist/internal/machine/commandRuntime.js.map +1 -1
  15. package/dist/internal/machine/configuration.d.ts +12 -7
  16. package/dist/internal/machine/configuration.d.ts.map +1 -1
  17. package/dist/internal/machine/configuration.js +9 -5
  18. package/dist/internal/machine/configuration.js.map +1 -1
  19. package/dist/internal/machine/executionPlan.d.ts +4 -3
  20. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  21. package/dist/internal/machine/executionPlan.js +68 -43
  22. package/dist/internal/machine/executionPlan.js.map +1 -1
  23. package/dist/internal/machine/initialization.d.ts +5 -0
  24. package/dist/internal/machine/initialization.d.ts.map +1 -0
  25. package/dist/internal/machine/initialization.js +61 -0
  26. package/dist/internal/machine/initialization.js.map +1 -0
  27. package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
  28. package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
  29. package/dist/internal/machine/inspectionRuntime.js +79 -0
  30. package/dist/internal/machine/inspectionRuntime.js.map +1 -0
  31. package/dist/internal/machine/invocation.d.ts +2 -1
  32. package/dist/internal/machine/invocation.d.ts.map +1 -1
  33. package/dist/internal/machine/invocation.js +18 -11
  34. package/dist/internal/machine/invocation.js.map +1 -1
  35. package/dist/internal/machine/machine.d.ts +1 -2
  36. package/dist/internal/machine/machine.d.ts.map +1 -1
  37. package/dist/internal/machine/machine.js +21 -6
  38. package/dist/internal/machine/machine.js.map +1 -1
  39. package/dist/internal/machine/planner.d.ts +17 -2
  40. package/dist/internal/machine/planner.d.ts.map +1 -1
  41. package/dist/internal/machine/planner.js +137 -64
  42. package/dist/internal/machine/planner.js.map +1 -1
  43. package/dist/internal/machine/process.d.ts.map +1 -1
  44. package/dist/internal/machine/process.js +7 -5
  45. package/dist/internal/machine/process.js.map +1 -1
  46. package/dist/internal/machine/runtime.d.ts +29 -5
  47. package/dist/internal/machine/runtime.d.ts.map +1 -1
  48. package/dist/internal/machine/runtime.js +440 -64
  49. package/dist/internal/machine/runtime.js.map +1 -1
  50. package/dist/internal/machine/symbols.d.ts +2 -0
  51. package/dist/internal/machine/symbols.d.ts.map +1 -1
  52. package/dist/internal/machine/symbols.js +2 -0
  53. package/dist/internal/machine/symbols.js.map +1 -1
  54. package/dist/internal/machine/topology.d.ts +13 -0
  55. package/dist/internal/machine/topology.d.ts.map +1 -1
  56. package/dist/internal/machine/topology.js +9 -0
  57. package/dist/internal/machine/topology.js.map +1 -1
  58. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  59. package/dist/internal/testing/machine/finiteModel.js +3 -3
  60. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  61. package/dist/unstable/reactivity/AtomMachine.d.ts +9 -0
  62. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  63. package/dist/unstable/reactivity/AtomMachine.js +9 -0
  64. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  65. package/docs/agent-guide.md +130 -20
  66. package/package.json +5 -5
  67. package/src/Machine.ts +548 -217
  68. package/src/internal/machine/atom.ts +26 -0
  69. package/src/internal/machine/cluster.ts +2 -2
  70. package/src/internal/machine/commandRuntime.ts +2 -7
  71. package/src/internal/machine/configuration.ts +38 -13
  72. package/src/internal/machine/executionPlan.ts +85 -52
  73. package/src/internal/machine/initialization.ts +73 -0
  74. package/src/internal/machine/inspectionRuntime.ts +102 -0
  75. package/src/internal/machine/invocation.ts +38 -13
  76. package/src/internal/machine/machine.ts +31 -6
  77. package/src/internal/machine/planner.ts +166 -66
  78. package/src/internal/machine/process.ts +7 -5
  79. package/src/internal/machine/runtime.ts +663 -91
  80. package/src/internal/machine/symbols.ts +3 -0
  81. package/src/internal/machine/topology.ts +27 -0
  82. package/src/internal/testing/machine/finiteModel.ts +8 -7
  83. package/src/unstable/reactivity/AtomMachine.ts +12 -0
@@ -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
 
@@ -235,7 +260,7 @@ export const startAll = (
235
260
  .filter((path) => configuration.active.has(path))
236
261
  .flatMap((path) => {
237
262
  const context = {
238
- ...(Configuration.getActorScope(configuration) ?? { self: scope.self, parent: scope.parent }),
263
+ ...(Configuration.getMachineReferences(configuration) ?? { self: scope.self, parent: scope.parent }),
239
264
  state: configuration.values.get(path),
240
265
  containingState: Configuration.getParentValue(machine, configuration, path),
241
266
  ancestors: Configuration.getParentValues(machine, configuration, path),
@@ -33,7 +33,7 @@ import type { EnsureExecutable } from "./readiness.js"
33
33
  import * as internalRuntime from "./runtime.js"
34
34
  import * as Serialization from "./serialization.js"
35
35
  import * as StateDefinition from "./stateDefinition.js"
36
- import { ChildMachineLogicTypeId } from "./symbols.js"
36
+ import { ChildMachineLogicTypeId, SnapshotBuilderStateTypeId } from "./symbols.js"
37
37
  import * as Topology from "./topology.js"
38
38
 
39
39
  export {
@@ -45,10 +45,9 @@ export {
45
45
  StartupError,
46
46
  StoppedError
47
47
  } from "./errors.js"
48
- export { ChildMachineLogicTypeId, InitialEventTypeId } from "./symbols.js"
48
+ export { ChildMachineLogicTypeId, InitialEventTypeId, SnapshotBuilderStateTypeId } from "./symbols.js"
49
49
 
50
50
  const TypeId = "~effect/Machine"
51
- export const SnapshotBuilderStateTypeId: unique symbol = Symbol("effect/Machine/SnapshotBuilderState")
52
51
  export const InvokeTypeId: unique symbol = Symbol.for("effect/Machine/Invoke")
53
52
  const ChildMachineTypeId = "~effect/Machine/ChildMachine"
54
53
  type IsAny<A> = 0 extends 1 & A ? true : false
@@ -300,6 +299,24 @@ const withFrom = <Method extends (value: unknown, ...args: ReadonlyArray<any>) =
300
299
  return method as Method & { readonly from: (...args: ReadonlyArray<any>) => unknown }
301
300
  }
302
301
 
302
+ const withInitial = <Builder extends object>(
303
+ builder: Builder,
304
+ path: string,
305
+ valued: boolean,
306
+ values?: Readonly<Record<string, unknown>>
307
+ ): Builder => {
308
+ const initial = withFrom(
309
+ (value: unknown) => Topology.makeInitialTarget(path, value, values),
310
+ "leaf",
311
+ valued
312
+ )
313
+ Object.defineProperty(builder, "initial", {
314
+ value: initial,
315
+ enumerable: false
316
+ })
317
+ return builder
318
+ }
319
+
303
320
  const makeSnapshotBuilder = (
304
321
  states: Machine.StateTree,
305
322
  options: SnapshotBuilderOptions
@@ -317,12 +334,15 @@ const makeSnapshotBuilder = (
317
334
  continue
318
335
  }
319
336
  const node = Topology.getStateNodeDefinition(path, definition)
320
- builder[key] = withFrom(
337
+ const method = withFrom(
321
338
  (value: unknown, selector?: (builder: unknown) => unknown) =>
322
339
  makeSnapshotForNode(definition, key, value, selector, options),
323
340
  node.states === undefined ? "leaf" : "nested",
324
341
  node.schema !== undefined
325
342
  )
343
+ builder[key] = node.states === undefined || options.mode !== "full" || options.prefix !== ""
344
+ ? method
345
+ : withInitial(method, path, node.schema !== undefined)
326
346
  }
327
347
  return builder
328
348
  }
@@ -348,7 +368,7 @@ const makeParallelSnapshotBuilder = (
348
368
  }
349
369
  const path = options.prefix === "" ? key : `${options.prefix}.${key}`
350
370
  const node = Topology.getStateNodeDefinition(path, definition)
351
- builder[key] = withFrom(
371
+ const method = withFrom(
352
372
  (value: unknown, selector?: (builder: unknown) => unknown) => {
353
373
  const nextRegions: Record<string, unknown> = {}
354
374
  for (const regionKey of Object.keys(regions)) {
@@ -360,6 +380,7 @@ const makeParallelSnapshotBuilder = (
360
380
  node.states === undefined ? "leaf" : "nested",
361
381
  node.schema !== undefined
362
382
  )
383
+ builder[key] = method
363
384
  }
364
385
  return builder
365
386
  }
@@ -538,7 +559,7 @@ const makeLocalTargetChildBuilder = (
538
559
  builder[child.key] = () => Topology.makeChoiceTarget(child.path, parent.path, values)
539
560
  continue
540
561
  }
541
- builder[child.key] = withFrom(
562
+ const method = withFrom(
542
563
  (value: unknown, selector?: (builder: unknown) => unknown) => {
543
564
  if (child.type === "atomic" || child.type === "final") {
544
565
  return makeTargetWithValues(child.path, value, values)
@@ -572,6 +593,9 @@ const makeLocalTargetChildBuilder = (
572
593
  child.type === "atomic" || child.type === "final" ? "leaf" : "nested",
573
594
  child.schema !== undefined
574
595
  )
596
+ builder[child.key] = child.type === "atomic" || child.type === "final"
597
+ ? method
598
+ : withInitial(method, child.path, child.schema !== undefined, values)
575
599
  }
576
600
  return builder
577
601
  }
@@ -677,6 +701,7 @@ const makeBranchTargetNodeBuilder = (
677
701
  "nested",
678
702
  node.schema !== undefined
679
703
  ) as unknown as Record<string, unknown>
704
+ withInitial(builder, node.path, node.schema !== undefined, values)
680
705
  if (node.type !== "parallel" || source === node.path || source.startsWith(`${node.path}.`)) {
681
706
  addBranchTargetChildren(builder, states, stateNodes, node.path, values, source)
682
707
  }