@typeonce/effect-machine 0.8.0 → 0.10.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 (108) hide show
  1. package/README.md +180 -40
  2. package/dist/Machine.d.ts +659 -481
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +86 -222
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +5 -12
  7. package/dist/internal/machine/activities.d.ts.map +1 -1
  8. package/dist/internal/machine/activities.js +47 -17
  9. package/dist/internal/machine/activities.js.map +1 -1
  10. package/dist/internal/machine/atom.d.ts +6 -2
  11. package/dist/internal/machine/atom.d.ts.map +1 -1
  12. package/dist/internal/machine/atom.js +5 -0
  13. package/dist/internal/machine/atom.js.map +1 -1
  14. package/dist/internal/machine/cluster.d.ts.map +1 -1
  15. package/dist/internal/machine/cluster.js +2 -1
  16. package/dist/internal/machine/cluster.js.map +1 -1
  17. package/dist/internal/machine/command.js +2 -2
  18. package/dist/internal/machine/command.js.map +1 -1
  19. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  20. package/dist/internal/machine/commandRuntime.js +6 -3
  21. package/dist/internal/machine/commandRuntime.js.map +1 -1
  22. package/dist/internal/machine/configuration.d.ts +8 -1
  23. package/dist/internal/machine/configuration.d.ts.map +1 -1
  24. package/dist/internal/machine/configuration.js +9 -4
  25. package/dist/internal/machine/configuration.js.map +1 -1
  26. package/dist/internal/machine/errors.d.ts +1 -1
  27. package/dist/internal/machine/errors.d.ts.map +1 -1
  28. package/dist/internal/machine/executionPlan.d.ts +3 -3
  29. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  30. package/dist/internal/machine/executionPlan.js +74 -24
  31. package/dist/internal/machine/executionPlan.js.map +1 -1
  32. package/dist/internal/machine/invocation.d.ts +10 -2
  33. package/dist/internal/machine/invocation.d.ts.map +1 -1
  34. package/dist/internal/machine/invocation.js +94 -34
  35. package/dist/internal/machine/invocation.js.map +1 -1
  36. package/dist/internal/machine/invocationEvent.d.ts +39 -0
  37. package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
  38. package/dist/internal/machine/invocationEvent.js +43 -0
  39. package/dist/internal/machine/invocationEvent.js.map +1 -0
  40. package/dist/internal/machine/machine.d.ts +16 -65
  41. package/dist/internal/machine/machine.d.ts.map +1 -1
  42. package/dist/internal/machine/machine.js +18 -73
  43. package/dist/internal/machine/machine.js.map +1 -1
  44. package/dist/internal/machine/planner.d.ts +4 -2
  45. package/dist/internal/machine/planner.d.ts.map +1 -1
  46. package/dist/internal/machine/planner.js +104 -24
  47. package/dist/internal/machine/planner.js.map +1 -1
  48. package/dist/internal/machine/process.d.ts.map +1 -1
  49. package/dist/internal/machine/process.js +8 -8
  50. package/dist/internal/machine/process.js.map +1 -1
  51. package/dist/internal/machine/protocol.d.ts +8 -5
  52. package/dist/internal/machine/protocol.d.ts.map +1 -1
  53. package/dist/internal/machine/protocol.js +207 -15
  54. package/dist/internal/machine/protocol.js.map +1 -1
  55. package/dist/internal/machine/runtime.d.ts +8 -2
  56. package/dist/internal/machine/runtime.d.ts.map +1 -1
  57. package/dist/internal/machine/runtime.js +86 -8
  58. package/dist/internal/machine/runtime.js.map +1 -1
  59. package/dist/internal/machine/symbols.d.ts +2 -0
  60. package/dist/internal/machine/symbols.d.ts.map +1 -1
  61. package/dist/internal/machine/symbols.js +2 -0
  62. package/dist/internal/machine/symbols.js.map +1 -1
  63. package/dist/internal/machine/topology.d.ts +7 -0
  64. package/dist/internal/machine/topology.d.ts.map +1 -1
  65. package/dist/internal/machine/topology.js +29 -0
  66. package/dist/internal/machine/topology.js.map +1 -1
  67. package/dist/internal/testing/machine/finiteModel.js +1 -1
  68. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  69. package/dist/internal/testing/machine/runtime.d.ts.map +1 -1
  70. package/dist/internal/testing/machine/runtime.js +2 -1
  71. package/dist/internal/testing/machine/runtime.js.map +1 -1
  72. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  73. package/dist/internal/testing/machine/verification.js +5 -3
  74. package/dist/internal/testing/machine/verification.js.map +1 -1
  75. package/dist/testing/MachineTest.d.ts +7 -7
  76. package/dist/testing/MachineTest.js +7 -7
  77. package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
  78. package/dist/unstable/cluster/ClusterMachine.js +1 -1
  79. package/dist/unstable/reactivity/AtomMachine.d.ts +25 -8
  80. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  81. package/dist/unstable/reactivity/AtomMachine.js +18 -3
  82. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  83. package/docs/agent-guide.md +242 -122
  84. package/package.json +2 -2
  85. package/src/Machine.ts +1453 -812
  86. package/src/internal/machine/activities.ts +48 -33
  87. package/src/internal/machine/atom.ts +38 -13
  88. package/src/internal/machine/cluster.ts +2 -1
  89. package/src/internal/machine/command.ts +2 -2
  90. package/src/internal/machine/commandRuntime.ts +9 -4
  91. package/src/internal/machine/configuration.ts +23 -5
  92. package/src/internal/machine/errors.ts +1 -1
  93. package/src/internal/machine/executionPlan.ts +102 -28
  94. package/src/internal/machine/invocation.ts +182 -50
  95. package/src/internal/machine/invocationEvent.ts +72 -0
  96. package/src/internal/machine/machine.ts +80 -387
  97. package/src/internal/machine/planner.ts +127 -32
  98. package/src/internal/machine/process.ts +18 -8
  99. package/src/internal/machine/protocol.ts +313 -25
  100. package/src/internal/machine/runtime.ts +135 -9
  101. package/src/internal/machine/symbols.ts +3 -0
  102. package/src/internal/machine/topology.ts +38 -0
  103. package/src/internal/testing/machine/finiteModel.ts +1 -1
  104. package/src/internal/testing/machine/runtime.ts +2 -1
  105. package/src/internal/testing/machine/verification.ts +5 -3
  106. package/src/testing/MachineTest.ts +7 -7
  107. package/src/unstable/cluster/ClusterMachine.ts +1 -1
  108. package/src/unstable/reactivity/AtomMachine.ts +45 -13
