@typeonce/effect-machine 0.19.1 → 0.21.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 +90 -14
- package/dist/Machine.d.ts +155 -18
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +15 -3
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +18 -4
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +33 -3
- 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 +7 -0
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +1 -0
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +49 -13
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +11 -2
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +49 -9
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +4 -3
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +12 -1
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +9 -1
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +7 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +15 -3
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +2 -0
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +10 -8
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +2 -2
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +48 -0
- package/docs/effect-atom-react.md +28 -0
- package/package.json +1 -1
- package/src/Machine.ts +371 -45
- package/src/internal/machine/atom.ts +26 -18
- package/src/internal/machine/executionPlan.ts +37 -3
- package/src/internal/machine/invocation.ts +13 -1
- package/src/internal/machine/machine.ts +75 -15
- package/src/internal/machine/planner.ts +64 -11
- package/src/internal/machine/runtime.ts +48 -19
- package/src/internal/machine/topology.ts +18 -1
- package/src/internal/testing/machine/verification.ts +16 -3
- package/src/testing/MachineTest.ts +2 -0
- package/src/unstable/reactivity/AtomMachine.ts +10 -8
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
validateDeclaredTransitionTarget
|
|
48
48
|
} from "./planner.js"
|
|
49
49
|
import { decodeEmitSync, decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js"
|
|
50
|
-
import { isInitialTarget, isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
50
|
+
import { isInitialTarget, isNoTarget, isSnapshot, isStateUpdate, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
51
51
|
|
|
52
52
|
interface IndexedExecutionDescriptor {
|
|
53
53
|
readonly flat: boolean
|
|
@@ -548,7 +548,25 @@ const collectIndexedEvaluatedTransition = (
|
|
|
548
548
|
selection.context,
|
|
549
549
|
selection.transition.evaluate
|
|
550
550
|
)
|
|
551
|
-
const
|
|
551
|
+
const update = isStateUpdate(transitionResult.state)
|
|
552
|
+
? (() => {
|
|
553
|
+
const index = descriptor.indexByPath.get(transitionResult.state.path)
|
|
554
|
+
const node = index === undefined ? undefined : descriptor.nodes[index]
|
|
555
|
+
if (
|
|
556
|
+
index === undefined || node === undefined || state.active[index] !== 1 || node.schema === undefined ||
|
|
557
|
+
(node.type !== "compound" && node.type !== "parallel")
|
|
558
|
+
) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`Machine state update owner "${transitionResult.state.path}" must be an active valued compound or parallel state`
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
path: node.path,
|
|
565
|
+
value: decodeStateValueSync(machine, node, transitionResult.state.value)
|
|
566
|
+
}
|
|
567
|
+
})()
|
|
568
|
+
: undefined
|
|
569
|
+
const unresolvedTarget = update === undefined ? transitionResult.state : undefined
|
|
552
570
|
validateDeclaredTransitionTarget(
|
|
553
571
|
selection.sourcePath,
|
|
554
572
|
selection.trigger,
|
|
@@ -568,9 +586,14 @@ const collectIndexedEvaluatedTransition = (
|
|
|
568
586
|
throw new Error("Machine expected indexed transition target to be a snapshot or target builder result")
|
|
569
587
|
}
|
|
570
588
|
const next = target === undefined
|
|
571
|
-
? state
|
|
589
|
+
? update === undefined ? state : (() => {
|
|
590
|
+
const next = copyOwnedIndexedState(state)
|
|
591
|
+
next.values[descriptor.indexByPath.get(update.path)!] = update.value
|
|
592
|
+
return next
|
|
593
|
+
})()
|
|
572
594
|
: normalizeIndexedTargetStateSync(machine, descriptor, state, target as any, selection.leafIndex)
|
|
573
595
|
const changed = selection.transition.reenter || !hasSameIndexedActive(state, next)
|
|
596
|
+
const stabilize = changed || update !== undefined
|
|
574
597
|
if (!changed) {
|
|
575
598
|
return {
|
|
576
599
|
selection,
|
|
@@ -578,11 +601,13 @@ const collectIndexedEvaluatedTransition = (
|
|
|
578
601
|
branchKey: transitionResult.branchKey,
|
|
579
602
|
unresolvedTarget: unresolvedTarget as any,
|
|
580
603
|
target: target as any,
|
|
604
|
+
update,
|
|
581
605
|
next,
|
|
582
606
|
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
583
607
|
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
584
608
|
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
585
609
|
changed: false,
|
|
610
|
+
stabilize,
|
|
586
611
|
exitPaths: [],
|
|
587
612
|
entryPaths: [],
|
|
588
613
|
choiceTransitions: initialResolution?.transitions ?? []
|
|
@@ -603,11 +628,13 @@ const collectIndexedEvaluatedTransition = (
|
|
|
603
628
|
branchKey: transitionResult.branchKey,
|
|
604
629
|
unresolvedTarget: unresolvedTarget as any,
|
|
605
630
|
target: target as any,
|
|
631
|
+
update,
|
|
606
632
|
next,
|
|
607
633
|
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
608
634
|
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
609
635
|
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
610
636
|
changed: true,
|
|
637
|
+
stabilize,
|
|
611
638
|
exitPaths: getExitPaths(machine, activeConfigurationFromIndexedState(descriptor, state), boundary),
|
|
612
639
|
entryPaths: getEntryPaths(machine, activeConfigurationFromIndexedState(descriptor, next), boundary),
|
|
613
640
|
choiceTransitions: initialResolution?.transitions ?? []
|
|
@@ -663,6 +690,13 @@ const indexedMicrostep = (
|
|
|
663
690
|
if (transitions.length === 1) {
|
|
664
691
|
next = transitions[0]!.next
|
|
665
692
|
} else {
|
|
693
|
+
for (const transition of transitions) {
|
|
694
|
+
if (transition.update !== undefined) {
|
|
695
|
+
const values = next.values.slice()
|
|
696
|
+
values[descriptor.indexByPath.get(transition.update.path)!] = transition.update.value
|
|
697
|
+
next = { ...next, values }
|
|
698
|
+
}
|
|
699
|
+
}
|
|
666
700
|
const applicationOrder = [
|
|
667
701
|
...transitions.filter((transition) => !transition.changed),
|
|
668
702
|
...transitions.filter((transition) => transition.changed)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import * as Cause from "effect/Cause"
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
9
|
import * as Stream from "effect/Stream"
|
|
10
|
-
import type { ChildMachine, Inspection, Logic, Machine } from "../../Machine.js"
|
|
10
|
+
import type { ChildMachine, ChildOwner, Inspection, Logic, Machine } from "../../Machine.js"
|
|
11
11
|
import * as Configuration from "./configuration.js"
|
|
12
12
|
import { InfiniteTransitionError, MachineSchemaDecodeError, StoppedError } from "./errors.js"
|
|
13
13
|
import * as InvocationEvent from "./invocationEvent.js"
|
|
@@ -63,6 +63,16 @@ const streamLogic = (
|
|
|
63
63
|
const resolveValue = (value: unknown, context: Machine.InvokeContext<any, any, any, any>): unknown =>
|
|
64
64
|
typeof value === "function" ? value(context) : value
|
|
65
65
|
|
|
66
|
+
const makeChildOwner = (scope: Runtime.ProcessScope<any>): ChildOwner<any> => ({
|
|
67
|
+
spawn:
|
|
68
|
+
((descriptor: ChildMachine.Any, options?: { readonly input?: unknown }) =>
|
|
69
|
+
(scope.spawn as any)(descriptor, options)) as ChildOwner<any>["spawn"],
|
|
70
|
+
sendTo: ((descriptor: ChildMachine.Any, event: unknown) => scope.sendTo(descriptor, event)) as ChildOwner<
|
|
71
|
+
any
|
|
72
|
+
>["sendTo"],
|
|
73
|
+
stop: ((descriptor: ChildMachine.Any) => scope.stopChild(descriptor)) as ChildOwner<any>["stop"]
|
|
74
|
+
})
|
|
75
|
+
|
|
66
76
|
const resolveOne = (
|
|
67
77
|
raw: Record<PropertyKey, any>,
|
|
68
78
|
context: Machine.InvokeContext<any, any, any, any>,
|
|
@@ -291,11 +301,13 @@ export const startAll = (
|
|
|
291
301
|
paths: ReadonlyArray<string>,
|
|
292
302
|
event: Machine.LifecycleEvent<any>
|
|
293
303
|
): Effect.Effect<void, any, any> | undefined => {
|
|
304
|
+
const children = makeChildOwner(scope)
|
|
294
305
|
const effects = Planner.sortEntryPaths(machine, paths)
|
|
295
306
|
.filter((path) => configuration.active.has(path))
|
|
296
307
|
.flatMap((path) => {
|
|
297
308
|
const context = {
|
|
298
309
|
...(Configuration.getMachineReferences(configuration) ?? { self: scope.self, parent: scope.parent }),
|
|
310
|
+
children,
|
|
299
311
|
state: configuration.values.get(path),
|
|
300
312
|
containingState: Configuration.getParentValue(machine, configuration, path),
|
|
301
313
|
ancestors: Configuration.getParentValues(machine, configuration, path),
|
|
@@ -227,6 +227,15 @@ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topol
|
|
|
227
227
|
reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true })
|
|
228
228
|
})
|
|
229
229
|
|
|
230
|
+
const decorateStateUpdateSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => {
|
|
231
|
+
const update = (
|
|
232
|
+
resolve: (context: any, enqueue: unknown) => unknown,
|
|
233
|
+
options?: unknown
|
|
234
|
+
) => makeDirectTransitionDescriptor(selection, resolve, options)
|
|
235
|
+
Object.assign(update, selection)
|
|
236
|
+
return Object.freeze(update) as unknown as Topology.TargetSelection
|
|
237
|
+
}
|
|
238
|
+
|
|
230
239
|
const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
|
|
231
240
|
|
|
232
241
|
const makeInitialBuilderDescriptor = (
|
|
@@ -268,6 +277,7 @@ const decorateInitialSelectorNode = (node: unknown): unknown => {
|
|
|
268
277
|
|
|
269
278
|
const decorateTransitionSelectorNode = (node: unknown): unknown => {
|
|
270
279
|
if (Topology.isTargetSelection(node)) {
|
|
280
|
+
if (node.kind === "update") return decorateStateUpdateSelection(node)
|
|
271
281
|
return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
|
|
272
282
|
}
|
|
273
283
|
if (typeof node === "function") {
|
|
@@ -363,22 +373,29 @@ const makeSelectionValue = (
|
|
|
363
373
|
scope: Topology.TargetSelectionScope
|
|
364
374
|
): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope)
|
|
365
375
|
|
|
376
|
+
const makeStateUpdateSelection = (
|
|
377
|
+
path: string,
|
|
378
|
+
scope: "local" | "branch"
|
|
379
|
+
): Topology.TargetSelection => Topology.makeTargetSelection("update", path, scope)
|
|
380
|
+
|
|
366
381
|
const addSelectionChildren = (
|
|
367
382
|
builder: Record<string, unknown>,
|
|
368
383
|
stateNodes: Machine.StateNodes,
|
|
369
384
|
parent: string,
|
|
370
|
-
scope: "local" | "branch"
|
|
385
|
+
scope: "local" | "branch",
|
|
386
|
+
source?: string
|
|
371
387
|
): void => {
|
|
372
388
|
for (const node of stateNodes.byPath.values()) {
|
|
373
389
|
if (node.parent !== parent || node.type === "history") continue
|
|
374
|
-
builder[node.key] = makeSelectionNode(stateNodes, node.path, scope)
|
|
390
|
+
builder[node.key] = makeSelectionNode(stateNodes, node.path, scope, source)
|
|
375
391
|
}
|
|
376
392
|
}
|
|
377
393
|
|
|
378
394
|
const makeSelectionNode = (
|
|
379
395
|
stateNodes: Machine.StateNodes,
|
|
380
396
|
path: string,
|
|
381
|
-
scope: Topology.TargetSelectionScope
|
|
397
|
+
scope: Topology.TargetSelectionScope,
|
|
398
|
+
source?: string
|
|
382
399
|
): unknown => {
|
|
383
400
|
const node = getTargetBuilderNode(stateNodes, path)
|
|
384
401
|
const kind: Topology.TargetSelectionKind = node.type === "choice" ? "choice" : "state"
|
|
@@ -389,7 +406,17 @@ const makeSelectionNode = (
|
|
|
389
406
|
enumerable: true
|
|
390
407
|
})
|
|
391
408
|
if (scope === "local" || scope === "branch") {
|
|
392
|
-
addSelectionChildren(method, stateNodes, path, scope)
|
|
409
|
+
addSelectionChildren(method, stateNodes, path, scope, source)
|
|
410
|
+
}
|
|
411
|
+
if (
|
|
412
|
+
scope === "branch" && source !== undefined && node.schema !== undefined &&
|
|
413
|
+
(source === path || source.startsWith(`${path}.`)) &&
|
|
414
|
+
getTargetBuilderNode(stateNodes, source).type !== "choice"
|
|
415
|
+
) {
|
|
416
|
+
Object.defineProperty(method, "update", {
|
|
417
|
+
value: makeStateUpdateSelection(path, "branch"),
|
|
418
|
+
enumerable: true
|
|
419
|
+
})
|
|
393
420
|
}
|
|
394
421
|
}
|
|
395
422
|
return method
|
|
@@ -424,13 +451,16 @@ const makeTargetSelector = (
|
|
|
424
451
|
}
|
|
425
452
|
const branch: Record<string, unknown> = {}
|
|
426
453
|
const root = getTargetBuilderNode(stateNodes, source.split(".")[0]!)
|
|
427
|
-
branch[root.key] = makeSelectionNode(stateNodes, root.path, "branch")
|
|
454
|
+
branch[root.key] = makeSelectionNode(stateNodes, root.path, "branch", source)
|
|
428
455
|
const local: Record<string, unknown> = {}
|
|
429
456
|
const localScope = getLocalTargetScope(stateNodes, source)
|
|
430
457
|
if (localScope !== undefined) {
|
|
431
458
|
const localScopeNode = getTargetBuilderNode(stateNodes, localScope)
|
|
432
459
|
if (localScopeNode.schema !== undefined) {
|
|
433
460
|
local.with = makeSelectionValue("state", localScope, "local")
|
|
461
|
+
if (getTargetBuilderNode(stateNodes, source).type !== "choice") {
|
|
462
|
+
local.update = makeStateUpdateSelection(localScope, "local")
|
|
463
|
+
}
|
|
434
464
|
}
|
|
435
465
|
addSelectionChildren(local, stateNodes, localScope, "local")
|
|
436
466
|
}
|
|
@@ -469,6 +499,13 @@ const getSelectionBuilder = (
|
|
|
469
499
|
source: string
|
|
470
500
|
): unknown => {
|
|
471
501
|
if (selection.kind === "none") return target.none
|
|
502
|
+
if (selection.kind === "update") {
|
|
503
|
+
return withFrom(
|
|
504
|
+
(value: unknown) => Topology.makeStateUpdate(selection.path!, value),
|
|
505
|
+
"leaf",
|
|
506
|
+
true
|
|
507
|
+
)
|
|
508
|
+
}
|
|
472
509
|
let builder: any
|
|
473
510
|
let parts = selection.path!.split(".")
|
|
474
511
|
if (selection.kind === "history") {
|
|
@@ -514,6 +551,12 @@ const validateResolvedSelection = (
|
|
|
514
551
|
}
|
|
515
552
|
return
|
|
516
553
|
}
|
|
554
|
+
if (selection.kind === "update") {
|
|
555
|
+
if (!Topology.isStateUpdate(result) || result.path !== selection.path) {
|
|
556
|
+
throw new Error(`Machine state update for "${selection.path}" must return its selected update builder`)
|
|
557
|
+
}
|
|
558
|
+
return
|
|
559
|
+
}
|
|
517
560
|
if (result === undefined) return
|
|
518
561
|
const resultPath = typeof result === "object" && result !== null && hasProperty(result, "path") &&
|
|
519
562
|
typeof result.path === "string"
|
|
@@ -556,6 +599,9 @@ const runCapturedBranch = (
|
|
|
556
599
|
return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
|
|
557
600
|
}
|
|
558
601
|
|
|
602
|
+
const topologyTargetPath = (selection: Topology.TargetSelection): string | undefined =>
|
|
603
|
+
selection.kind === "update" ? undefined : selection.path
|
|
604
|
+
|
|
559
605
|
const isArrayIndexKey = (key: string): boolean => {
|
|
560
606
|
const index = Number(key)
|
|
561
607
|
return Number.isInteger(index) && index >= 0 && index < 0xffff_ffff && String(index) === key
|
|
@@ -738,7 +784,9 @@ const captureTransition = (
|
|
|
738
784
|
declinable,
|
|
739
785
|
targets: [
|
|
740
786
|
...new Set(
|
|
741
|
-
branches.flatMap((branch) =>
|
|
787
|
+
branches.flatMap((branch) =>
|
|
788
|
+
topologyTargetPath(branch.selection) === undefined ? [] : [branch.selection.path!]
|
|
789
|
+
)
|
|
742
790
|
)
|
|
743
791
|
],
|
|
744
792
|
branches: branches.map((branch) =>
|
|
@@ -746,7 +794,7 @@ const captureTransition = (
|
|
|
746
794
|
type: "branch" as const,
|
|
747
795
|
key: branch.key,
|
|
748
796
|
title: branch.title,
|
|
749
|
-
target: branch.selection
|
|
797
|
+
target: topologyTargetPath(branch.selection),
|
|
750
798
|
selection: transitionTargetSelection(branch.selection)
|
|
751
799
|
})
|
|
752
800
|
),
|
|
@@ -763,10 +811,10 @@ const captureTransition = (
|
|
|
763
811
|
return {
|
|
764
812
|
reenter,
|
|
765
813
|
declinable,
|
|
766
|
-
targets: branch.selection
|
|
814
|
+
targets: topologyTargetPath(branch.selection) === undefined ? [] : [branch.selection.path!],
|
|
767
815
|
branches: [{
|
|
768
816
|
type: "direct" as const,
|
|
769
|
-
target: branch.selection
|
|
817
|
+
target: topologyTargetPath(branch.selection),
|
|
770
818
|
selection: transitionTargetSelection(branch.selection)
|
|
771
819
|
}],
|
|
772
820
|
evaluate,
|
|
@@ -2124,15 +2172,30 @@ export const transition = <State, Event, Error = never, Requirements = never>(
|
|
|
2124
2172
|
export const child = <const Id extends string, M extends Machine.Any>(
|
|
2125
2173
|
id: Id,
|
|
2126
2174
|
machine: M
|
|
2175
|
+
): ChildMachine<Id, M> =>
|
|
2176
|
+
makeChild(id, machine, (input) =>
|
|
2177
|
+
machine.input === undefined
|
|
2178
|
+
? (internalProcess.toProcessLogic as any)(machine)
|
|
2179
|
+
: (internalProcess.toProcessLogic as any)(machine, input))
|
|
2180
|
+
|
|
2181
|
+
const makeChild = <const Id extends string, M extends Machine.Any>(
|
|
2182
|
+
id: Id,
|
|
2183
|
+
machine: M,
|
|
2184
|
+
makeLogic: (input?: unknown) => Logic<any, any, any, any, any, any>
|
|
2127
2185
|
): ChildMachine<Id, M> => ({
|
|
2128
2186
|
[ChildMachineTypeId]: ChildMachineTypeId,
|
|
2129
2187
|
id,
|
|
2130
2188
|
machine,
|
|
2131
|
-
[ChildMachineLogicTypeId]:
|
|
2189
|
+
[ChildMachineLogicTypeId]: makeLogic
|
|
2190
|
+
})
|
|
2191
|
+
|
|
2192
|
+
export const childFamily = <M extends Machine.Any>(machine: M): ChildMachine.Family<M> => {
|
|
2193
|
+
const makeLogic = (input?: unknown): Logic<any, any, any, any, any, any> =>
|
|
2132
2194
|
machine.input === undefined
|
|
2133
2195
|
? (internalProcess.toProcessLogic as any)(machine)
|
|
2134
2196
|
: (internalProcess.toProcessLogic as any)(machine, input)
|
|
2135
|
-
|
|
2197
|
+
return (id) => makeChild(id, machine, makeLogic)
|
|
2198
|
+
}
|
|
2136
2199
|
|
|
2137
2200
|
export const childAddress = <Event = never>(id: string): ChildAddress<Event> => id as ChildAddress<Event>
|
|
2138
2201
|
|
|
@@ -2174,10 +2237,7 @@ export const spawn: {
|
|
|
2174
2237
|
SpawnError<Options>,
|
|
2175
2238
|
ChildInitialError
|
|
2176
2239
|
>
|
|
2177
|
-
} = ((
|
|
2178
|
-
logic: Logic<any, any, any, any, any, any>,
|
|
2179
|
-
options?: SpawnOptions
|
|
2180
|
-
) =>
|
|
2240
|
+
} = ((logic: Logic<any, any, any, any, any, any>, options?: SpawnOptions) =>
|
|
2181
2241
|
Effect.flatMap(
|
|
2182
2242
|
internalRuntime.MachineRuntime,
|
|
2183
2243
|
(runtime) => options === undefined ? runtime.spawn(logic) : (runtime.spawn as any)(logic, options)
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
isInitialTarget,
|
|
53
53
|
isNoTarget,
|
|
54
54
|
isSnapshot,
|
|
55
|
+
isStateUpdate,
|
|
55
56
|
isTarget,
|
|
56
57
|
makeChoiceTarget,
|
|
57
58
|
makeTarget,
|
|
@@ -78,6 +79,11 @@ export type MicrostepPlan<State, Event, E, R> = {
|
|
|
78
79
|
readonly changed: boolean
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
type SettlingMicrostep<State, Event, E, R> = MicrostepPlan<State, Event, E, R> & {
|
|
83
|
+
/** Internal signal that eventless stabilization must run again. */
|
|
84
|
+
readonly stabilize: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
export type MacrostepPlan<State, Event, E, R, Output> =
|
|
82
88
|
& {
|
|
83
89
|
readonly next: State
|
|
@@ -580,10 +586,15 @@ export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R
|
|
|
580
586
|
| Machine.Snapshot<States>
|
|
581
587
|
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
582
588
|
| undefined
|
|
589
|
+
readonly update: {
|
|
590
|
+
readonly path: string
|
|
591
|
+
readonly value: unknown
|
|
592
|
+
} | undefined
|
|
583
593
|
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
584
594
|
readonly raisedEvents: ReadonlyArray<Event>
|
|
585
595
|
readonly emittedEvents: ReadonlyArray<unknown>
|
|
586
596
|
readonly changed: boolean
|
|
597
|
+
readonly stabilize: boolean
|
|
587
598
|
readonly exitPaths: ReadonlyArray<string>
|
|
588
599
|
readonly entryPaths: ReadonlyArray<string>
|
|
589
600
|
readonly choiceTransitions: ReadonlyArray<{
|
|
@@ -1143,7 +1154,9 @@ export const removeConflictingTransitions = <
|
|
|
1143
1154
|
let preempted = false
|
|
1144
1155
|
const transitionsToRemove = new Set<EvaluatedTransition<States, Event, E, R, Context>>()
|
|
1145
1156
|
for (const selected of filtered) {
|
|
1146
|
-
|
|
1157
|
+
const writesSameState = transition.update !== undefined && selected.update !== undefined &&
|
|
1158
|
+
transition.update.path === selected.update.path
|
|
1159
|
+
if (hasPathIntersection(transition.exitPaths, selected.exitPaths) || writesSameState) {
|
|
1147
1160
|
if (isDescendantOf(transition.selection.sourcePath, selected.selection.sourcePath)) {
|
|
1148
1161
|
transitionsToRemove.add(selected)
|
|
1149
1162
|
} else {
|
|
@@ -1344,6 +1357,7 @@ const collectEvaluatedTransition = <
|
|
|
1344
1357
|
throw new Error("Machine transition returned decline without declaring declinable: true")
|
|
1345
1358
|
}
|
|
1346
1359
|
const unresolvedTarget = transitionResult.state === undefined
|
|
1360
|
+
|| isStateUpdate(transitionResult.state)
|
|
1347
1361
|
? undefined
|
|
1348
1362
|
: transitionResult.state as
|
|
1349
1363
|
| Machine.Snapshot<States>
|
|
@@ -1356,6 +1370,21 @@ const collectEvaluatedTransition = <
|
|
|
1356
1370
|
selection.transition.targets,
|
|
1357
1371
|
unresolvedTarget
|
|
1358
1372
|
)
|
|
1373
|
+
const update = isStateUpdate(transitionResult.state)
|
|
1374
|
+
? (() => {
|
|
1375
|
+
const node = getNode(machine, transitionResult.state.path)
|
|
1376
|
+
if (
|
|
1377
|
+
!state.active.has(node.path) || node.schema === undefined ||
|
|
1378
|
+
(node.type !== "compound" && node.type !== "parallel")
|
|
1379
|
+
) {
|
|
1380
|
+
throw new Error(`Machine state update owner "${node.path}" must be an active valued compound or parallel state`)
|
|
1381
|
+
}
|
|
1382
|
+
return {
|
|
1383
|
+
path: node.path,
|
|
1384
|
+
value: decodeStateValueSync(machine, node, transitionResult.state.value)
|
|
1385
|
+
}
|
|
1386
|
+
})()
|
|
1387
|
+
: undefined
|
|
1359
1388
|
const choiceResolution = unresolvedTarget === undefined
|
|
1360
1389
|
? undefined
|
|
1361
1390
|
: resolveChoiceTarget(
|
|
@@ -1456,7 +1485,10 @@ const collectEvaluatedTransition = <
|
|
|
1456
1485
|
? getTargetNodePath(target)
|
|
1457
1486
|
: getTargetNodePath(unresolvedTarget)
|
|
1458
1487
|
let stateAfterTransition = target === undefined
|
|
1459
|
-
? state
|
|
1488
|
+
? update === undefined ? state : {
|
|
1489
|
+
...state,
|
|
1490
|
+
values: new Map(state.values).set(update.path, update.value)
|
|
1491
|
+
}
|
|
1460
1492
|
: normalizeTargetConfigurationSync<States>(machine, state, target)
|
|
1461
1493
|
for (const additionalTarget of additionalChoiceTargets) {
|
|
1462
1494
|
stateAfterTransition = normalizeTargetConfigurationSync<States>(
|
|
@@ -1466,6 +1498,7 @@ const collectEvaluatedTransition = <
|
|
|
1466
1498
|
)
|
|
1467
1499
|
}
|
|
1468
1500
|
const changed = selection.transition.reenter || !hasSameActivePaths(state, stateAfterTransition)
|
|
1501
|
+
const stabilize = changed || update !== undefined
|
|
1469
1502
|
|
|
1470
1503
|
if (!changed) {
|
|
1471
1504
|
return {
|
|
@@ -1474,6 +1507,7 @@ const collectEvaluatedTransition = <
|
|
|
1474
1507
|
branchKey: transitionResult.branchKey,
|
|
1475
1508
|
unresolvedTarget,
|
|
1476
1509
|
target,
|
|
1510
|
+
update,
|
|
1477
1511
|
commands: [
|
|
1478
1512
|
...transitionResult.commands,
|
|
1479
1513
|
...(choiceResolution?.commands ?? []),
|
|
@@ -1496,6 +1530,7 @@ const collectEvaluatedTransition = <
|
|
|
1496
1530
|
...additionalTargetEmittedEvents
|
|
1497
1531
|
],
|
|
1498
1532
|
changed,
|
|
1533
|
+
stabilize,
|
|
1499
1534
|
exitPaths: [],
|
|
1500
1535
|
entryPaths: [],
|
|
1501
1536
|
choiceTransitions: [
|
|
@@ -1521,6 +1556,7 @@ const collectEvaluatedTransition = <
|
|
|
1521
1556
|
branchKey: transitionResult.branchKey,
|
|
1522
1557
|
unresolvedTarget,
|
|
1523
1558
|
target,
|
|
1559
|
+
update,
|
|
1524
1560
|
commands: [
|
|
1525
1561
|
...transitionResult.commands,
|
|
1526
1562
|
...(choiceResolution?.commands ?? []),
|
|
@@ -1543,6 +1579,7 @@ const collectEvaluatedTransition = <
|
|
|
1543
1579
|
...additionalTargetEmittedEvents
|
|
1544
1580
|
],
|
|
1545
1581
|
changed,
|
|
1582
|
+
stabilize,
|
|
1546
1583
|
exitPaths: reenteredHistoryTarget !== undefined
|
|
1547
1584
|
? sortExitPaths(
|
|
1548
1585
|
machine,
|
|
@@ -1744,7 +1781,8 @@ export const planInitialSync = <
|
|
|
1744
1781
|
emittedEvents: [...choiceResolution.emittedEvents, ...initialHistoryEmittedEvents],
|
|
1745
1782
|
exitPaths: [],
|
|
1746
1783
|
entryPaths: [],
|
|
1747
|
-
changed: false
|
|
1784
|
+
changed: false,
|
|
1785
|
+
stabilize: false
|
|
1748
1786
|
}]
|
|
1749
1787
|
)
|
|
1750
1788
|
|
|
@@ -1850,7 +1888,8 @@ const microstep = <
|
|
|
1850
1888
|
emittedEvents: [],
|
|
1851
1889
|
exitPaths: [],
|
|
1852
1890
|
entryPaths: [],
|
|
1853
|
-
changed: false
|
|
1891
|
+
changed: false,
|
|
1892
|
+
stabilize: false
|
|
1854
1893
|
}
|
|
1855
1894
|
}
|
|
1856
1895
|
|
|
@@ -1881,6 +1920,17 @@ const microstep = <
|
|
|
1881
1920
|
...transition.choiceTransitions
|
|
1882
1921
|
])
|
|
1883
1922
|
let stateAfterTransition = state
|
|
1923
|
+
// Updates never reactivate topology. Apply every retained value write to the
|
|
1924
|
+
// original active configuration before any control target becomes
|
|
1925
|
+
// authoritative.
|
|
1926
|
+
for (const transition of sortedTransitions) {
|
|
1927
|
+
if (transition.update !== undefined) {
|
|
1928
|
+
stateAfterTransition = {
|
|
1929
|
+
...stateAfterTransition,
|
|
1930
|
+
values: new Map(stateAfterTransition.values).set(transition.update.path, transition.update.value)
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1884
1934
|
// Value-only targets are evaluated against the original configuration. If
|
|
1885
1935
|
// one is applied after a control-changing transition, it can resurrect a
|
|
1886
1936
|
// branch that the changing transition exited. Apply value-only updates
|
|
@@ -1901,6 +1951,7 @@ const microstep = <
|
|
|
1901
1951
|
}
|
|
1902
1952
|
|
|
1903
1953
|
const changed = transitions.some((transition) => transition.changed)
|
|
1954
|
+
const stabilize = transitions.some((transition) => transition.stabilize)
|
|
1904
1955
|
const transitionActions = sortedTransitions
|
|
1905
1956
|
.flatMap((transition) => transition.commands)
|
|
1906
1957
|
const transitionRaisedEvents = sortedTransitions
|
|
@@ -1918,7 +1969,8 @@ const microstep = <
|
|
|
1918
1969
|
emittedEvents: transitionEmittedEvents,
|
|
1919
1970
|
exitPaths: [],
|
|
1920
1971
|
entryPaths: [],
|
|
1921
|
-
changed: false
|
|
1972
|
+
changed: false,
|
|
1973
|
+
stabilize
|
|
1922
1974
|
}
|
|
1923
1975
|
}
|
|
1924
1976
|
|
|
@@ -1951,7 +2003,8 @@ const microstep = <
|
|
|
1951
2003
|
emittedEvents: [...exit.emittedEvents, ...transitionEmittedEvents, ...entry.emittedEvents],
|
|
1952
2004
|
exitPaths,
|
|
1953
2005
|
entryPaths,
|
|
1954
|
-
changed: true
|
|
2006
|
+
changed: true,
|
|
2007
|
+
stabilize
|
|
1955
2008
|
}
|
|
1956
2009
|
}
|
|
1957
2010
|
|
|
@@ -1974,7 +2027,7 @@ const settle = <
|
|
|
1974
2027
|
commands: Array<RuntimeCommand>,
|
|
1975
2028
|
raisedEvents: Array<Machine.EventOf<Events>>,
|
|
1976
2029
|
emittedEvents: Array<unknown>,
|
|
1977
|
-
microsteps: Array<
|
|
2030
|
+
microsteps: Array<SettlingMicrostep<ActiveConfiguration, Machine.EventOf<Events>, E, R>>
|
|
1978
2031
|
) => {
|
|
1979
2032
|
let currentState = state
|
|
1980
2033
|
let currentEvent = event
|
|
@@ -2010,7 +2063,7 @@ const settle = <
|
|
|
2010
2063
|
pendingCompletions.length === 0 ? [] : [pendingCompletions.shift()!]
|
|
2011
2064
|
)
|
|
2012
2065
|
if (done.length > 0) {
|
|
2013
|
-
const doneStep:
|
|
2066
|
+
const doneStep: SettlingMicrostep<ActiveConfiguration, Machine.EventOf<Events>, E, R> = microstep(
|
|
2014
2067
|
machine,
|
|
2015
2068
|
currentState,
|
|
2016
2069
|
currentEvent,
|
|
@@ -2021,7 +2074,7 @@ const settle = <
|
|
|
2021
2074
|
emittedEvents.push(...doneStep.emittedEvents)
|
|
2022
2075
|
microsteps.push(doneStep)
|
|
2023
2076
|
currentState = doneStep.next
|
|
2024
|
-
shouldRunAlways = doneStep.
|
|
2077
|
+
shouldRunAlways = doneStep.stabilize
|
|
2025
2078
|
continue
|
|
2026
2079
|
}
|
|
2027
2080
|
if (isActiveFinalConfiguration(machine, currentState)) {
|
|
@@ -2038,7 +2091,7 @@ const settle = <
|
|
|
2038
2091
|
? selectAlwaysTransitions<States, Events, Emits, E, R>(machine, currentState, currentEvent)
|
|
2039
2092
|
: []
|
|
2040
2093
|
if (always.length > 0) {
|
|
2041
|
-
const alwaysStep:
|
|
2094
|
+
const alwaysStep: SettlingMicrostep<ActiveConfiguration, Machine.EventOf<Events>, E, R> = microstep(
|
|
2042
2095
|
machine,
|
|
2043
2096
|
currentState,
|
|
2044
2097
|
currentEvent,
|
|
@@ -2049,7 +2102,7 @@ const settle = <
|
|
|
2049
2102
|
emittedEvents.push(...alwaysStep.emittedEvents)
|
|
2050
2103
|
microsteps.push(alwaysStep)
|
|
2051
2104
|
currentState = alwaysStep.next
|
|
2052
|
-
shouldRunAlways = alwaysStep.
|
|
2105
|
+
shouldRunAlways = alwaysStep.stabilize
|
|
2053
2106
|
continue
|
|
2054
2107
|
}
|
|
2055
2108
|
|
|
@@ -18,9 +18,10 @@ 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 { Inspection, Machine as MachineDefinition, MachineTarget } from "../../Machine.js"
|
|
21
|
+
import type { ChildMachine, Inspection, Machine as MachineDefinition, MachineTarget } from "../../Machine.js"
|
|
22
22
|
import { ChildAlreadyExistsError, StoppedError } from "./errors.js"
|
|
23
23
|
import * as InspectionRuntime from "./inspectionRuntime.js"
|
|
24
|
+
import { ChildMachineLogicTypeId } from "./symbols.js"
|
|
24
25
|
|
|
25
26
|
type ChildDescriptor = {
|
|
26
27
|
readonly id: string
|
|
@@ -376,7 +377,7 @@ const sendMachineTarget = (
|
|
|
376
377
|
export interface ProcessScope<Event> {
|
|
377
378
|
readonly self: ProcessAddress<Event>
|
|
378
379
|
readonly parent: ProcessAddress<unknown> | undefined
|
|
379
|
-
readonly spawn: ProcessSpawn
|
|
380
|
+
readonly spawn: ProcessSpawn<Event>
|
|
380
381
|
readonly sendParent: (event: unknown) => Effect.Effect<void, StoppedError>
|
|
381
382
|
readonly emit: (event: unknown) => Effect.Effect<void>
|
|
382
383
|
readonly sendTo: {
|
|
@@ -552,7 +553,15 @@ export interface ProcessLogic<
|
|
|
552
553
|
run(context: ProcessContext<State, Event>): Effect.Effect<Output, Error, Requirements>
|
|
553
554
|
}
|
|
554
555
|
|
|
555
|
-
export interface ProcessSpawn {
|
|
556
|
+
export interface ProcessSpawn<OwnerEvent = unknown> {
|
|
557
|
+
<const Child extends ChildMachine.Any>(
|
|
558
|
+
child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, OwnerEvent>,
|
|
559
|
+
...options: ChildMachine.SpawnArgs<Child>
|
|
560
|
+
): Effect.Effect<
|
|
561
|
+
ChildMachine.Ref<Child>,
|
|
562
|
+
ChildAlreadyExistsError | ChildMachine.StartError<Child>,
|
|
563
|
+
ChildMachine.StartRequirements<Child>
|
|
564
|
+
>
|
|
556
565
|
<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(
|
|
557
566
|
logic: ProcessLogic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>
|
|
558
567
|
): Effect.Effect<
|
|
@@ -1207,6 +1216,14 @@ const makeChildRuntimeSync = (
|
|
|
1207
1216
|
})
|
|
1208
1217
|
}
|
|
1209
1218
|
|
|
1219
|
+
function spawn<const Child extends ChildMachine.Any>(
|
|
1220
|
+
child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, unknown>,
|
|
1221
|
+
...options: ChildMachine.SpawnArgs<Child>
|
|
1222
|
+
): Effect.Effect<
|
|
1223
|
+
ChildMachine.Ref<Child>,
|
|
1224
|
+
ChildAlreadyExistsError | ChildMachine.StartError<Child>,
|
|
1225
|
+
ChildMachine.StartRequirements<Child>
|
|
1226
|
+
>
|
|
1210
1227
|
function spawn<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(
|
|
1211
1228
|
logic: ProcessLogic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>
|
|
1212
1229
|
): Effect.Effect<
|
|
@@ -1232,32 +1249,44 @@ const makeChildRuntimeSync = (
|
|
|
1232
1249
|
ChildAlreadyExistsError | ChildInitialError,
|
|
1233
1250
|
Exclude<ChildRequirements, Scope.Scope>
|
|
1234
1251
|
>
|
|
1235
|
-
function spawn
|
|
1236
|
-
|
|
1237
|
-
|
|
1252
|
+
function spawn(
|
|
1253
|
+
logicOrChild: ProcessLogic<any, any, any, any, any, any> | ChildMachine.Any,
|
|
1254
|
+
options?: {
|
|
1238
1255
|
readonly id: string
|
|
1239
1256
|
readonly descriptor?: ChildDescriptor
|
|
1240
1257
|
readonly onOutcome?: (
|
|
1241
|
-
outcome: RuntimeOutcome<
|
|
1258
|
+
outcome: RuntimeOutcome<any, any, any>
|
|
1242
1259
|
) => Effect.Effect<void>
|
|
1243
1260
|
readonly [activeSnapshotObserver]?: (
|
|
1244
|
-
snapshot: Extract<RuntimeSnapshot<
|
|
1261
|
+
snapshot: Extract<RuntimeSnapshot<any, any, any>, { readonly status: "active" }>
|
|
1245
1262
|
) => Effect.Effect<void>
|
|
1246
1263
|
readonly [sendParentOverride]?: (event: unknown) => Effect.Effect<void, StoppedError>
|
|
1247
|
-
}
|
|
1248
|
-
): Effect.Effect<
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1264
|
+
} | { readonly input?: unknown }
|
|
1265
|
+
): Effect.Effect<MachineRef<any, any, any, any>, any, any> {
|
|
1266
|
+
const descriptor = typeof logicOrChild === "object" && logicOrChild !== null &&
|
|
1267
|
+
ChildMachineLogicTypeId in logicOrChild
|
|
1268
|
+
? logicOrChild as ChildMachine.Any
|
|
1269
|
+
: undefined
|
|
1270
|
+
const logic = descriptor === undefined
|
|
1271
|
+
? logicOrChild as ProcessLogic<any, any, any, any, any, any>
|
|
1272
|
+
: descriptor[ChildMachineLogicTypeId](
|
|
1273
|
+
(options as { readonly input?: unknown } | undefined)?.input
|
|
1274
|
+
) as unknown as ProcessLogic<any, any, any, any, any, any>
|
|
1275
|
+
const spawnOptions = descriptor === undefined
|
|
1276
|
+
? options as {
|
|
1277
|
+
readonly id: string
|
|
1278
|
+
readonly descriptor?: ChildDescriptor
|
|
1279
|
+
readonly onOutcome?: (outcome: RuntimeOutcome<any, any, any>) => Effect.Effect<void>
|
|
1280
|
+
readonly [activeSnapshotObserver]?: (
|
|
1281
|
+
snapshot: Extract<RuntimeSnapshot<any, any, any>, { readonly status: "active" }>
|
|
1282
|
+
) => Effect.Effect<void>
|
|
1283
|
+
readonly [sendParentOverride]?: (event: unknown) => Effect.Effect<void, StoppedError>
|
|
1284
|
+
} | undefined
|
|
1285
|
+
: { id: descriptor.id, descriptor }
|
|
1253
1286
|
const token = Symbol()
|
|
1254
1287
|
const key = spawnOptions?.id ?? token
|
|
1255
1288
|
let startedChild: MachineRef<any, any, any, any> | undefined
|
|
1256
|
-
return Effect.suspend(()
|
|
1257
|
-
MachineRef<ChildState, ChildEvent, ChildError, ChildOutput>,
|
|
1258
|
-
ChildAlreadyExistsError | ChildInitialError,
|
|
1259
|
-
Exclude<ChildRequirements, Scope.Scope>
|
|
1260
|
-
> => {
|
|
1289
|
+
return Effect.suspend(() => {
|
|
1261
1290
|
if (registry.closed) {
|
|
1262
1291
|
return Effect.interrupt
|
|
1263
1292
|
}
|