@typeonce/effect-machine 0.17.0 → 0.19.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 +133 -80
- package/dist/Machine.d.ts +434 -297
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +13 -55
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts +2 -2
- 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/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +1 -1
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +48 -10
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/serialization.d.ts.map +1 -1
- package/dist/internal/machine/serialization.js +75 -18
- package/dist/internal/machine/serialization.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +9 -6
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +13 -10
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +5 -3
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +22 -2
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +4 -0
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/docs/agent-guide.md +189 -83
- package/package.json +4 -4
- package/src/Machine.ts +845 -1051
- package/src/internal/machine/cluster.ts +4 -1
- package/src/internal/machine/invocation.ts +1 -1
- package/src/internal/machine/machine.ts +64 -8
- package/src/internal/machine/serialization.ts +100 -25
- package/src/internal/testing/machine/exploration.ts +1 -1
- package/src/internal/testing/machine/trace.ts +1 -1
- package/src/internal/testing/machine/verification.ts +16 -11
- package/src/testing/MachineTest.ts +13 -10
- package/src/unstable/cluster/ClusterMachine.ts +51 -1
- package/src/unstable/reactivity/AtomMachine.ts +1 -1
|
@@ -72,6 +72,7 @@ export const RejectionReason = Schema.Literals([
|
|
|
72
72
|
"InvalidCheckpoint",
|
|
73
73
|
"UnsupportedProcessLocal",
|
|
74
74
|
"TransitionFailure",
|
|
75
|
+
"SnapshotEncodeFailure",
|
|
75
76
|
"PersistenceFailure",
|
|
76
77
|
"EmissionFailure"
|
|
77
78
|
])
|
|
@@ -341,7 +342,9 @@ export const make = <
|
|
|
341
342
|
emitted.push(...planned.emittedEvents as any)
|
|
342
343
|
}
|
|
343
344
|
|
|
344
|
-
const encoded = yield* internalMachine.encodeSnapshot(rootMachine, current)
|
|
345
|
+
const encoded = yield* internalMachine.encodeSnapshot(rootMachine, current).pipe(
|
|
346
|
+
Effect.mapError((error) => reject("SnapshotEncodeFailure", String(error.cause)))
|
|
347
|
+
)
|
|
345
348
|
if (emitted.length > 0 && layerOptions?.enqueue === undefined) {
|
|
346
349
|
return yield* fail("EmissionFailure", "No durable enqueue handler was configured")
|
|
347
350
|
}
|
|
@@ -31,7 +31,7 @@ export interface AnyConfig {
|
|
|
31
31
|
export const makeKey = (path: string, id: string): string => `${path.length}:${path}${id}`
|
|
32
32
|
|
|
33
33
|
/** @internal */
|
|
34
|
-
export const makeChildId = (path: string, id: string): string => `Machine.
|
|
34
|
+
export const makeChildId = (path: string, id: string): string => `Machine.invocation:${makeKey(path, id)}`
|
|
35
35
|
|
|
36
36
|
const oneShot = (effect: Effect.Effect<any, any, any>): Logic<void, never, any, any, any> => ({
|
|
37
37
|
initial: () => Effect.void,
|
|
@@ -54,6 +54,7 @@ const TypeId = "~effect/Machine"
|
|
|
54
54
|
const ParentTypeId = "~effect/Machine/Parent"
|
|
55
55
|
export const InvokeTypeId: unique symbol = Symbol.for("effect/Machine/Invoke")
|
|
56
56
|
export const TransitionTypeId: unique symbol = Symbol.for("effect/Machine/Transition")
|
|
57
|
+
const InvokeBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InvokeBuilderDescriptor")
|
|
57
58
|
const ChildMachineTypeId = "~effect/Machine/ChildMachine"
|
|
58
59
|
type IsAny<A> = 0 extends 1 & A ? true : false
|
|
59
60
|
type MachineRuntimeRequirement = internalRuntime.MachineRuntime
|
|
@@ -788,20 +789,75 @@ const captureEventHandlers = (
|
|
|
788
789
|
return captured
|
|
789
790
|
}
|
|
790
791
|
|
|
792
|
+
interface InvokeBuilderDescriptor {
|
|
793
|
+
readonly [InvokeBuilderDescriptorTypeId]: typeof InvokeBuilderDescriptorTypeId
|
|
794
|
+
readonly config: Readonly<Record<PropertyKey, unknown>>
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
type InvokeBuilderChannel = "onDone" | "onFailure" | "onElement" | "onSnapshot"
|
|
798
|
+
|
|
799
|
+
const makeInvokeBuilder = (
|
|
800
|
+
config: Readonly<Record<PropertyKey, unknown>>,
|
|
801
|
+
channels: ReadonlyArray<InvokeBuilderChannel>
|
|
802
|
+
): InvokeBuilderDescriptor => {
|
|
803
|
+
const builder: Record<PropertyKey, unknown> = {
|
|
804
|
+
[InvokeBuilderDescriptorTypeId]: InvokeBuilderDescriptorTypeId as typeof InvokeBuilderDescriptorTypeId,
|
|
805
|
+
config
|
|
806
|
+
}
|
|
807
|
+
for (const channel of channels) {
|
|
808
|
+
if (!hasProperty(config, channel)) {
|
|
809
|
+
builder[channel] = (handler: unknown) => makeInvokeBuilder({ ...config, [channel]: handler }, channels)
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return Object.freeze(builder) as unknown as InvokeBuilderDescriptor
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const invokeSelector = Object.freeze({
|
|
816
|
+
effect: (id: string, effect: unknown) => makeInvokeBuilder({ id, effect }, ["onDone", "onFailure"]),
|
|
817
|
+
stream: (id: string, stream: unknown) => makeInvokeBuilder({ id, stream }, ["onElement", "onDone", "onFailure"]),
|
|
818
|
+
timer: (id: string, after: unknown) => makeInvokeBuilder({ id, after }, ["onDone"]),
|
|
819
|
+
logic: (id: string, options: Readonly<Record<PropertyKey, unknown>>) =>
|
|
820
|
+
makeInvokeBuilder({ id, ...options }, ["onSnapshot", "onDone", "onFailure"]),
|
|
821
|
+
child: (child: unknown, options?: Readonly<Record<PropertyKey, unknown>>) =>
|
|
822
|
+
makeInvokeBuilder(options === undefined ? { child } : { child, ...options }, [
|
|
823
|
+
"onSnapshot",
|
|
824
|
+
"onDone",
|
|
825
|
+
"onFailure"
|
|
826
|
+
])
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
const invokeBuilderConfig = (value: unknown, path: string): Readonly<Record<PropertyKey, unknown>> => {
|
|
830
|
+
if (
|
|
831
|
+
typeof value !== "object" || value === null ||
|
|
832
|
+
!hasProperty(value, InvokeBuilderDescriptorTypeId) ||
|
|
833
|
+
value[InvokeBuilderDescriptorTypeId] !== InvokeBuilderDescriptorTypeId
|
|
834
|
+
) {
|
|
835
|
+
throw new Error(`Machine invocation for state "${path}" must be constructed from its source selector`)
|
|
836
|
+
}
|
|
837
|
+
return (value as unknown as InvokeBuilderDescriptor).config
|
|
838
|
+
}
|
|
839
|
+
|
|
791
840
|
const captureInvokeDefinition = (
|
|
792
841
|
invoke: unknown,
|
|
793
842
|
stateNodes: Machine.StateNodes,
|
|
794
843
|
path: string
|
|
795
844
|
): unknown => {
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
845
|
+
if (typeof invoke !== "function") {
|
|
846
|
+
throw new Error(`Machine invocation for state "${path}" must be a source-first callback`)
|
|
847
|
+
}
|
|
848
|
+
const authored = invoke(invokeSelector)
|
|
849
|
+
const definitions = Array.isArray(authored) ? authored : [authored]
|
|
850
|
+
const capturedDefinitions = definitions.map((definition) => {
|
|
851
|
+
const captured = { ...invokeBuilderConfig(definition, path) }
|
|
852
|
+
for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
|
|
853
|
+
if (captured[key] !== undefined) {
|
|
854
|
+
captured[key] = captureTransition(captured[key], stateNodes, path, key)
|
|
855
|
+
}
|
|
802
856
|
}
|
|
803
|
-
|
|
804
|
-
|
|
857
|
+
return captured
|
|
858
|
+
})
|
|
859
|
+
if (Array.isArray(authored)) return capturedDefinitions
|
|
860
|
+
return capturedDefinitions[0]
|
|
805
861
|
}
|
|
806
862
|
|
|
807
863
|
const flattenHandlers = (
|
|
@@ -31,22 +31,48 @@ const EncodedSnapshotSchema = Schema.Struct({
|
|
|
31
31
|
_tag: Schema.Literal("MachineSnapshot"),
|
|
32
32
|
active: Schema.Array(Schema.Struct({
|
|
33
33
|
path: Schema.String,
|
|
34
|
-
value: Schema.
|
|
34
|
+
value: Schema.optionalKey(Schema.Json)
|
|
35
35
|
})),
|
|
36
|
-
completed: Schema.
|
|
36
|
+
completed: Schema.optionalKey(Schema.Array(Schema.Struct({
|
|
37
37
|
path: Schema.String,
|
|
38
|
-
output: Schema.
|
|
38
|
+
output: Schema.optionalKey(Schema.Json)
|
|
39
39
|
}))),
|
|
40
|
-
history: Schema.
|
|
40
|
+
history: Schema.optionalKey(Schema.Record(
|
|
41
41
|
Schema.String,
|
|
42
42
|
Schema.Struct({
|
|
43
43
|
mode: Schema.Literals(["shallow", "deep"]),
|
|
44
44
|
active: Schema.Array(Schema.String),
|
|
45
|
-
values: Schema.Record(Schema.String, Schema.
|
|
45
|
+
values: Schema.Record(Schema.String, Schema.Json)
|
|
46
46
|
})
|
|
47
47
|
))
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
const jsonCodecCache = new WeakMap<object, Schema.Top>()
|
|
51
|
+
|
|
52
|
+
const getJsonCodec = (schema: Schema.Top): Schema.Top => {
|
|
53
|
+
const key = schema as object
|
|
54
|
+
const cached = jsonCodecCache.get(key)
|
|
55
|
+
if (cached !== undefined) return cached
|
|
56
|
+
const codec = Schema.toCodecJson(schema)
|
|
57
|
+
jsonCodecCache.set(key, codec)
|
|
58
|
+
return codec
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const encodeError = (
|
|
62
|
+
machine: Machine.Any,
|
|
63
|
+
options: {
|
|
64
|
+
readonly boundary: "state" | "output" | "history"
|
|
65
|
+
readonly state: string
|
|
66
|
+
},
|
|
67
|
+
cause: Schema.SchemaError | Cause.Cause<unknown>
|
|
68
|
+
): MachineSchemaEncodeError =>
|
|
69
|
+
new MachineSchemaEncodeError({
|
|
70
|
+
machineId: machine.id,
|
|
71
|
+
boundary: options.boundary,
|
|
72
|
+
state: options.state,
|
|
73
|
+
cause
|
|
74
|
+
})
|
|
75
|
+
|
|
50
76
|
const encodeBoundary = (
|
|
51
77
|
machine: Machine.Any,
|
|
52
78
|
schema: Schema.Top,
|
|
@@ -55,16 +81,14 @@ const encodeBoundary = (
|
|
|
55
81
|
readonly boundary: "state" | "output" | "history"
|
|
56
82
|
readonly state: string
|
|
57
83
|
}
|
|
58
|
-
): Effect.Effect<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
})
|
|
67
|
-
)
|
|
84
|
+
): Effect.Effect<Schema.Json, MachineSchemaEncodeError, unknown> =>
|
|
85
|
+
Effect.try({
|
|
86
|
+
try: () => getJsonCodec(schema),
|
|
87
|
+
catch: (cause) => encodeError(machine, options, Cause.die(cause))
|
|
88
|
+
}).pipe(
|
|
89
|
+
Effect.flatMap((codec) => Schema.encodeUnknownEffect(codec)(value)),
|
|
90
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.Json)),
|
|
91
|
+
Effect.mapError((cause) => cause instanceof MachineSchemaEncodeError ? cause : encodeError(machine, options, cause))
|
|
68
92
|
)
|
|
69
93
|
|
|
70
94
|
const decodeEncodedBoundary = (
|
|
@@ -76,14 +100,26 @@ const decodeEncodedBoundary = (
|
|
|
76
100
|
readonly state: string
|
|
77
101
|
}
|
|
78
102
|
): Effect.Effect<unknown, MachineSchemaDecodeError, unknown> =>
|
|
79
|
-
|
|
80
|
-
|
|
103
|
+
Effect.try({
|
|
104
|
+
try: () => getJsonCodec(schema),
|
|
105
|
+
catch: (cause) =>
|
|
81
106
|
new MachineSchemaDecodeError({
|
|
82
107
|
machineId: machine.id,
|
|
83
108
|
boundary: options.boundary,
|
|
84
109
|
state: options.state,
|
|
85
|
-
cause
|
|
110
|
+
cause: Cause.die(cause)
|
|
86
111
|
})
|
|
112
|
+
}).pipe(
|
|
113
|
+
Effect.flatMap((codec) => Schema.decodeUnknownEffect(codec)(value)),
|
|
114
|
+
Effect.mapError((cause) =>
|
|
115
|
+
cause instanceof MachineSchemaDecodeError ?
|
|
116
|
+
cause :
|
|
117
|
+
new MachineSchemaDecodeError({
|
|
118
|
+
machineId: machine.id,
|
|
119
|
+
boundary: options.boundary,
|
|
120
|
+
state: options.state,
|
|
121
|
+
cause
|
|
122
|
+
})
|
|
87
123
|
)
|
|
88
124
|
)
|
|
89
125
|
|
|
@@ -91,7 +127,7 @@ const getCompletionSchema = (
|
|
|
91
127
|
machine: Machine.Any,
|
|
92
128
|
configuration: ActiveConfiguration,
|
|
93
129
|
path: string
|
|
94
|
-
): Schema.Top => {
|
|
130
|
+
): Schema.Top | undefined => {
|
|
95
131
|
const node = getNode(machine, path)
|
|
96
132
|
if (node.type === "compound") {
|
|
97
133
|
const child = getActiveChildPath(machine, configuration, path)
|
|
@@ -100,7 +136,7 @@ const getCompletionSchema = (
|
|
|
100
136
|
}
|
|
101
137
|
return getCompletionSchema(machine, configuration, child)
|
|
102
138
|
}
|
|
103
|
-
return node.output
|
|
139
|
+
return node.output
|
|
104
140
|
}
|
|
105
141
|
|
|
106
142
|
/** Defensively validates and normalizes an in-memory logical snapshot. Unlike
|
|
@@ -132,9 +168,17 @@ export const normalizeSnapshotEffect = <const States extends Machine.StateSchema
|
|
|
132
168
|
throw new Error(`Machine snapshot contains invalid completion "${path}"`)
|
|
133
169
|
}
|
|
134
170
|
completionPaths.add(path)
|
|
171
|
+
const schema = getCompletionSchema(machine, configuration, path)
|
|
172
|
+
if (schema === undefined) {
|
|
173
|
+
if (completion.output !== undefined) {
|
|
174
|
+
throw new Error(`Machine snapshot contains an output for state "${path}" without an output schema`)
|
|
175
|
+
}
|
|
176
|
+
outputs.set(path, undefined)
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
135
179
|
outputs.set(
|
|
136
180
|
path,
|
|
137
|
-
yield* decodeBoundary(machine,
|
|
181
|
+
yield* decodeBoundary(machine, schema, completion.output, {
|
|
138
182
|
boundary: "output",
|
|
139
183
|
state: path
|
|
140
184
|
})
|
|
@@ -242,9 +286,17 @@ export const encodeSnapshot = (
|
|
|
242
286
|
if (!configuration.active.has(path) || !isActiveFinalNode(machine, configuration, path)) {
|
|
243
287
|
throw new Error(`Machine encoded snapshot contains invalid completion "${path}"`)
|
|
244
288
|
}
|
|
289
|
+
const schema = getCompletionSchema(machine, configuration, path)
|
|
290
|
+
if (schema === undefined) {
|
|
291
|
+
if (output !== undefined) {
|
|
292
|
+
throw new Error(`Machine snapshot contains an output for state "${path}" without an output schema`)
|
|
293
|
+
}
|
|
294
|
+
completed.push({ path })
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
245
297
|
const encodedOutput = yield* encodeBoundary(
|
|
246
298
|
machine,
|
|
247
|
-
|
|
299
|
+
schema,
|
|
248
300
|
output,
|
|
249
301
|
{
|
|
250
302
|
boundary: "output",
|
|
@@ -289,7 +341,7 @@ export const encodeSnapshot = (
|
|
|
289
341
|
})
|
|
290
342
|
)
|
|
291
343
|
}
|
|
292
|
-
const encodedValues: Record<string,
|
|
344
|
+
const encodedValues: Record<string, Schema.Json> = {}
|
|
293
345
|
for (const path of record.active) {
|
|
294
346
|
const stateNode = machine.stateNodes.byPath.get(path)
|
|
295
347
|
if (
|
|
@@ -338,12 +390,21 @@ export const encodeSnapshot = (
|
|
|
338
390
|
}
|
|
339
391
|
}
|
|
340
392
|
|
|
341
|
-
|
|
393
|
+
const encoded: Machine.EncodedSnapshot = {
|
|
342
394
|
_tag: "MachineSnapshot" as const,
|
|
343
395
|
active,
|
|
344
396
|
...(completed.length === 0 ? {} : { completed }),
|
|
345
397
|
...(Object.keys(history).length === 0 ? {} : { history })
|
|
346
398
|
}
|
|
399
|
+
return yield* Schema.decodeUnknownEffect(EncodedSnapshotSchema)(encoded).pipe(
|
|
400
|
+
Effect.mapError((cause) =>
|
|
401
|
+
new MachineSchemaEncodeError({
|
|
402
|
+
machineId: machine.id,
|
|
403
|
+
boundary: "configuration",
|
|
404
|
+
cause
|
|
405
|
+
})
|
|
406
|
+
)
|
|
407
|
+
)
|
|
347
408
|
}).pipe(Effect.catchCause((cause) => failEncodeCause(machine, cause)))
|
|
348
409
|
|
|
349
410
|
export const decodeSnapshot = (
|
|
@@ -501,11 +562,25 @@ export const decodeSnapshot = (
|
|
|
501
562
|
throw new Error(`Machine encoded snapshot contains invalid completion "${completion.path}"`)
|
|
502
563
|
}
|
|
503
564
|
completionPaths.add(completion.path)
|
|
565
|
+
const schema = getCompletionSchema(machine, configuration, completion.path)
|
|
566
|
+
const hasOutput = Object.prototype.hasOwnProperty.call(completion, "output")
|
|
567
|
+
if (schema === undefined) {
|
|
568
|
+
if (hasOutput) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`Machine encoded snapshot contains an output for state "${completion.path}" without an output schema`
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
completions.push({ path: completion.path, output: undefined })
|
|
574
|
+
continue
|
|
575
|
+
}
|
|
576
|
+
if (!hasOutput) {
|
|
577
|
+
throw new Error(`Machine encoded snapshot omits output for state "${completion.path}"`)
|
|
578
|
+
}
|
|
504
579
|
completions.push({
|
|
505
580
|
path: completion.path,
|
|
506
581
|
output: yield* decodeEncodedBoundary(
|
|
507
582
|
machine,
|
|
508
|
-
|
|
583
|
+
schema,
|
|
509
584
|
completion.output,
|
|
510
585
|
{
|
|
511
586
|
boundary: "output",
|
|
@@ -34,7 +34,7 @@ import { makeTransitionCoverageCollector } from "./transitionCoverage.js"
|
|
|
34
34
|
|
|
35
35
|
type AnyMachine = Machine.Machine.Any
|
|
36
36
|
|
|
37
|
-
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
37
|
+
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
38
38
|
|
|
39
39
|
type ReadyMachine<M extends AnyMachine> =
|
|
40
40
|
& M
|
|
@@ -22,7 +22,7 @@ import type { EnsureExecutable } from "../../machine/readiness.js"
|
|
|
22
22
|
|
|
23
23
|
type AnyMachine = Machine.Machine.Any
|
|
24
24
|
|
|
25
|
-
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
25
|
+
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
26
26
|
|
|
27
27
|
type StatePath<M extends AnyMachine> = Machine.Machine.StateIdentifier<Machine.Machine.States<M>>
|
|
28
28
|
|
|
@@ -152,7 +152,7 @@ export const interpretModel = ReferenceModel.interpretModel
|
|
|
152
152
|
|
|
153
153
|
type AnyMachine = Machine.Machine.Any
|
|
154
154
|
|
|
155
|
-
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
155
|
+
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
156
156
|
|
|
157
157
|
type StatePath<M extends AnyMachine> = Machine.Machine.StateIdentifier<Machine.Machine.States<M>>
|
|
158
158
|
|
|
@@ -772,7 +772,7 @@ export const observedGraph: <M extends AnyMachine>(
|
|
|
772
772
|
traceOrTraces: Trace<M> | ReadonlyArray<Trace<M>>
|
|
773
773
|
) => Effect.Effect<
|
|
774
774
|
ObservedGraph<M>,
|
|
775
|
-
|
|
775
|
+
never,
|
|
776
776
|
Machine.Machine.SnapshotEncodingServices<Machine.Machine.States<M>>
|
|
777
777
|
> = Effect.fnUntraced(function*<M extends AnyMachine>(
|
|
778
778
|
machine: M,
|
|
@@ -837,19 +837,24 @@ export const observedGraph: <M extends AnyMachine>(
|
|
|
837
837
|
}
|
|
838
838
|
})
|
|
839
839
|
|
|
840
|
-
const
|
|
840
|
+
const representations = yield* Effect.forEach(
|
|
841
841
|
occurrences,
|
|
842
842
|
({ snapshot }) =>
|
|
843
|
-
(Machine.encodeSnapshot as any)(machine, snapshot) as Effect.Effect<
|
|
843
|
+
((Machine.encodeSnapshot as any)(machine, snapshot) as Effect.Effect<
|
|
844
844
|
Machine.Machine.EncodedSnapshot,
|
|
845
845
|
Machine.MachineSchemaEncodeError,
|
|
846
846
|
Machine.Machine.SnapshotEncodingServices<Machine.Machine.States<M>>
|
|
847
|
-
>
|
|
847
|
+
>).pipe(
|
|
848
|
+
Effect.match({
|
|
849
|
+
onFailure: () => ({ identity: snapshot, encoded: undefined }),
|
|
850
|
+
onSuccess: (encoded) => ({ identity: encoded, encoded })
|
|
851
|
+
})
|
|
852
|
+
)
|
|
848
853
|
)
|
|
849
|
-
const
|
|
850
|
-
const occurrenceIds =
|
|
854
|
+
const representationIdentity = makeStructuralIdentityIndex()
|
|
855
|
+
const occurrenceIds = representations.map(({ identity }) => representationIdentity(identity))
|
|
851
856
|
const grouped = new Map<string, {
|
|
852
|
-
readonly encoded: Machine.Machine.EncodedSnapshot
|
|
857
|
+
readonly encoded: Machine.Machine.EncodedSnapshot | undefined
|
|
853
858
|
readonly snapshot: Machine.Machine.Snapshot<Machine.Machine.States<M>>
|
|
854
859
|
startup: number
|
|
855
860
|
event: number
|
|
@@ -862,7 +867,7 @@ export const observedGraph: <M extends AnyMachine>(
|
|
|
862
867
|
grouped.set(
|
|
863
868
|
id,
|
|
864
869
|
group = {
|
|
865
|
-
encoded:
|
|
870
|
+
encoded: representations[index]!.encoded,
|
|
866
871
|
snapshot: occurrence.snapshot,
|
|
867
872
|
startup: 0,
|
|
868
873
|
event: 0,
|
|
@@ -879,8 +884,8 @@ export const observedGraph: <M extends AnyMachine>(
|
|
|
879
884
|
const node = Graph.addNode(mutable, {
|
|
880
885
|
id,
|
|
881
886
|
snapshot: group.snapshot,
|
|
882
|
-
encoded: group.encoded,
|
|
883
|
-
configuration: group.
|
|
887
|
+
...(group.encoded === undefined ? {} : { encoded: group.encoded }),
|
|
888
|
+
configuration: rawConfigurationPaths(machine, group.snapshot) as ReadonlyArray<StatePath<M>>,
|
|
884
889
|
observations: {
|
|
885
890
|
total: group.startup + group.event + group.microstep,
|
|
886
891
|
startup: group.startup,
|
|
@@ -122,7 +122,7 @@ export const interpretModel: (model: FiniteModel, events: ReadonlyArray<string>)
|
|
|
122
122
|
|
|
123
123
|
type AnyMachine = Machine.Machine.Any
|
|
124
124
|
|
|
125
|
-
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
125
|
+
type InputValue<M extends AnyMachine> = Machine.Machine.Input<M>
|
|
126
126
|
|
|
127
127
|
type StatePath<M extends AnyMachine> = Machine.Machine.StateIdentifier<Machine.Machine.States<M>>
|
|
128
128
|
|
|
@@ -151,7 +151,7 @@ type RootReadyMachine<M extends AnyMachine> =
|
|
|
151
151
|
* @category models
|
|
152
152
|
* @since 0.4.0
|
|
153
153
|
*/
|
|
154
|
-
export type Scenario<M extends AnyMachine> = Machine.Machine.
|
|
154
|
+
export type Scenario<M extends AnyMachine> = Machine.Machine.InputSchema<M> extends typeof Schema.Void ? {
|
|
155
155
|
readonly events: ReadonlyArray<Machine.Machine.InputEvent<M>>
|
|
156
156
|
}
|
|
157
157
|
: {
|
|
@@ -174,7 +174,7 @@ export type ScenarioOptions<M extends AnyMachine> =
|
|
|
174
174
|
readonly maxEvents?: number
|
|
175
175
|
readonly eventsArbitrary?: FastCheck.Arbitrary<ReadonlyArray<Machine.Machine.InputEvent<M>>>
|
|
176
176
|
}
|
|
177
|
-
& (Machine.Machine.
|
|
177
|
+
& (Machine.Machine.InputSchema<M> extends typeof Schema.Void ? {
|
|
178
178
|
readonly inputArbitrary?: never
|
|
179
179
|
}
|
|
180
180
|
: {
|
|
@@ -1410,7 +1410,7 @@ interface ExploreOptionsBase<M extends AnyMachine, Key extends ExplorationKey> {
|
|
|
1410
1410
|
*/
|
|
1411
1411
|
export type ExploreOptions<M extends AnyMachine, Key extends ExplorationKey = ExplorationKey> =
|
|
1412
1412
|
& ExploreOptionsBase<M, Key>
|
|
1413
|
-
& (Machine.Machine.
|
|
1413
|
+
& (Machine.Machine.InputSchema<M> extends typeof Schema.Void ? {
|
|
1414
1414
|
readonly input?: never
|
|
1415
1415
|
}
|
|
1416
1416
|
: {
|
|
@@ -1935,7 +1935,7 @@ export interface ObservedGraphNodeObservations {
|
|
|
1935
1935
|
}
|
|
1936
1936
|
|
|
1937
1937
|
/**
|
|
1938
|
-
* One
|
|
1938
|
+
* One decoded logical snapshot stored in the observed Effect graph.
|
|
1939
1939
|
*
|
|
1940
1940
|
* @category models
|
|
1941
1941
|
* @since 0.4.0
|
|
@@ -1943,7 +1943,8 @@ export interface ObservedGraphNodeObservations {
|
|
|
1943
1943
|
export interface ObservedGraphNode<M extends AnyMachine> {
|
|
1944
1944
|
readonly id: string
|
|
1945
1945
|
readonly snapshot: Machine.Machine.Snapshot<Machine.Machine.States<M>>
|
|
1946
|
-
|
|
1946
|
+
/** Canonical JSON representation when this local snapshot is portable. */
|
|
1947
|
+
readonly encoded?: Machine.Machine.EncodedSnapshot
|
|
1947
1948
|
readonly configuration: ReadonlyArray<StatePath<M>>
|
|
1948
1949
|
readonly observations: ObservedGraphNodeObservations
|
|
1949
1950
|
}
|
|
@@ -2004,9 +2005,11 @@ export interface ObservedGraph<M extends AnyMachine> {
|
|
|
2004
2005
|
|
|
2005
2006
|
/**
|
|
2006
2007
|
* Converts concrete planner traces into an observed logical-state graph.
|
|
2007
|
-
*
|
|
2008
|
-
*
|
|
2009
|
-
*
|
|
2008
|
+
* Portable nodes are deduplicated by the public snapshot encoding. If encoding
|
|
2009
|
+
* fails, process-local values are instead compared by cycle-safe structural
|
|
2010
|
+
* identity and `ObservedGraphNode.encoded` is omitted. Every edge is a concrete
|
|
2011
|
+
* startup or public-event macrostep. This intentionally does not claim to be a
|
|
2012
|
+
* static or exhaustive graph of the machine.
|
|
2010
2013
|
*
|
|
2011
2014
|
* @category verification
|
|
2012
2015
|
* @since 0.4.0
|
|
@@ -2016,7 +2019,7 @@ export const observedGraph: <M extends AnyMachine>(
|
|
|
2016
2019
|
traceOrTraces: Trace<M> | ReadonlyArray<Trace<M>>
|
|
2017
2020
|
) => Effect.Effect<
|
|
2018
2021
|
ObservedGraph<M>,
|
|
2019
|
-
|
|
2022
|
+
never,
|
|
2020
2023
|
Machine.Machine.SnapshotEncodingServices<Machine.Machine.States<M>>
|
|
2021
2024
|
> = internal.observedGraph
|
|
2022
2025
|
|
|
@@ -153,6 +153,7 @@ export const RejectionReason = Schema.Literals([
|
|
|
153
153
|
"InvalidCheckpoint",
|
|
154
154
|
"UnsupportedProcessLocal",
|
|
155
155
|
"TransitionFailure",
|
|
156
|
+
"SnapshotEncodeFailure",
|
|
156
157
|
"PersistenceFailure",
|
|
157
158
|
"EmissionFailure"
|
|
158
159
|
])
|
|
@@ -246,6 +247,51 @@ type MachineServices<M extends Machine.Machine.Any> =
|
|
|
246
247
|
|
|
247
248
|
type IsAny<A> = 0 extends (1 & A) ? true : false
|
|
248
249
|
|
|
250
|
+
type IsNever<A> = [A] extends [never] ? true : false
|
|
251
|
+
|
|
252
|
+
type IsUnknown<A> = IsAny<A> extends true ? false : unknown extends A ? true : false
|
|
253
|
+
|
|
254
|
+
type IsJsonEncoded<S extends Schema.Top> = IsAny<S["Encoded"]> extends true ? false
|
|
255
|
+
: IsNever<S["Encoded"]> extends true ? false
|
|
256
|
+
: IsUnknown<S["Encoded"]> extends true ? false
|
|
257
|
+
: [S["Encoded"]] extends [Schema.Json] ? true
|
|
258
|
+
: false
|
|
259
|
+
|
|
260
|
+
type NonJsonState<States extends Machine.Machine.StateSchemas> = Machine.Machine.ValuedStateIdentifier<States> extends
|
|
261
|
+
infer StateId
|
|
262
|
+
? StateId extends Machine.Machine.ValuedStateIdentifier<States> ?
|
|
263
|
+
IsJsonEncoded<Machine.Machine.SchemaByIdentifier<States, StateId>> extends true ? never : StateId
|
|
264
|
+
: never
|
|
265
|
+
: never
|
|
266
|
+
|
|
267
|
+
type NonJsonOutput<States extends Machine.Machine.StateSchemas> = Machine.Machine.DeclaredOutputState<States> extends
|
|
268
|
+
infer StateId
|
|
269
|
+
? StateId extends Machine.Machine.DeclaredOutputState<States> ?
|
|
270
|
+
Machine.Machine.NodeByIdentifier<States, StateId> extends {
|
|
271
|
+
readonly output: infer Output extends Schema.Top
|
|
272
|
+
} ? IsJsonEncoded<Output> extends true ? never : StateId
|
|
273
|
+
: never
|
|
274
|
+
: never
|
|
275
|
+
: never
|
|
276
|
+
|
|
277
|
+
type NonJsonInputEvent<Events extends ReadonlyArray<Machine.Machine.TaggedSchema>> = {
|
|
278
|
+
readonly [Index in keyof Events]: Events[Index] extends infer EventSchema extends Machine.Machine.TaggedSchema
|
|
279
|
+
? IsJsonEncoded<EventSchema> extends true ? never : Machine.Machine.TagOf<EventSchema>
|
|
280
|
+
: never
|
|
281
|
+
}[number]
|
|
282
|
+
|
|
283
|
+
type EnsureJsonEncoded<
|
|
284
|
+
States extends Machine.Machine.StateSchemas,
|
|
285
|
+
InputEvents extends ReadonlyArray<Machine.Machine.TaggedSchema>
|
|
286
|
+
> = [NonJsonState<States> | NonJsonOutput<States> | NonJsonInputEvent<InputEvents>] extends [never] ? unknown
|
|
287
|
+
: {
|
|
288
|
+
readonly "~effect/ClusterMachine/NonJsonEncoded": {
|
|
289
|
+
readonly states: NonJsonState<States>
|
|
290
|
+
readonly outputs: NonJsonOutput<States>
|
|
291
|
+
readonly inputEvents: NonJsonInputEvent<InputEvents>
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
249
295
|
type ExcludeCompatibleRuntime<Requirements, Events, Emits> = Requirements extends Machine.Runtime.Requirement<
|
|
250
296
|
infer RequiredEvents,
|
|
251
297
|
infer RequiredEmits
|
|
@@ -303,6 +349,9 @@ export const layerMemory: Layer.Layer<Storage> = internal.layerMemory
|
|
|
303
349
|
* rejected. Planning-time raised events remain part of the current macrostep.
|
|
304
350
|
* Arbitrary action effects may run again after a crash before checkpoint
|
|
305
351
|
* commit, so the bridge does not provide exactly-once external effects.
|
|
352
|
+
* State values, completion outputs, and public input events must declare
|
|
353
|
+
* JSON-compatible encoded representations. Local and internal event protocols
|
|
354
|
+
* remain unrestricted because they do not cross the Cluster boundary.
|
|
306
355
|
*
|
|
307
356
|
* **Example**
|
|
308
357
|
*
|
|
@@ -364,7 +413,8 @@ export const make: <
|
|
|
364
413
|
ParentEvents
|
|
365
414
|
>
|
|
366
415
|
& EnsureExecutable<States, UnhandledStates, OutputStates>
|
|
367
|
-
& Machine.Machine.RootCompatible<ParentEvents
|
|
416
|
+
& Machine.Machine.RootCompatible<ParentEvents>
|
|
417
|
+
& EnsureJsonEncoded<States, InputEvents>,
|
|
368
418
|
options: {
|
|
369
419
|
readonly version: string
|
|
370
420
|
},
|
|
@@ -582,7 +582,7 @@ type EnsureMachineExecutable<M extends Machine.Machine.Any> = IsAny<Machine.Mach
|
|
|
582
582
|
>
|
|
583
583
|
|
|
584
584
|
type MachineInputArgsOf<M extends Machine.Machine.Any> = [
|
|
585
|
-
...Machine.Machine.InputArgs<Machine.Machine.
|
|
585
|
+
...Machine.Machine.InputArgs<Machine.Machine.InputSchema<M>>
|
|
586
586
|
]
|
|
587
587
|
|
|
588
588
|
type MachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
|