@@ -18,6 +18,7 @@ import * as Scope from "effect/Scope"
18
18
  import * as Stream from "effect/Stream"
19
19
  import * as SynchronizedRef from "effect/SynchronizedRef"
20
20
  import type * as Take from "effect/Take"
21
+ import type { ActorRef } from "../../Machine.js"
21
22
  import { ChildAlreadyExistsError, StoppedError } from "./errors.js"
22
23
 
23
24
  type ChildDescriptor = {
@@ -284,12 +285,13 @@ export type RuntimeOutcome<State, Error = never, Output = never> =
284
285
  readonly snapshot: Extract<RuntimeSnapshot<State, Error, Output>, { readonly status: "stopped" }>
285
286
  }
286
287
 
287
- export interface MachineRef<out State, in Event, out Error = never, out Output = never> {
288
+ export interface MachineRef<out State, in Event, out Error = never, out Output = never, out Emitted = never> {
288
289
  readonly id: string
289
290
  readonly sessionId: string
290
291
  readonly state: Effect.Effect<State>
291
292
  readonly snapshot: Effect.Effect<RuntimeSnapshot<State, Error, Output>>
292
293
  readonly changes: Stream.Stream<RuntimeSnapshot<State, Error, Output>>
294
+ readonly emissions: Stream.Stream<Emitted>
293
295
  readonly join: Effect.Effect<Output, Error | StoppedError>
294
296
  readonly stop: Effect.Effect<void>
295
297
  readonly send: (event: Event) => Effect.Effect<void, StoppedError>
@@ -307,12 +309,19 @@ interface ProcessAddress<in Event> {
307
309
  readonly send: (event: Event) => Effect.Effect<void, StoppedError>
308
310
  }
309
311
 
312
+ const isProcessAddress = (value: unknown): value is ActorRef<unknown> =>
313
+ typeof value === "object" && value !== null && "send" in value && typeof value.send === "function"
314
+
310
315
  export interface ProcessScope<Event> {
311
316
  readonly self: ProcessAddress<Event>
312
317
  readonly parent: ProcessAddress<unknown> | undefined
313
318
  readonly spawn: ProcessSpawn
314
319
  readonly sendParent: (event: unknown) => Effect.Effect<void, StoppedError>
315
- readonly sendTo: (child: ChildSelector, event: unknown) => Effect.Effect<void, StoppedError>
320
+ readonly emit: (event: unknown) => Effect.Effect<void>
321
+ readonly sendTo: {
322
+ <TargetEvent>(target: ActorRef<TargetEvent>, event: TargetEvent): Effect.Effect<void, StoppedError>
323
+ (child: ChildSelector, event: unknown): Effect.Effect<void, StoppedError>
324
+ }
316
325
  readonly stopChild: (child: ChildSelector) => Effect.Effect<void>
317
326
  /** @internal */
318
327
  readonly failCause: (cause: Cause.Cause<unknown>) => Effect.Effect<void>
@@ -708,6 +717,10 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
708
717
  ownerPath: options.path,
709
718
  ownerActive: true
710
719
  })
720
+ const parent: ProcessAddress<unknown> = {
721
+ ...this.self,
722
+ send: (event) => options.sendParent(isCurrent, event)
723
+ }
711
724
  const startOptions: StartInternalOptions = {
712
725
  detached: true,
713
726
  id: options.id,
@@ -722,7 +735,7 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
722
735
  },
723
736
  onStopSync: () => unregisterChild(this.registry, options.id, token),
724
737
  skipStoppedOutcome: true,
725
- parent: this.self,
738
+ parent,
726
739
  runtime: this.runtime
727
740
  }
728
741
  const execution = logic.execution
@@ -785,6 +798,9 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
785
798
 
786
799
  const noChildChanges = Stream.succeed(Option.none()).pipe(Stream.concat(Stream.never))
