@typeonce/effect-machine 0.9.0 → 0.11.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 +167 -28
- package/dist/Machine.d.ts +491 -283
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +74 -76
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +7 -3
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +42 -6
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js +2 -1
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/command.js +2 -2
- package/dist/internal/machine/command.js.map +1 -1
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
- package/dist/internal/machine/commandRuntime.js +6 -3
- package/dist/internal/machine/commandRuntime.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts +8 -1
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +9 -4
- package/dist/internal/machine/configuration.js.map +1 -1
- package/dist/internal/machine/errors.d.ts +1 -1
- package/dist/internal/machine/errors.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts +3 -3
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +50 -25
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +3 -2
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +15 -19
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +16 -10
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +2 -1
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +61 -24
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/process.d.ts +3 -0
- package/dist/internal/machine/process.d.ts.map +1 -1
- package/dist/internal/machine/process.js +11 -8
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +7 -13
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +128 -50
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +22 -2
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +138 -10
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +7 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +6 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/runtime.d.ts.map +1 -1
- package/dist/internal/testing/machine/runtime.js +2 -1
- package/dist/internal/testing/machine/runtime.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +5 -3
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +7 -7
- package/dist/testing/MachineTest.js +7 -7
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +27 -8
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +20 -3
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +191 -54
- package/package.json +1 -1
- package/src/Machine.ts +1220 -360
- package/src/internal/machine/atom.ts +76 -53
- package/src/internal/machine/cluster.ts +2 -1
- package/src/internal/machine/command.ts +2 -2
- package/src/internal/machine/commandRuntime.ts +9 -4
- package/src/internal/machine/configuration.ts +23 -5
- package/src/internal/machine/errors.ts +1 -1
- package/src/internal/machine/executionPlan.ts +74 -29
- package/src/internal/machine/invocation.ts +3 -2
- package/src/internal/machine/machine.ts +74 -49
- package/src/internal/machine/planner.ts +68 -24
- package/src/internal/machine/process.ts +75 -8
- package/src/internal/machine/protocol.ts +207 -68
- package/src/internal/machine/runtime.ts +266 -11
- package/src/internal/machine/topology.ts +15 -0
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/internal/testing/machine/runtime.ts +2 -1
- package/src/internal/testing/machine/verification.ts +5 -3
- package/src/testing/MachineTest.ts +7 -7
- package/src/unstable/cluster/ClusterMachine.ts +1 -1
- package/src/unstable/reactivity/AtomMachine.ts +44 -10
|
@@ -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>
|
|
@@ -300,6 +302,22 @@ export interface MachineRef<out State, in Event, out Error = never, out Output =
|
|
|
300
302
|
readonly childChanges: (child: any) => Stream.Stream<Option.Option<any>>
|
|
301
303
|
}
|
|
302
304
|
|
|
305
|
+
export interface PreparedProcess<
|
|
306
|
+
out State,
|
|
307
|
+
in Event,
|
|
308
|
+
out Error,
|
|
309
|
+
out Output,
|
|
310
|
+
out Emitted,
|
|
311
|
+
out StartError,
|
|
312
|
+
StartRequirements
|
|
313
|
+
> {
|
|
314
|
+
readonly id: string
|
|
315
|
+
readonly sessionId: string
|
|
316
|
+
readonly changes: Stream.Stream<RuntimeSnapshot<State, Error, Output>, StartError>
|
|
317
|
+
readonly emissions: Stream.Stream<Emitted>
|
|
318
|
+
readonly start: Effect.Effect<MachineRef<State, Event, Error, Output, Emitted>, StartError, StartRequirements>
|
|
319
|
+
}
|
|
320
|
+
|
|
303
321
|
interface ProcessAddress<in Event> {
|
|
304
322
|
readonly id: string
|
|
305
323
|
readonly sessionId: string
|
|
@@ -307,12 +325,19 @@ interface ProcessAddress<in Event> {
|
|
|
307
325
|
readonly send: (event: Event) => Effect.Effect<void, StoppedError>
|
|
308
326
|
}
|
|
309
327
|
|
|
328
|
+
const isProcessAddress = (value: unknown): value is ActorRef<unknown> =>
|
|
329
|
+
typeof value === "object" && value !== null && "send" in value && typeof value.send === "function"
|
|
330
|
+
|
|
310
331
|
export interface ProcessScope<Event> {
|
|
311
332
|
readonly self: ProcessAddress<Event>
|
|
312
333
|
readonly parent: ProcessAddress<unknown> | undefined
|
|
313
334
|
readonly spawn: ProcessSpawn
|
|
314
335
|
readonly sendParent: (event: unknown) => Effect.Effect<void, StoppedError>
|
|
315
|
-
readonly
|
|
336
|
+
readonly emit: (event: unknown) => Effect.Effect<void>
|
|
337
|
+
readonly sendTo: {
|
|
338
|
+
<TargetEvent>(target: ActorRef<TargetEvent>, event: TargetEvent): Effect.Effect<void, StoppedError>
|
|
339
|
+
(child: ChildSelector, event: unknown): Effect.Effect<void, StoppedError>
|
|
340
|
+
}
|
|
316
341
|
readonly stopChild: (child: ChildSelector) => Effect.Effect<void>
|
|
317
342
|
/** @internal */
|
|
318
343
|
readonly failCause: (cause: Cause.Cause<unknown>) => Effect.Effect<void>
|
|
@@ -600,6 +625,8 @@ const makeProcessRuntime: Effect.Effect<ProcessRuntime> = Effect.sync(() => {
|
|
|
600
625
|
interface StartInternalOptions {
|
|
601
626
|
readonly detached?: boolean
|
|
602
627
|
readonly id?: string
|
|
628
|
+
readonly sessionId?: string
|
|
629
|
+
readonly emissions?: EmissionRuntime
|
|
603
630
|
readonly onOutcome?: (outcome: RuntimeOutcome<any, any, any>) => Effect.Effect<void>
|
|
604
631
|
readonly onSnapshot?: (
|
|
605
632
|
snapshot: Extract<RuntimeSnapshot<any, any, any>, { readonly status: "active" }>
|
|
@@ -708,6 +735,10 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
|
|
|
708
735
|
ownerPath: options.path,
|
|
709
736
|
ownerActive: true
|
|
710
737
|
})
|
|
738
|
+
const parent: ProcessAddress<unknown> = {
|
|
739
|
+
...this.self,
|
|
740
|
+
send: (event) => options.sendParent(isCurrent, event)
|
|
741
|
+
}
|
|
711
742
|
const startOptions: StartInternalOptions = {
|
|
712
743
|
detached: true,
|
|
713
744
|
id: options.id,
|
|
@@ -722,7 +753,7 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
|
|
|
722
753
|
},
|
|
723
754
|
onStopSync: () => unregisterChild(this.registry, options.id, token),
|
|
724
755
|
skipStoppedOutcome: true,
|
|
725
|
-
parent
|
|
756
|
+
parent,
|
|
726
757
|
runtime: this.runtime
|
|
727
758
|
}
|
|
728
759
|
const execution = logic.execution
|
|
@@ -785,6 +816,57 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
|
|
|
785
816
|
|
|
786
817
|
const noChildChanges = Stream.succeed(Option.none()).pipe(Stream.concat(Stream.never))
|
|
787
818
|
const noParentSend = (_event: unknown): Effect.Effect<void, StoppedError> => Effect.void
|
|
819
|
+
const EmissionsClosed: unique symbol = Symbol("effect/Machine/EmissionsClosed")
|
|
820
|
+
|
|
821
|
+
type LazyEmissions = PubSub.PubSub<unknown> | typeof EmissionsClosed | undefined
|
|
822
|
+
|
|
823
|
+
interface EmissionRuntime {
|
|
824
|
+
readonly emit: (event: unknown) => Effect.Effect<void>
|
|
825
|
+
readonly close: () => Effect.Effect<void>
|
|
826
|
+
readonly stream: Stream.Stream<unknown>
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const makeEmissionRuntime = (): EmissionRuntime => {
|
|
830
|
+
let emissions: LazyEmissions
|
|
831
|
+
const getOrCreate: Effect.Effect<PubSub.PubSub<unknown> | undefined> = Effect.suspend(() => {
|
|
832
|
+
const observed = emissions
|
|
833
|
+
if (observed === EmissionsClosed) return Effect.succeed(undefined)
|
|
834
|
+
if (observed !== undefined) return Effect.succeed(observed)
|
|
835
|
+
return PubSub.unbounded<unknown>().pipe(
|
|
836
|
+
Effect.flatMap((candidate) =>
|
|
837
|
+
Effect.sync(() => {
|
|
838
|
+
const latest = emissions
|
|
839
|
+
if (latest === EmissionsClosed) return [undefined, true] as const
|
|
840
|
+
if (latest !== undefined) return [latest, true] as const
|
|
841
|
+
emissions = candidate
|
|
842
|
+
return [candidate, false] as const
|
|
843
|
+
}).pipe(
|
|
844
|
+
Effect.flatMap(([selected, discard]) =>
|
|
845
|
+
discard ? PubSub.shutdown(candidate).pipe(Effect.as(selected)) : Effect.succeed(selected)
|
|
846
|
+
)
|
|
847
|
+
)
|
|
848
|
+
)
|
|
849
|
+
)
|
|
850
|
+
})
|
|
851
|
+
return {
|
|
852
|
+
emit: (event) =>
|
|
853
|
+
Effect.suspend(() =>
|
|
854
|
+
emissions === undefined || emissions === EmissionsClosed
|
|
855
|
+
? Effect.void
|
|
856
|
+
: PubSub.publish(emissions, event).pipe(Effect.asVoid)
|
|
857
|
+
),
|
|
858
|
+
close: () => {
|
|
859
|
+
const observed = emissions
|
|
860
|
+
emissions = EmissionsClosed
|
|
861
|
+
return observed === undefined || observed === EmissionsClosed ? Effect.void : PubSub.shutdown(observed)
|
|
862
|
+
},
|
|
863
|
+
stream: Stream.unwrap(
|
|
864
|
+
getOrCreate.pipe(
|
|
865
|
+
Effect.map((emissions) => emissions === undefined ? Stream.empty : Stream.fromPubSub(emissions))
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
}
|
|
788
870
|
|
|
789
871
|
const childlessRuntime: ChildRuntime = {
|
|
790
872
|
close: () => Effect.void,
|
|
@@ -1088,9 +1170,10 @@ const startGenericInternal: <
|
|
|
1088
1170
|
| { readonly _tag: "Done"; readonly output: Output }
|
|
1089
1171
|
| { readonly _tag: "Failure"; readonly cause: Cause.Cause<Error> }
|
|
1090
1172
|
|
|
1091
|
-
const sessionId = yield* runtime.nextSessionId
|
|
1173
|
+
const sessionId = options.sessionId ?? (yield* runtime.nextSessionId)
|
|
1092
1174
|
const id = requestedId ?? sessionId
|
|
1093
1175
|
const queue = yield* Queue.unbounded<ProcessMessage<Event>>()
|
|
1176
|
+
const emissions = options.emissions ?? makeEmissionRuntime()
|
|
1094
1177
|
const termination = yield* Deferred.make<ProcessTermination>()
|
|
1095
1178
|
const done = yield* Deferred.make<Output, Error | StoppedError>()
|
|
1096
1179
|
const awaitCompletion = Deferred.await(done).pipe(Effect.exit, Effect.asVoid)
|
|
@@ -1161,7 +1244,7 @@ const startGenericInternal: <
|
|
|
1161
1244
|
}
|
|
1162
1245
|
const cleanupStartupFailure = <A, E>(exit: Exit.Exit<A, E>): Effect.Effect<void> =>
|
|
1163
1246
|
Exit.isFailure(exit)
|
|
1164
|
-
? closeChildren(exit)
|
|
1247
|
+
? closeChildren(exit).pipe(Effect.andThen(emissions.close()))
|
|
1165
1248
|
: Effect.void
|
|
1166
1249
|
const cleanup = onStopSync === undefined ? onStop ?? Effect.void : Effect.sync(onStopSync)
|
|
1167
1250
|
const sendParent = overrideSendParent ?? (parent === undefined ? noParentSend : parent.send)
|
|
@@ -1171,7 +1254,11 @@ const startGenericInternal: <
|
|
|
1171
1254
|
parent,
|
|
1172
1255
|
spawn,
|
|
1173
1256
|
sendParent,
|
|
1174
|
-
|
|
1257
|
+
emit: emissions.emit,
|
|
1258
|
+
sendTo: ((target: unknown, event: unknown) =>
|
|
1259
|
+
isProcessAddress(target) ? target.send(event) : sendTo(target as ChildSelector, event)) as ProcessScope<
|
|
1260
|
+
Event
|
|
1261
|
+
>["sendTo"],
|
|
1175
1262
|
stopChild,
|
|
1176
1263
|
failCause: (cause) =>
|
|
1177
1264
|
Deferred.succeed(termination, {
|
|
@@ -1209,7 +1296,9 @@ const startGenericInternal: <
|
|
|
1209
1296
|
const runtimeSnapshot = snapshot.snapshot
|
|
1210
1297
|
return runtimeSnapshot.status !== "active"
|
|
1211
1298
|
? publish
|
|
1212
|
-
: publish.pipe(Effect.tap(() =>
|
|
1299
|
+
: publish.pipe(Effect.tap(() =>
|
|
1300
|
+
notifyActiveSnapshot(onSnapshot, runtimeSnapshot)
|
|
1301
|
+
))
|
|
1213
1302
|
}
|
|
1214
1303
|
|
|
1215
1304
|
const completeChanges = (
|
|
@@ -1348,6 +1437,7 @@ const startGenericInternal: <
|
|
|
1348
1437
|
Effect.andThen(Queue.shutdown(queue)),
|
|
1349
1438
|
Effect.andThen(closeChildren(exit)),
|
|
1350
1439
|
Effect.andThen(setAndPublishSnapshot(snapshot)),
|
|
1440
|
+
Effect.andThen(emissions.close()),
|
|
1351
1441
|
Effect.andThen(Effect.sync(() => {
|
|
1352
1442
|
if (Exit.isFailure(exit)) {
|
|
1353
1443
|
failAcknowledgedMessage(inFlightMessage, exit.cause)
|
|
@@ -1504,6 +1594,7 @@ const startGenericInternal: <
|
|
|
1504
1594
|
state: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot.state)),
|
|
1505
1595
|
snapshot: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot)),
|
|
1506
1596
|
changes: changesStream,
|
|
1597
|
+
emissions: emissions.stream as Stream.Stream<never>,
|
|
1507
1598
|
join: Deferred.await(done),
|
|
1508
1599
|
stop,
|
|
1509
1600
|
send: self.send,
|
|
@@ -1657,6 +1748,8 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
1657
1748
|
private interruptRequested = false
|
|
1658
1749
|
private offerRevision = 0
|
|
1659
1750
|
private inFlightMessage: ProcessMessage<unknown> | undefined
|
|
1751
|
+
private readonly externalEmissions: EmissionRuntime | undefined
|
|
1752
|
+
private emissionsPubSub: LazyEmissions
|
|
1660
1753
|
|
|
1661
1754
|
constructor(
|
|
1662
1755
|
private readonly logic: ProcessLogic<any, any, any, any, any, any>,
|
|
@@ -1666,6 +1759,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
1666
1759
|
) {
|
|
1667
1760
|
this.sessionId = sessionId
|
|
1668
1761
|
this.id = options.id ?? sessionId
|
|
1762
|
+
this.externalEmissions = options.emissions
|
|
1669
1763
|
this.send = (event) => this.offerMessage(event)
|
|
1670
1764
|
this.address = {
|
|
1671
1765
|
id: this.id,
|
|
@@ -1690,7 +1784,11 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
1690
1784
|
parent,
|
|
1691
1785
|
spawn: this.childRuntime.spawn,
|
|
1692
1786
|
sendParent,
|
|
1693
|
-
|
|
1787
|
+
emit: (event) => this.emitEvent(event),
|
|
1788
|
+
sendTo: ((target: unknown, event: unknown) =>
|
|
1789
|
+
isProcessAddress(target)
|
|
1790
|
+
? target.send(event)
|
|
1791
|
+
: this.childRuntime.sendTo(target as ChildSelector, event)) as ProcessScope<unknown>["sendTo"],
|
|
1694
1792
|
stopChild: this.childRuntime.stop,
|
|
1695
1793
|
failCause: (cause: Cause.Cause<unknown>) => this.failCause(cause)
|
|
1696
1794
|
}
|
|
@@ -1743,7 +1841,11 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
1743
1841
|
parent,
|
|
1744
1842
|
spawn: self.childRuntime.spawn,
|
|
1745
1843
|
sendParent,
|
|
1746
|
-
|
|
1844
|
+
emit: (event) => self.emitEvent(event),
|
|
1845
|
+
sendTo: ((target: unknown, event: unknown) =>
|
|
1846
|
+
isProcessAddress(target)
|
|
1847
|
+
? target.send(event)
|
|
1848
|
+
: self.childRuntime.sendTo(target as ChildSelector, event)) as ProcessScope<unknown>["sendTo"],
|
|
1747
1849
|
stopChild: self.childRuntime.stop,
|
|
1748
1850
|
failCause: (cause: Cause.Cause<unknown>) => self.failCause(cause)
|
|
1749
1851
|
}
|
|
@@ -1838,6 +1940,10 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
1838
1940
|
return this.changesStream()
|
|
1839
1941
|
}
|
|
1840
1942
|
|
|
1943
|
+
get emissions(): Stream.Stream<never> {
|
|
1944
|
+
return (this.externalEmissions?.stream ?? this.emissionsStream()) as Stream.Stream<never>
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1841
1947
|
get join(): Effect.Effect<unknown, unknown> {
|
|
1842
1948
|
return Effect.suspend(() => {
|
|
1843
1949
|
if (this.lifecycle === "Completed") {
|
|
@@ -2145,6 +2251,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
2145
2251
|
}).pipe(
|
|
2146
2252
|
Effect.andThen(this.childRuntime.close(exit)),
|
|
2147
2253
|
Effect.andThen(this.setAndPublishSnapshot(snapshot)),
|
|
2254
|
+
Effect.andThen(this.closeEmissions()),
|
|
2148
2255
|
Effect.andThen(Effect.sync(() => {
|
|
2149
2256
|
if (requested._tag === "Failure") {
|
|
2150
2257
|
failAcknowledgedMessage(this.inFlightMessage, requested.cause)
|
|
@@ -2193,6 +2300,57 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
|
|
|
2193
2300
|
: PubSub.publish(snapshot.changes, Exit.succeed<void>(undefined)).pipe(Effect.asVoid)
|
|
2194
2301
|
}
|
|
2195
2302
|
|
|
2303
|
+
private emitEvent(event: unknown): Effect.Effect<void> {
|
|
2304
|
+
if (this.externalEmissions !== undefined) return this.externalEmissions.emit(event)
|
|
2305
|
+
return Effect.suspend(() =>
|
|
2306
|
+
this.emissionsPubSub === undefined || this.emissionsPubSub === EmissionsClosed
|
|
2307
|
+
? Effect.void
|
|
2308
|
+
: PubSub.publish(this.emissionsPubSub, event).pipe(Effect.asVoid)
|
|
2309
|
+
)
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
shutdownEmissions(): Effect.Effect<void> {
|
|
2313
|
+
return this.closeEmissions()
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
private closeEmissions(): Effect.Effect<void> {
|
|
2317
|
+
if (this.externalEmissions !== undefined) return this.externalEmissions.close()
|
|
2318
|
+
const observed = this.emissionsPubSub
|
|
2319
|
+
this.emissionsPubSub = EmissionsClosed
|
|
2320
|
+
return observed === undefined || observed === EmissionsClosed ? Effect.void : PubSub.shutdown(observed)
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
private getOrCreateEmissions(): Effect.Effect<PubSub.PubSub<unknown> | undefined> {
|
|
2324
|
+
return Effect.suspend(() => {
|
|
2325
|
+
const observed = this.emissionsPubSub
|
|
2326
|
+
if (observed === EmissionsClosed) return Effect.succeed(undefined)
|
|
2327
|
+
if (observed !== undefined) return Effect.succeed(observed)
|
|
2328
|
+
return PubSub.unbounded<unknown>().pipe(
|
|
2329
|
+
Effect.flatMap((candidate) =>
|
|
2330
|
+
Effect.sync(() => {
|
|
2331
|
+
const latest = this.emissionsPubSub
|
|
2332
|
+
if (latest === EmissionsClosed) return [undefined, true] as const
|
|
2333
|
+
if (latest !== undefined) return [latest, true] as const
|
|
2334
|
+
this.emissionsPubSub = candidate
|
|
2335
|
+
return [candidate, false] as const
|
|
2336
|
+
}).pipe(
|
|
2337
|
+
Effect.flatMap(([selected, discard]) =>
|
|
2338
|
+
discard ? PubSub.shutdown(candidate).pipe(Effect.as(selected)) : Effect.succeed(selected)
|
|
2339
|
+
)
|
|
2340
|
+
)
|
|
2341
|
+
)
|
|
2342
|
+
)
|
|
2343
|
+
})
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
private emissionsStream(): Stream.Stream<unknown> {
|
|
2347
|
+
return Stream.unwrap(
|
|
2348
|
+
this.getOrCreateEmissions().pipe(
|
|
2349
|
+
Effect.map((emissions) => emissions === undefined ? Stream.empty : Stream.fromPubSub(emissions))
|
|
2350
|
+
)
|
|
2351
|
+
)
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2196
2354
|
private setAndPublishSnapshot(snapshot: RuntimeSnapshot<unknown, unknown, unknown>): Effect.Effect<void> {
|
|
2197
2355
|
return Effect.suspend(() => {
|
|
2198
2356
|
this.flushPendingChanges()
|
|
@@ -2400,17 +2558,20 @@ const startCompactCompiledInternal: typeof startGenericInternal = Effect.fnUntra
|
|
|
2400
2558
|
logic: ProcessLogic<any, any, any, any, any, any>,
|
|
2401
2559
|
options: StartInternalOptions
|
|
2402
2560
|
) {
|
|
2403
|
-
const sessionId = yield* options.runtime.nextSessionId
|
|
2561
|
+
const sessionId = options.sessionId ?? (yield* options.runtime.nextSessionId)
|
|
2404
2562
|
const services = yield* Effect.context<any>()
|
|
2405
2563
|
const execution = logic.execution as CompiledProcessExecution<any, any, any, any, any, any>
|
|
2406
2564
|
const process = new CompiledProcess(logic, options, services, sessionId)
|
|
2407
2565
|
// A compiled initializer is synchronous by construction. Only startup
|
|
2408
2566
|
// callbacks that themselves return Effects need the generic initialization
|
|
2409
2567
|
// program; the compiled drain is still provided the complete service context.
|
|
2410
|
-
|
|
2568
|
+
const initialize = execution.initialSync !== undefined &&
|
|
2411
2569
|
options.onReady === undefined && options.onSnapshot === undefined
|
|
2412
2570
|
? process.initializeCompiledSync()
|
|
2413
2571
|
: process.initialize()
|
|
2572
|
+
return yield* initialize.pipe(
|
|
2573
|
+
Effect.onExit((exit) => Exit.isFailure(exit) ? process.shutdownEmissions() : Effect.void)
|
|
2574
|
+
)
|
|
2414
2575
|
}) as typeof startGenericInternal
|
|
2415
2576
|
|
|
2416
2577
|
const startLogicInternal: typeof startGenericInternal = ((
|
|
@@ -2510,3 +2671,97 @@ export const startProcess: <
|
|
|
2510
2671
|
}
|
|
2511
2672
|
)
|
|
2512
2673
|
})
|
|
2674
|
+
|
|
2675
|
+
const prepareProcessWithStrategy = Effect.fnUntraced(function*<
|
|
2676
|
+
State,
|
|
2677
|
+
Event,
|
|
2678
|
+
Error,
|
|
2679
|
+
Requirements,
|
|
2680
|
+
Output,
|
|
2681
|
+
InitialError,
|
|
2682
|
+
Emitted
|
|
2683
|
+
>(
|
|
2684
|
+
logic: ProcessLogic<State, Event, Error, Requirements, Output, InitialError>,
|
|
2685
|
+
strategy: ProcessRuntimeStrategy,
|
|
2686
|
+
options?: {
|
|
2687
|
+
readonly id?: string
|
|
2688
|
+
}
|
|
2689
|
+
) {
|
|
2690
|
+
const runtime = yield* makeProcessRuntime
|
|
2691
|
+
const sessionId = yield* runtime.nextSessionId
|
|
2692
|
+
const emissions = makeEmissionRuntime()
|
|
2693
|
+
const started = yield* Deferred.make<MachineRef<State, Event, Error, Output, Emitted>, InitialError>()
|
|
2694
|
+
const internalOptions: StartInternalOptions = options === undefined
|
|
2695
|
+
? {
|
|
2696
|
+
detached: true,
|
|
2697
|
+
emissions,
|
|
2698
|
+
runtime,
|
|
2699
|
+
sessionId
|
|
2700
|
+
}
|
|
2701
|
+
: {
|
|
2702
|
+
...options,
|
|
2703
|
+
detached: true,
|
|
2704
|
+
emissions,
|
|
2705
|
+
runtime,
|
|
2706
|
+
sessionId
|
|
2707
|
+
}
|
|
2708
|
+
const initialize = strategy === "generic"
|
|
2709
|
+
? startGenericInternal(logic, internalOptions)
|
|
2710
|
+
: strategy === "compiled"
|
|
2711
|
+
? logic.execution?._tag === "Compiled"
|
|
2712
|
+
? startCompactCompiledInternal(logic, internalOptions)
|
|
2713
|
+
: Effect.die(new Error("Machine cannot force the compiled runtime for generic process logic"))
|
|
2714
|
+
: startLogicInternal(logic, internalOptions)
|
|
2715
|
+
const start = yield* Effect.cached(
|
|
2716
|
+
initialize.pipe(
|
|
2717
|
+
Effect.onExit((exit) => Deferred.done(started, exit))
|
|
2718
|
+
) as Effect.Effect<MachineRef<State, Event, Error, Output, Emitted>, InitialError, Requirements>
|
|
2719
|
+
)
|
|
2720
|
+
return {
|
|
2721
|
+
id: options?.id ?? sessionId,
|
|
2722
|
+
sessionId,
|
|
2723
|
+
changes: Stream.unwrap(
|
|
2724
|
+
Deferred.await(started).pipe(Effect.map((ref) => ref.changes))
|
|
2725
|
+
),
|
|
2726
|
+
emissions: emissions.stream as Stream.Stream<Emitted>,
|
|
2727
|
+
start
|
|
2728
|
+
}
|
|
2729
|
+
})
|
|
2730
|
+
|
|
2731
|
+
export const prepareProcess: <
|
|
2732
|
+
State,
|
|
2733
|
+
Event,
|
|
2734
|
+
Error = never,
|
|
2735
|
+
Requirements = never,
|
|
2736
|
+
Output = never,
|
|
2737
|
+
InitialError = never,
|
|
2738
|
+
Emitted = never
|
|
2739
|
+
>(
|
|
2740
|
+
logic: ProcessLogic<State, Event, Error, Requirements, Output, InitialError>,
|
|
2741
|
+
options?: {
|
|
2742
|
+
readonly id?: string
|
|
2743
|
+
}
|
|
2744
|
+
) => Effect.Effect<
|
|
2745
|
+
PreparedProcess<State, Event, Error, Output, Emitted, InitialError, Requirements>
|
|
2746
|
+
> =
|
|
2747
|
+
((logic: ProcessLogic<any, any, any, any, any, any>, options?: { readonly id?: string }) =>
|
|
2748
|
+
prepareProcessWithStrategy(logic, "auto", options)) as any
|
|
2749
|
+
|
|
2750
|
+
/** @internal Test-only prepared startup strategy selection. */
|
|
2751
|
+
export const prepareProcessWithStrategyForTesting = <
|
|
2752
|
+
State,
|
|
2753
|
+
Event,
|
|
2754
|
+
Error = never,
|
|
2755
|
+
Requirements = never,
|
|
2756
|
+
Output = never,
|
|
2757
|
+
InitialError = never,
|
|
2758
|
+
Emitted = never
|
|
2759
|
+
>(
|
|
2760
|
+
logic: ProcessLogic<State, Event, Error, Requirements, Output, InitialError>,
|
|
2761
|
+
strategy: ProcessRuntimeStrategy,
|
|
2762
|
+
options?: {
|
|
2763
|
+
readonly id?: string
|
|
2764
|
+
}
|
|
2765
|
+
): Effect.Effect<
|
|
2766
|
+
PreparedProcess<State, Event, Error, Output, Emitted, InitialError, Requirements>
|
|
2767
|
+
> => prepareProcessWithStrategy(logic, strategy, options) as any
|
|
@@ -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,
|
|
@@ -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.
|
|
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 (
|
|
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
|
-
?
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
*
|