787
800
  const noParentSend = (_event: unknown): Effect.Effect<void, StoppedError> => Effect.void
801
+ const EmissionsClosed: unique symbol = Symbol("effect/Machine/EmissionsClosed")
802
+
803
+ type LazyEmissions = PubSub.PubSub<unknown> | typeof EmissionsClosed | undefined
788
804
 
789
805
  const childlessRuntime: ChildRuntime = {
790
806
  close: () => Effect.void,
@@ -1091,6 +1107,38 @@ const startGenericInternal: <
1091
1107
  const sessionId = yield* runtime.nextSessionId
1092
1108
  const id = requestedId ?? sessionId
1093
1109
  const queue = yield* Queue.unbounded<ProcessMessage<Event>>()
1110
+ let emissions: LazyEmissions
1111
+ const getOrCreateEmissions: Effect.Effect<PubSub.PubSub<unknown> | undefined> = Effect.suspend(() => {
1112
+ const observed = emissions
1113
+ if (observed === EmissionsClosed) return Effect.succeed(undefined)
1114
+ if (observed !== undefined) return Effect.succeed(observed)
1115
+ return PubSub.unbounded<unknown>().pipe(
1116
+ Effect.flatMap((candidate) =>
1117
+ Effect.sync(() => {
1118
+ const latest = emissions
1119
+ if (latest === EmissionsClosed) return [undefined, true] as const
1120
+ if (latest !== undefined) return [latest, true] as const
1121
+ emissions = candidate
1122
+ return [candidate, false] as const
1123
+ }).pipe(
1124
+ Effect.flatMap(([selected, discard]) =>
1125
+ discard ? PubSub.shutdown(candidate).pipe(Effect.as(selected)) : Effect.succeed(selected)
1126
+ )
1127
+ )
1128
+ )
1129
+ )
1130
+ })
1131
+ const closeEmissions = (): Effect.Effect<void> => {
1132
+ const observed = emissions
1133
+ emissions = EmissionsClosed
1134
+ return observed === undefined || observed === EmissionsClosed ? Effect.void : PubSub.shutdown(observed)
1135
+ }
1136
+ const emit = (event: unknown): Effect.Effect<void> =>
1137
+ Effect.suspend(() =>
1138
+ emissions === undefined || emissions === EmissionsClosed
1139
+ ? Effect.void
1140
+ : PubSub.publish(emissions, event).pipe(Effect.asVoid)
1141
+ )
1094
1142
  const termination = yield* Deferred.make<ProcessTermination>()
1095
1143
  const done = yield* Deferred.make<Output, Error | StoppedError>()
1096
1144
  const awaitCompletion = Deferred.await(done).pipe(Effect.exit, Effect.asVoid)
@@ -1161,7 +1209,7 @@ const startGenericInternal: <
1161
1209
  }
1162
1210
  const cleanupStartupFailure = <A, E>(exit: Exit.Exit<A, E>): Effect.Effect<void> =>
1163
1211
  Exit.isFailure(exit)
1164
- ? closeChildren(exit)
1212
+ ? closeChildren(exit).pipe(Effect.andThen(closeEmissions()))
1165
1213
  : Effect.void
1166
1214
  const cleanup = onStopSync === undefined ? onStop ?? Effect.void : Effect.sync(onStopSync)
1167
1215
  const sendParent = overrideSendParent ?? (parent === undefined ? noParentSend : parent.send)
@@ -1171,7 +1219,11 @@ const startGenericInternal: <
1171
1219
  parent,
1172
1220
  spawn,
1173
1221
  sendParent,
1174
- sendTo,
1222
+ emit,
1223
+ sendTo: ((target: unknown, event: unknown) =>
1224
+ isProcessAddress(target) ? target.send(event) : sendTo(target as ChildSelector, event)) as ProcessScope<
1225
+ Event
1226
+ >["sendTo"],
1175
1227
  stopChild,
1176
1228
  failCause: (cause) =>
1177
1229
  Deferred.succeed(termination, {
@@ -1209,7 +1261,9 @@ const startGenericInternal: <
1209
1261
  const runtimeSnapshot = snapshot.snapshot
1210
1262
  return runtimeSnapshot.status !== "active"
1211
1263
  ? publish
1212
- : publish.pipe(Effect.tap(() => notifyActiveSnapshot(onSnapshot, runtimeSnapshot)))
1264
+ : publish.pipe(Effect.tap(() =>
1265
+ notifyActiveSnapshot(onSnapshot, runtimeSnapshot)
1266
+ ))
1213
1267
  }
1214
1268
 
1215
1269
  const completeChanges = (
@@ -1348,6 +1402,7 @@ const startGenericInternal: <
1348
1402
  Effect.andThen(Queue.shutdown(queue)),
1349
1403
  Effect.andThen(closeChildren(exit)),
1350
1404
  Effect.andThen(setAndPublishSnapshot(snapshot)),
1405
+ Effect.andThen(closeEmissions()),
1351
1406
  Effect.andThen(Effect.sync(() => {
1352
1407
  if (Exit.isFailure(exit)) {
1353
1408
  failAcknowledgedMessage(inFlightMessage, exit.cause)
@@ -1504,6 +1559,11 @@ const startGenericInternal: <
1504
1559
  state: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot.state)),
1505
1560
  snapshot: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot)),
1506
1561
  changes: changesStream,
1562
+ emissions: Stream.unwrap(
1563
+ getOrCreateEmissions.pipe(
1564
+ Effect.map((emissions) => emissions === undefined ? Stream.empty : Stream.fromPubSub(emissions))
1565
+ )
1566
+ ) as Stream.Stream<never>,
1507
1567
  join: Deferred.await(done),
1508
1568
  stop,
1509
1569
  send: self.send,
@@ -1657,6 +1717,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1657
1717
  private interruptRequested = false
1658
1718
  private offerRevision = 0
1659
1719
  private inFlightMessage: ProcessMessage<unknown> | undefined
1720
+ private emissionsPubSub: LazyEmissions
1660
1721
 
1661
1722
  constructor(
1662
1723
  private readonly logic: ProcessLogic<any, any, any, any, any, any>,
@@ -1690,7 +1751,11 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1690
1751
  parent,
1691
1752
  spawn: this.childRuntime.spawn,
1692
1753
  sendParent,
1693
- sendTo: this.childRuntime.sendTo,
1754
+ emit: (event) => this.emitEvent(event),
1755
+ sendTo: ((target: unknown, event: unknown) =>
1756
+ isProcessAddress(target)
1757
+ ? target.send(event)
1758
+ : this.childRuntime.sendTo(target as ChildSelector, event)) as ProcessScope<unknown>["sendTo"],
1694
1759
  stopChild: this.childRuntime.stop,
1695
1760
  failCause: (cause: Cause.Cause<unknown>) => this.failCause(cause)
1696
1761
  }
@@ -1743,7 +1808,11 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1743
1808
  parent,
1744
1809
  spawn: self.childRuntime.spawn,
1745
1810
  sendParent,
1746
- sendTo: self.childRuntime.sendTo,
1811
+ emit: (event) => self.emitEvent(event),
1812
+ sendTo: ((target: unknown, event: unknown) =>
1813
+ isProcessAddress(target)
1814
+ ? target.send(event)
1815
+ : self.childRuntime.sendTo(target as ChildSelector, event)) as ProcessScope<unknown>["sendTo"],
1747
1816
  stopChild: self.childRuntime.stop,
1748
1817
  failCause: (cause: Cause.Cause<unknown>) => self.failCause(cause)
1749
1818
  }
@@ -1838,6 +1907,10 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1838
1907
  return this.changesStream()
1839
1908
  }
1840
1909
 
1910
+ get emissions(): Stream.Stream<never> {
1911
+ return this.emissionsStream() as Stream.Stream<never>
1912
+ }
1913
+
1841
1914
  get join(): Effect.Effect<unknown, unknown> {
1842
1915
  return Effect.suspend(() => {
1843
1916
  if (this.lifecycle === "Completed") {
@@ -2145,6 +2218,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2145
2218
  }).pipe(
2146
2219
  Effect.andThen(this.childRuntime.close(exit)),
2147
2220
  Effect.andThen(this.setAndPublishSnapshot(snapshot)),
2221
+ Effect.andThen(this.closeEmissions()),
2148
2222
  Effect.andThen(Effect.sync(() => {
2149
2223
  if (requested._tag === "Failure") {
2150
2224
  failAcknowledgedMessage(this.inFlightMessage, requested.cause)
@@ -2193,6 +2267,55 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2193
2267
  : PubSub.publish(snapshot.changes, Exit.succeed<void>(undefined)).pipe(Effect.asVoid)
2194
2268
  }
2195
2269
 
2270
+ private emitEvent(event: unknown): Effect.Effect<void> {
2271
+ return Effect.suspend(() =>
2272
+ this.emissionsPubSub === undefined || this.emissionsPubSub === EmissionsClosed
2273
+ ? Effect.void
2274
+ : PubSub.publish(this.emissionsPubSub, event).pipe(Effect.asVoid)
2275
+ )
2276
+ }
2277
+
2278
+ shutdownEmissions(): Effect.Effect<void> {
2279
+ return this.closeEmissions()
2280
+ }
2281
+
2282
+ private closeEmissions(): Effect.Effect<void> {
2283
+ const observed = this.emissionsPubSub
2284
+ this.emissionsPubSub = EmissionsClosed
2285
+ return observed === undefined || observed === EmissionsClosed ? Effect.void : PubSub.shutdown(observed)
2286
+ }
2287
+
2288
+ private getOrCreateEmissions(): Effect.Effect<PubSub.PubSub<unknown> | undefined> {
2289
+ return Effect.suspend(() => {
2290
+ const observed = this.emissionsPubSub
2291
+ if (observed === EmissionsClosed) return Effect.succeed(undefined)
2292
+ if (observed !== undefined) return Effect.succeed(observed)
2293
+ return PubSub.unbounded<unknown>().pipe(
2294
+ Effect.flatMap((candidate) =>
2295
+ Effect.sync(() => {
2296
+ const latest = this.emissionsPubSub
2297
+ if (latest === EmissionsClosed) return [undefined, true] as const
2298
+ if (latest !== undefined) return [latest, true] as const
2299
+ this.emissionsPubSub = candidate
2300
+ return [candidate, false] as const
2301
+ }).pipe(
2302
+ Effect.flatMap(([selected, discard]) =>
2303
+ discard ? PubSub.shutdown(candidate).pipe(Effect.as(selected)) : Effect.succeed(selected)
2304
+ )
2305
+ )
2306
+ )
2307
+ )
2308
+ })
2309
+ }
2310
+
2311
+ private emissionsStream(): Stream.Stream<unknown> {
2312
+ return Stream.unwrap(
2313
+ this.getOrCreateEmissions().pipe(
2314
+ Effect.map((emissions) => emissions === undefined ? Stream.empty : Stream.fromPubSub(emissions))
2315
+ )
2316
+ )
2317
+ }
2318
+
2196
2319
  private setAndPublishSnapshot(snapshot: RuntimeSnapshot<unknown, unknown, unknown>): Effect.Effect<void> {
2197
2320
  return Effect.suspend(() => {
2198
2321
  this.flushPendingChanges()
@@ -2407,10 +2530,13 @@ const startCompactCompiledInternal: typeof startGenericInternal = Effect.fnUntra
2407
2530
  // A compiled initializer is synchronous by construction. Only startup
2408
2531
  // callbacks that themselves return Effects need the generic initialization
2409
2532
  // program; the compiled drain is still provided the complete service context.
2410
- return yield* execution.initialSync !== undefined &&
2533
+ const initialize = execution.initialSync !== undefined &&
2411
2534
  options.onReady === undefined && options.onSnapshot === undefined
2412
2535
  ? process.initializeCompiledSync()
2413
2536
  : process.initialize()
2537
+ return yield* initialize.pipe(
2538
+ Effect.onExit((exit) => Exit.isFailure(exit) ? process.shutdownEmissions() : Effect.void)
2539
+ )
2414
2540
  }) as typeof startGenericInternal
2415
2541
 
2416
2542
  const startLogicInternal: typeof startGenericInternal = ((
@@ -1,2 +1,5 @@
1
1
  /** @internal */
2
2
  export const InitialEventTypeId: unique symbol = Symbol("effect/Machine/InitialEvent")
3
+
4
+ /** @internal Returns process logic for a child descriptor and optional input. */
5
+ export const ChildMachineLogicTypeId: unique symbol = Symbol("effect/Machine/ChildMachineLogic")
@@ -21,6 +21,8 @@ export const HistoryTargetTypeId: unique symbol = Symbol("effect/Machine/History
21
21
 
22
22
  export const ChoiceTargetTypeId: unique symbol = Symbol("effect/Machine/ChoiceTarget")
23
23
 
24
+ export const NoTargetTypeId: unique symbol = Symbol("effect/Machine/NoTarget")
25
+
24
26
  interface StateInput {
25
27
  readonly [StateInputTypeId]: typeof StateInputTypeId
26
28
  readonly input: unknown
@@ -42,6 +44,19 @@ export interface ChoiceTarget {
42
44
  readonly values?: Readonly<Record<string, unknown>>
43
45
  }
44
46
 
47
+ /** Internal marker returned by an explicitly targetless transition. */
48
+ export interface NoTarget {
49
+ readonly [NoTargetTypeId]: typeof NoTargetTypeId
50
+ }
51
+
52
+ const noTarget = Object.freeze({
53
+ [NoTargetTypeId]: NoTargetTypeId
54
+ }) as NoTarget
55
+
56
+ export const makeNoTarget = (): Machine.NoTarget => noTarget
57
+
58
+ export const isNoTarget = (u: unknown): u is Machine.NoTarget => hasProperty(u, NoTargetTypeId)
59
+
45
60
  export const makeHistoryTarget = (path: string, parent: string): HistoryTarget => ({
46
61
  [HistoryTargetTypeId]: HistoryTargetTypeId,
47
62
  path,
@@ -392,6 +407,29 @@ export const transitionDefinitions = (
392
407
  targets: transitionTargets(config.onDone)
393
408
  })
394
409
  }
410
+ const invokes = config.invoke === undefined
411
+ ? []
412
+ : Array.isArray(config.invoke)
413
+ ? config.invoke
414
+ : [config.invoke]
415
+ for (const invoke of invokes) {
416
+ const id = "child" in invoke ? String(invoke.child.id) : String(invoke.id)
417
+ for (const outcome of ["done", "failure", "snapshot"] as const) {
418
+ const handler = outcome === "done"
419
+ ? invoke.onDone
420
+ : outcome === "failure"
421
+ ? invoke.onFailure
422
+ : invoke.onSnapshot
423
+ if (handler !== undefined) {
424
+ definitions.push({
425
+ source: node.path,
426
+ trigger: { type: "invoke", id, outcome },
427
+ reenter: typeof handler === "object" && handler !== null && handler.reenter === true,
428
+ targets: transitionTargets(handler)
429
+ })
430
+ }
431
+ }
432
+ }
395
433
  }
396
434
  return definitions
397
435
  }
@@ -1487,7 +1487,7 @@ export const compileModel = (model: FiniteModel): Machine.Machine.Any => {
1487
1487
  const initial = byPath.get(model.initial)!
1488
1488
  const machine = Machine.make({
1489
1489
  states: defined.states as any,
1490
- events: eventSchemas as any,
1490
+ events: Machine.events(...eventSchemas) as any,
1491
1491
  initial: () => selectSnapshot(defined.initial as any, initial.path, byPath, [initial.path], 0) as any
1492
1492
  })
1493
1493
  const transitions = new Map(model.transitions.map((transition) => [
@@ -15,6 +15,7 @@ import * as Stream from "effect/Stream"
15
15
  import { FastCheck, TestClock } from "effect/testing"
16
16
  import * as Machine from "../../../Machine.js"
17
17
  import type { CausalRuntimeEvidence, Probe, ProbeStep, RuntimeInvariant } from "../../../testing/MachineTest.js"
18
+ import * as Protocol from "../../machine/protocol.js"
18
19
  import { type SchemaArbitraryReport, toArbitraryWithReport } from "./arbitrary.js"
19
20
  import { assertRuntimeInvariants, type RuntimeInvariantError } from "./runtimeInvariant.js"
20
21
 
@@ -1523,7 +1524,7 @@ export const runtimeCommands = <M extends AnyMachine>(
1523
1524
 
1524
1525
  const reports: Array<SchemaArbitraryReport> = []
1525
1526
  const eventArbitraries = options.eventArbitrary === undefined
1526
- ? machine.events.map((schema) => {
1527
+ ? Protocol.inputEventSchemas(machine).map((schema) => {
1527
1528
  const derived = toArbitraryWithReport(schema)
1528
1529
  reports.push(derived.report)
1529
1530
  return derived.value as FastCheck.Arbitrary<Machine.Machine.InputEvent<M>>
@@ -35,6 +35,7 @@ import type {
35
35
  VerificationViolation,
36
36
  VerifyOptions
37
37
  } from "../../../testing/MachineTest.js"
38
+ import * as Protocol from "../../machine/protocol.js"
38
39
  import { toArbitraryWithReport } from "./arbitrary.js"
39
40
  import type { FiniteModel } from "./finiteModel.js"
40
41
  import * as ReferenceModel from "./referenceModel.js"
@@ -167,6 +168,7 @@ export const scenarios = <M extends AnyMachine>(
167
168
  machine: M,
168
169
  options: ScenarioOptions<M> = {} as ScenarioOptions<M>
169
170
  ): Scenarios<M> => {
171
+ const eventSchemas = Protocol.inputEventSchemas(machine)
170
172
  const minEvents = options.minEvents ?? 0
171
173
  const maxEvents = options.maxEvents ?? 50
172
174
  if (options.eventsArbitrary === undefined) {
@@ -175,7 +177,7 @@ export const scenarios = <M extends AnyMachine>(
175
177
  if (minEvents > maxEvents) {
176
178
  throw new Error("MachineTest.scenarios expected minEvents to be less than or equal to maxEvents")
177
179
  }
178
- if (machine.events.length === 0 && minEvents > 0) {
180
+ if (eventSchemas.length === 0 && minEvents > 0) {
179
181
  throw new Error(
180
182
  "MachineTest.scenarios cannot generate a non-empty event sequence for a machine without public events"
181
183
  )
@@ -184,7 +186,7 @@ export const scenarios = <M extends AnyMachine>(
184
186
 
185
187
  const diagnostics: Array<SchemaArbitraryDiagnostic> = []
186
188
  const eventArbitraries = options.eventsArbitrary === undefined
187
- ? machine.events.map((schema, index) => {
189
+ ? eventSchemas.map((schema, index) => {
188
190
  const derived = toArbitraryWithReport(schema)
189
191
  diagnostics.push({
190
192
  boundary: "event",
@@ -524,7 +526,7 @@ const publicEventTags = <M extends AnyMachine>(machine: M): {
524
526
  } => {
525
527
  const tags: Array<Machine.Machine.TagOf<Machine.Machine.InputEvents<M>[number]>> = []
526
528
  const diagnostics: Array<{ readonly schemaIndex: number; readonly message: string }> = []
527
- machine.events.forEach((schema, schemaIndex) => {
529
+ Protocol.inputEventSchemas(machine).forEach((schema, schemaIndex) => {
528
530
  const values = finiteTagValues(SchemaAST.toType(schema.ast))
529
531
  if (values === undefined) {
530
532
  diagnostics.push({
@@ -230,7 +230,7 @@ export interface Scenarios<M extends AnyMachine> {
230
230
  * const States = Machine.defineStates({ Idle })
231
231
  * const machine = Machine.make({
232
232
  * states: States.states,
233
- * events: [Reset],
233
+ * events: Machine.events(Reset),
234
234
  * initial: () => States.initial.Idle.from()
235
235
  * }).handle({ Idle: { on: { Reset: () => States.initial.Idle.from() } } })
236
236
  *
@@ -479,7 +479,7 @@ export { ProbeUnavailableError } from "../internal/testing/machine/verification.
479
479
  * const States = Machine.defineStates({ Idle })
480
480
  * const machine = Machine.make({
481
481
  * states: States.states,
482
- * events: [],
482
+ * events: Machine.events(),
483
483
  * initial: () => States.initial.Idle.from()
484
484
  * }).handle({ Idle: {} })
485
485
  *
@@ -1089,7 +1089,7 @@ export const Invariant: {
1089
1089
  * const States = Machine.defineStates({ Count })
1090
1090
  * const machine = Machine.make({
1091
1091
  * states: States.states,
1092
- * events: [],
1092
+ * events: Machine.events(),
1093
1093
  * initial: () => States.initial.Count(new Count({ value: 0 }))
1094
1094
  * }).handle({ Count: {} })
1095
1095
  *
@@ -1407,7 +1407,7 @@ export type ExploreOptions<M extends AnyMachine, Key extends ExplorationKey = Ex
1407
1407
  * const States = Machine.defineStates({ Count })
1408
1408
  * const machine = Machine.make({
1409
1409
  * states: States.states,
1410
- * events: [Increment],
1410
+ * events: Machine.events(Increment),
1411
1411
  * initial: () => States.initial.Count(new Count({ value: 0 }))
1412
1412
  * }).handle({
1413
1413
  * Count: { on: { Increment: ({ state }) =>
@@ -1591,7 +1591,7 @@ export type RunServices<M extends AnyMachine> = IsAny<
1591
1591
  * const States = Machine.defineStates({ Idle })
1592
1592
  * const machine = Machine.make({
1593
1593
  * states: States.states,
1594
- * events: [],
1594
+ * events: Machine.events(),
1595
1595
  * initial: () => States.initial.Idle.from()
1596
1596
  * }).handle({ Idle: {} })
1597
1597
  *
@@ -1818,7 +1818,7 @@ export interface Coverage<M extends AnyMachine> {
1818
1818
  * const States = Machine.defineStates({ Idle })
1819
1819
  * const machine = Machine.make({
1820
1820
  * states: States.states,
1821
- * events: [],
1821
+ * events: Machine.events(),
1822
1822
  * initial: () => States.initial.Idle.from()
1823
1823
  * }).handle({ Idle: {} })
1824
1824
  *
@@ -2034,7 +2034,7 @@ export interface VerifyOptions {
2034
2034
  * const States = Machine.defineStates({ Idle })
2035
2035
  * const machine = Machine.make({
2036
2036
  * states: States.states,
2037
- * events: [],
2037
+ * events: Machine.events(),
2038
2038
  * initial: () => States.initial.Idle.from()
2039
2039
  * }).handle({ Idle: {} })
2040
2040
  *
@@ -315,7 +315,7 @@ export const layerMemory: Layer.Layer<Storage> = internal.layerMemory
315
315
  * const States = Machine.defineStates({ Idle })
316
316
  * const machine = Machine.make({
317
317
  * states: States.states,
318
- * events: [],
318
+ * events: Machine.events(),
319
319
  * initial: () => States.initial.Idle.from()
320
320
  * }).handle({ Idle: {} })
321
321
  *
@@ -7,6 +7,7 @@
7
7
  import type * as Option from "effect/Option"
8
8
  import type * as Schema from "effect/Schema"
9
9
  import type * as Scope from "effect/Scope"
10
+ import type * as Stream from "effect/Stream"
10
11
  import type { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"
11
12
  import * as internal from "../../internal/machine/atom.js"
12
13
  import type { ChildNotActiveError, NotReadyError } from "../../internal/machine/atom.js"
@@ -89,14 +90,14 @@ type MachineStartError<InitialE, E, InitialR, R, RuntimeError = never> =
89
90
  * @category models
90
91
  * @since 0.4.0
91
92
  */
92
- export interface MachineAtom<State, Event, Error = never, Output = never, StartError = never> {
93
+ export interface MachineAtom<State, Event, Error = never, Output = never, StartError = never, Emitted = never> {
93
94
  /**
94
95
  * Atom containing the running machine handle once startup succeeds.
95
96
  *
96
97
  * @since 0.4.0
97
98
  */
98
99
  readonly ref: Atom.Atom<
99
- AsyncResult.AsyncResult<Machine.MachineRef<State, Event, Error, Output>, StartError>
100
+ AsyncResult.AsyncResult<Machine.MachineRef<State, Event, Error, Output, Emitted>, StartError>
100
101
  >
101
102
 
102
103
  /**
@@ -163,6 +164,32 @@ type RefState<Ref> = Ref extends Machine.MachineRef<infer State, any, any, any>
163
164
  type RefError<Ref> = Ref extends Machine.MachineRef<any, any, infer Error, any> ? Error : never
164
165
 
165
166
  type RefOutput<Ref> = Ref extends Machine.MachineRef<any, any, any, infer Output> ? Output : never
167
+ type RefEmitted<Ref> = Ref extends Machine.MachineRef<any, any, any, any, infer Emitted> ? Emitted : never
168
+
169
+ /**
170
+ * Observes ephemeral notifications from the actor owned by a machine atom.
171
+ * The stream requires an `AtomRegistry` and does not replay earlier emissions.
172
+ *
173
+ * @category getters
174
+ * @since 0.10.0
175
+ */
176
+ export const emissions: <State, Event, Error, Output, StartError, Emitted>(
177
+ self: MachineAtom<State, Event, Error, Output, StartError, Emitted>
178
+ ) => Stream.Stream<Emitted, StartError, AtomRegistry.AtomRegistry> = internal.emissions
179
+
180
+ /**
181
+ * Observes emissions from each active instance selected by a child bridge.
182
+ *
183
+ * @category getters
184
+ * @since 0.10.0
185
+ */
186
+ export const childEmissions: <Child extends Machine.ChildMachine.Any, StartError>(
187
+ self: ChildMachineAtom<Child, StartError>
188
+ ) => Stream.Stream<
189
+ RefEmitted<Machine.ChildMachine.Ref<Child>>,
190
+ StartError,
191
+ AtomRegistry.AtomRegistry
192
+ > = internal.childEmissions
166
193
 
167
194
  /**
168
195
  * Reactive access to one invoked child machine selected by its descriptor.
@@ -328,7 +355,7 @@ type ChildState<Child extends Machine.ChildMachine.Any> = RefState<Machine.Child
328
355
  * const States = Machine.defineStates({ Count })
329
356
  * const machine = Machine.make({
330
357
  * states: States.states,
331
- * events: [],
358
+ * events: Machine.events(),
332
359
  * initial: () => States.initial.Count(new Count({ value: 0 }))
333
360
  * }).handle({ Count: {} })
334
361
  * const machineAtom = AtomMachine.make(machine)
@@ -439,7 +466,7 @@ export const selectSnapshotChild: <
439
466
  * const States = Machine.defineStates({ Idle })
440
467
  * const machine = Machine.make({
441
468
  * states: States.states,
442
- * events: [],
469
+ * events: Machine.events(),
443
470
  * initial: () => States.initial.Idle.from()
444
471
  * }).handle({ Idle: {} })
445
472
  * const machineAtom = AtomMachine.make(machine)
@@ -537,7 +564,7 @@ type MachineInputArgsOf<M extends Machine.Machine.Any> = [
537
564
 
538
565
  type MachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
539
566
  Machine.Machine.Snapshot<Machine.Machine.States<M>>,
540
- Machine.Machine.InputEvent<M>,
567
+ Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
541
568
  MachineRuntimeError<Machine.Machine.Error<M>, Machine.Machine.Services<M>>,
542
569
  Machine.Machine.Output<M>,
543
570
  MachineStartError<
@@ -546,15 +573,17 @@ type MachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
546
573
  Machine.Machine.InitialServices<M>,
547
574
  Machine.Machine.Services<M>,
548
575
  RuntimeError
549
- >
576
+ >,
577
+ Machine.Machine.EmittedEvent<M>
550
578
  >
551
579
 
552
580
  type ResumedMachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
553
581
  Machine.Machine.Snapshot<Machine.Machine.States<M>>,
554
- Machine.Machine.InputEvent<M>,
582
+ Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
555
583
  MachineRuntimeError<Machine.Machine.Error<M>, Machine.Machine.Services<M>>,
556
584
  Machine.Machine.Output<M>,
557
- Machine.MachineSchemaDecodeError | RuntimeError
585
+ Machine.MachineSchemaDecodeError | RuntimeError,
586
+ Machine.Machine.EmittedEvent<M>
558
587
  >
559
588
 
560
589
  /**
@@ -607,7 +636,7 @@ export interface Bound<Services, RuntimeError = never> {
607
636
  * const States = Machine.defineStates({ Idle })
608
637
  * const machine = Machine.make({
609
638
  * states: States.states,
610
- * events: [],
639
+ * events: Machine.events(),
611
640
  * initial: () => States.initial.Idle.from()
612
641
  * }).handle({ Idle: {} })
613
642
  *
@@ -631,7 +660,8 @@ export const make: {
631
660
  FinalStates extends Machine.Machine.StateIdentifier<States> = never,
632
661
  Output = never,
633
662
  OutputStates extends Machine.Machine.StateIdentifier<States> = never,
634
- InputEvents extends ReadonlyArray<Machine.Machine.TaggedSchema> = Events
663
+ InputEvents extends ReadonlyArray<Machine.Machine.TaggedSchema> = Events,
664
+ ParentEvents extends ReadonlyArray<Machine.Machine.TaggedSchema> = readonly []
635
665
  >(
636
666
  machine:
637
667
  & Machine.Machine<
@@ -647,7 +677,8 @@ export const make: {
647
677
  Output,
648
678
  Emits,
649
679
  OutputStates,
650
- InputEvents
680
+ InputEvents,
681
+ ParentEvents
651
682
  >
652
683
  & EnsureNoExternalRequirements<
653
684
  MachineRequirements<
@@ -661,10 +692,11 @@ export const make: {
661
692
  ...args: [...Machine.Machine.InputArgs<Input>]
662
693
  ): MachineAtom<
663
694
  Machine.Machine.Snapshot<States>,
664
- Machine.Machine.EventOf<InputEvents>,
695
+ Machine.Machine.EventInputOf<InputEvents>,
665
696
  MachineRuntimeError<E, R>,
666
697
  Output,
667
- MachineStartError<InitialE, E, InitialR, R>
698
+ MachineStartError<InitialE, E, InitialR, R>,
699
+ Machine.Machine.EmittedEventOf<Emits>
668
700
  >
669
701
  } = internal.make
670
702