@typeonce/effect-machine 0.6.0 → 0.7.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 +4 -3
- package/dist/Machine.d.ts +24 -6
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +5 -0
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +6 -3
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +1 -1
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +26 -0
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +23 -0
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +14 -0
- package/package.json +5 -5
- package/src/Machine.ts +6909 -0
- package/src/index.ts +1 -0
- package/src/internal/machine/activities.ts +108 -0
- package/src/internal/machine/atom.ts +684 -0
- package/src/internal/machine/cluster.ts +394 -0
- package/src/internal/machine/command.ts +58 -0
- package/src/internal/machine/commandRuntime.ts +43 -0
- package/src/internal/machine/configuration.ts +1331 -0
- package/src/internal/machine/errors.ts +87 -0
- package/src/internal/machine/executionPlan.ts +996 -0
- package/src/internal/machine/invocation.ts +119 -0
- package/src/internal/machine/machine.ts +1750 -0
- package/src/internal/machine/planner.ts +1933 -0
- package/src/internal/machine/process.ts +906 -0
- package/src/internal/machine/protocol.ts +322 -0
- package/src/internal/machine/readiness.ts +10 -0
- package/src/internal/machine/runtime.ts +2512 -0
- package/src/internal/machine/serialization.ts +498 -0
- package/src/internal/machine/stateDefinition.ts +270 -0
- package/src/internal/machine/symbols.ts +2 -0
- package/src/internal/machine/topology.ts +479 -0
- package/src/internal/testing/machine/arbitrary.ts +102 -0
- package/src/internal/testing/machine/exploration.ts +331 -0
- package/src/internal/testing/machine/finiteModel.ts +1498 -0
- package/src/internal/testing/machine/invariant.ts +372 -0
- package/src/internal/testing/machine/probe.ts +79 -0
- package/src/internal/testing/machine/referenceModel.ts +1505 -0
- package/src/internal/testing/machine/runtime.ts +1710 -0
- package/src/internal/testing/machine/runtimeInvariant.ts +486 -0
- package/src/internal/testing/machine/trace.ts +150 -0
- package/src/internal/testing/machine/verification.ts +1890 -0
- package/src/testing/MachineTest.ts +2067 -0
- package/src/testing/index.ts +7 -0
- package/src/unstable/cluster/ClusterMachine.ts +390 -0
- package/src/unstable/cluster/index.ts +1 -0
- package/src/unstable/reactivity/AtomMachine.ts +696 -0
- package/src/unstable/reactivity/index.ts +1 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal machine schema protocol and boundary decoders.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.4.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Cause from "effect/Cause"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import { hasProperty } from "effect/Predicate"
|
|
10
|
+
import * as Result from "effect/Result"
|
|
11
|
+
import * as Schema from "effect/Schema"
|
|
12
|
+
import type { Machine } from "../../Machine.js"
|
|
13
|
+
import { MachineSchemaDecodeError } from "./errors.js"
|
|
14
|
+
import { getStateNodeSchema, isStateInput } from "./topology.js"
|
|
15
|
+
|
|
16
|
+
export interface DecodeBoundaryOptions {
|
|
17
|
+
readonly boundary: "input" | "event" | "emit" | "state" | "output" | "history" | "configuration"
|
|
18
|
+
readonly state?: string
|
|
19
|
+
readonly event?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface MachineProtocolSchemas {
|
|
23
|
+
readonly event: Schema.Top
|
|
24
|
+
readonly emit: Schema.Top
|
|
25
|
+
readonly eventConstructors: ReadonlySet<object>
|
|
26
|
+
readonly trustedEvents: WeakSet<object>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type BoundaryDecoder = (value: unknown) => Effect.Effect<unknown, Schema.SchemaError, unknown>
|
|
30
|
+
|
|
31
|
+
type BoundaryResultDecoder = (value: unknown) => Result.Result<unknown, Schema.SchemaError>
|
|
32
|
+
|
|
33
|
+
const boundaryDecoderCache = new WeakMap<object, BoundaryDecoder>()
|
|
34
|
+
|
|
35
|
+
const boundaryResultDecoderCache = new WeakMap<object, BoundaryResultDecoder>()
|
|
36
|
+
|
|
37
|
+
const getBoundaryDecoder = (schema: Schema.Top): BoundaryDecoder => {
|
|
38
|
+
const key = schema as object
|
|
39
|
+
const cached = boundaryDecoderCache.get(key)
|
|
40
|
+
if (cached !== undefined) {
|
|
41
|
+
return cached
|
|
42
|
+
}
|
|
43
|
+
const decoder = Schema.decodeUnknownEffect(Schema.toType(schema)) as BoundaryDecoder
|
|
44
|
+
boundaryDecoderCache.set(key, decoder)
|
|
45
|
+
return decoder
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const getBoundaryResultDecoder = (schema: Schema.Top): BoundaryResultDecoder => {
|
|
49
|
+
const key = schema as object
|
|
50
|
+
const cached = boundaryResultDecoderCache.get(key)
|
|
51
|
+
if (cached !== undefined) {
|
|
52
|
+
return cached
|
|
53
|
+
}
|
|
54
|
+
const decoder = Schema.decodeUnknownResult(Schema.toType(schema)) as BoundaryResultDecoder
|
|
55
|
+
boundaryResultDecoderCache.set(key, decoder)
|
|
56
|
+
return decoder
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const MachineProtocolTypeId = Symbol.for("effect/Machine/protocol")
|
|
60
|
+
|
|
61
|
+
const getProtocolSchemas = (machine: Machine.Any): MachineProtocolSchemas => {
|
|
62
|
+
const protocol = (machine as any)[MachineProtocolTypeId] as MachineProtocolSchemas | undefined
|
|
63
|
+
if (protocol === undefined) {
|
|
64
|
+
throw new Error("Machine protocol is unavailable")
|
|
65
|
+
}
|
|
66
|
+
return protocol
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const setProtocolSchemas = (machine: Machine.Any, protocol: MachineProtocolSchemas): void => {
|
|
70
|
+
Object.defineProperty(machine, MachineProtocolTypeId, {
|
|
71
|
+
value: protocol,
|
|
72
|
+
enumerable: false
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const collectEventConstructors = (
|
|
77
|
+
schemas: ReadonlyArray<Machine.TaggedSchema>
|
|
78
|
+
): ReadonlySet<object> => {
|
|
79
|
+
const constructors = new Set<object>()
|
|
80
|
+
const add = (schema: Machine.TaggedSchema): void => {
|
|
81
|
+
const key = schema as object
|
|
82
|
+
if (constructors.has(key)) return
|
|
83
|
+
constructors.add(key)
|
|
84
|
+
if (!hasProperty(schema, "cases") || typeof schema.cases !== "object" || schema.cases === null) return
|
|
85
|
+
for (const candidate of Object.values(schema.cases)) {
|
|
86
|
+
if (
|
|
87
|
+
((typeof candidate === "object" && candidate !== null) || typeof candidate === "function") &&
|
|
88
|
+
hasProperty(candidate, "make")
|
|
89
|
+
) {
|
|
90
|
+
add(candidate as Machine.TaggedSchema)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const schema of schemas) add(schema)
|
|
95
|
+
return constructors
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const setProtocol = (machine: Machine.Any): void => {
|
|
99
|
+
const events = [...machine.events, ...machine.internalEvents]
|
|
100
|
+
setProtocolSchemas(machine, {
|
|
101
|
+
event: Schema.Union(events),
|
|
102
|
+
emit: Schema.Union(machine.emits),
|
|
103
|
+
eventConstructors: collectEventConstructors(events),
|
|
104
|
+
trustedEvents: new WeakSet()
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const copyProtocol = (source: Machine.Any, target: Machine.Any): void =>
|
|
109
|
+
setProtocolSchemas(target, getProtocolSchemas(source))
|
|
110
|
+
|
|
111
|
+
export const getEventName = (event: unknown): string | undefined =>
|
|
112
|
+
hasProperty(event, "_tag") ? String(event._tag) : undefined
|
|
113
|
+
|
|
114
|
+
export const decodeBoundary = <A>(
|
|
115
|
+
machine: Machine.Any,
|
|
116
|
+
schema: Schema.Top,
|
|
117
|
+
value: unknown,
|
|
118
|
+
options: DecodeBoundaryOptions
|
|
119
|
+
): Effect.Effect<A, MachineSchemaDecodeError> =>
|
|
120
|
+
getBoundaryDecoder(schema)(value).pipe(
|
|
121
|
+
Effect.mapError((cause) =>
|
|
122
|
+
new MachineSchemaDecodeError({
|
|
123
|
+
machineId: machine.id,
|
|
124
|
+
boundary: options.boundary,
|
|
125
|
+
cause,
|
|
126
|
+
...(options.state === undefined ? {} : { state: options.state }),
|
|
127
|
+
...(options.event === undefined ? {} : { event: options.event })
|
|
128
|
+
})
|
|
129
|
+
)
|
|
130
|
+
) as Effect.Effect<A, MachineSchemaDecodeError>
|
|
131
|
+
|
|
132
|
+
export const decodeBoundarySync = <A>(
|
|
133
|
+
machine: Machine.Any,
|
|
134
|
+
schema: Schema.Top,
|
|
135
|
+
value: unknown,
|
|
136
|
+
options: DecodeBoundaryOptions
|
|
137
|
+
): A => {
|
|
138
|
+
const decoded = getBoundaryResultDecoder(schema)(value)
|
|
139
|
+
if (Result.isFailure(decoded)) {
|
|
140
|
+
throw new MachineSchemaDecodeError({
|
|
141
|
+
machineId: machine.id,
|
|
142
|
+
boundary: options.boundary,
|
|
143
|
+
cause: decoded.failure,
|
|
144
|
+
...(options.state === undefined ? {} : { state: options.state }),
|
|
145
|
+
...(options.event === undefined ? {} : { event: options.event })
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
return decoded.success as A
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const makeBoundarySync = <A>(
|
|
152
|
+
machine: Machine.Any,
|
|
153
|
+
schema: Schema.Top,
|
|
154
|
+
input: unknown,
|
|
155
|
+
options: DecodeBoundaryOptions
|
|
156
|
+
): A => {
|
|
157
|
+
try {
|
|
158
|
+
return schema.make(input as never) as A
|
|
159
|
+
} catch (cause) {
|
|
160
|
+
const issue = cause instanceof Error ? cause.cause : undefined
|
|
161
|
+
throw new MachineSchemaDecodeError({
|
|
162
|
+
machineId: machine.id,
|
|
163
|
+
boundary: options.boundary,
|
|
164
|
+
cause: Schema.isSchemaError(cause)
|
|
165
|
+
? cause
|
|
166
|
+
: hasProperty(issue, "~effect/SchemaIssue/Issue")
|
|
167
|
+
? new Schema.SchemaError(issue as any)
|
|
168
|
+
: Cause.die(cause),
|
|
169
|
+
...(options.state === undefined ? {} : { state: options.state }),
|
|
170
|
+
...(options.event === undefined ? {} : { event: options.event })
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Constructs an event through one of the machine protocol's own schemas and
|
|
176
|
+
* records the decoded value as trusted by that protocol. Machine clones share
|
|
177
|
+
* the protocol record, while unrelated machines retain independent trust. */
|
|
178
|
+
export const makeEvent = <Schema extends Machine.TaggedSchema>(
|
|
179
|
+
machine: Machine.Any,
|
|
180
|
+
schema: Schema,
|
|
181
|
+
input: unknown
|
|
182
|
+
): Schema["Type"] => {
|
|
183
|
+
const protocol = getProtocolSchemas(machine)
|
|
184
|
+
if (!protocol.eventConstructors.has(schema as object)) {
|
|
185
|
+
throw new Error("Machine.event expected a schema from the machine event protocol")
|
|
186
|
+
}
|
|
187
|
+
const inputName = getEventName(input)
|
|
188
|
+
const event = makeBoundarySync<Schema["Type"]>(
|
|
189
|
+
machine,
|
|
190
|
+
schema,
|
|
191
|
+
input,
|
|
192
|
+
inputName === undefined ? { boundary: "event" } : { boundary: "event", event: inputName }
|
|
193
|
+
)
|
|
194
|
+
protocol.trustedEvents.add(event as object)
|
|
195
|
+
return event
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const isTrustedEvent = (protocol: MachineProtocolSchemas, event: unknown): boolean =>
|
|
199
|
+
typeof event === "object" && event !== null && protocol.trustedEvents.has(event)
|
|
200
|
+
|
|
201
|
+
export const decodeInput = <Input extends Schema.Top>(
|
|
202
|
+
machine: Machine.Any,
|
|
203
|
+
schema: Input,
|
|
204
|
+
value: unknown
|
|
205
|
+
): Effect.Effect<Input["Type"], MachineSchemaDecodeError> =>
|
|
206
|
+
decodeBoundary<Input["Type"]>(machine, schema, value, { boundary: "input" })
|
|
207
|
+
|
|
208
|
+
export const decodeEvent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
209
|
+
machine: Machine.Any,
|
|
210
|
+
event: unknown
|
|
211
|
+
): Effect.Effect<Machine.EventOf<Events>, MachineSchemaDecodeError> => {
|
|
212
|
+
const protocol = getProtocolSchemas(machine)
|
|
213
|
+
if (isTrustedEvent(protocol, event)) {
|
|
214
|
+
return Effect.succeed(event as Machine.EventOf<Events>)
|
|
215
|
+
}
|
|
216
|
+
const eventName = getEventName(event)
|
|
217
|
+
return decodeBoundary<Machine.EventOf<Events>>(
|
|
218
|
+
machine,
|
|
219
|
+
protocol.event,
|
|
220
|
+
event,
|
|
221
|
+
eventName === undefined ? { boundary: "event" } : { boundary: "event", event: eventName }
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const decodeEventSync = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
226
|
+
machine: Machine.Any,
|
|
227
|
+
event: unknown
|
|
228
|
+
): Machine.EventOf<Events> => {
|
|
229
|
+
const protocol = getProtocolSchemas(machine)
|
|
230
|
+
if (isTrustedEvent(protocol, event)) {
|
|
231
|
+
return event as Machine.EventOf<Events>
|
|
232
|
+
}
|
|
233
|
+
const eventName = getEventName(event)
|
|
234
|
+
return decodeBoundarySync<Machine.EventOf<Events>>(
|
|
235
|
+
machine,
|
|
236
|
+
protocol.event,
|
|
237
|
+
event,
|
|
238
|
+
eventName === undefined ? { boundary: "event" } : { boundary: "event", event: eventName }
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const decodeEmit = <const Emits extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
243
|
+
machine: Machine.Any,
|
|
244
|
+
event: unknown
|
|
245
|
+
): Effect.Effect<Machine.EmitOf<Emits>, MachineSchemaDecodeError> => {
|
|
246
|
+
const eventName = getEventName(event)
|
|
247
|
+
return decodeBoundary<Machine.EmitOf<Emits>>(
|
|
248
|
+
machine,
|
|
249
|
+
getProtocolSchemas(machine).emit,
|
|
250
|
+
event,
|
|
251
|
+
eventName === undefined ? { boundary: "emit" } : { boundary: "emit", event: eventName }
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const decodeEmitSync = <const Emits extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
256
|
+
machine: Machine.Any,
|
|
257
|
+
event: unknown
|
|
258
|
+
): Machine.EmitOf<Emits> => {
|
|
259
|
+
const eventName = getEventName(event)
|
|
260
|
+
return decodeBoundarySync<Machine.EmitOf<Emits>>(
|
|
261
|
+
machine,
|
|
262
|
+
getProtocolSchemas(machine).emit,
|
|
263
|
+
event,
|
|
264
|
+
eventName === undefined ? { boundary: "emit" } : { boundary: "emit", event: eventName }
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const decodeInputSync = <Input extends Schema.Top>(
|
|
269
|
+
machine: Machine.Any,
|
|
270
|
+
schema: Input,
|
|
271
|
+
value: unknown
|
|
272
|
+
): Input["Type"] => decodeBoundarySync<Input["Type"]>(machine, schema, value, { boundary: "input" })
|
|
273
|
+
|
|
274
|
+
export const decodeStateValue = (
|
|
275
|
+
machine: Machine.Any,
|
|
276
|
+
node: Machine.StateNode,
|
|
277
|
+
value: unknown
|
|
278
|
+
): Effect.Effect<unknown, MachineSchemaDecodeError> =>
|
|
279
|
+
isStateInput(value)
|
|
280
|
+
? getStateNodeSchema(node).makeEffect(value.input).pipe(
|
|
281
|
+
Effect.mapError((cause) =>
|
|
282
|
+
new MachineSchemaDecodeError({
|
|
283
|
+
machineId: machine.id,
|
|
284
|
+
boundary: "state",
|
|
285
|
+
state: node.path,
|
|
286
|
+
cause: new Schema.SchemaError(cause)
|
|
287
|
+
})
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
: decodeBoundary(machine, getStateNodeSchema(node), value, { boundary: "state", state: node.path })
|
|
291
|
+
|
|
292
|
+
export const decodeStateValueSync = (
|
|
293
|
+
machine: Machine.Any,
|
|
294
|
+
node: Machine.StateNode,
|
|
295
|
+
value: unknown
|
|
296
|
+
): unknown => {
|
|
297
|
+
if (!isStateInput(value)) {
|
|
298
|
+
return decodeBoundarySync(machine, getStateNodeSchema(node), value, { boundary: "state", state: node.path })
|
|
299
|
+
}
|
|
300
|
+
return makeBoundarySync(machine, getStateNodeSchema(node), value.input, {
|
|
301
|
+
boundary: "state",
|
|
302
|
+
state: node.path
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const decodeOutputValue = (
|
|
307
|
+
machine: Machine.Any,
|
|
308
|
+
node: Machine.StateNode,
|
|
309
|
+
value: unknown
|
|
310
|
+
): Effect.Effect<unknown, MachineSchemaDecodeError> =>
|
|
311
|
+
node.output === undefined
|
|
312
|
+
? Effect.succeed(value)
|
|
313
|
+
: decodeBoundary(machine, node.output, value, { boundary: "output", state: node.path })
|
|
314
|
+
|
|
315
|
+
export const decodeOutputValueSync = (
|
|
316
|
+
machine: Machine.Any,
|
|
317
|
+
node: Machine.StateNode,
|
|
318
|
+
value: unknown
|
|
319
|
+
): unknown =>
|
|
320
|
+
node.output === undefined
|
|
321
|
+
? value
|
|
322
|
+
: decodeBoundarySync(machine, node.output, value, { boundary: "output", state: node.path })
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as Machine from "../../Machine.js"
|
|
2
|
+
|
|
3
|
+
/** Canonical proof required before a machine can be planned or executed. */
|
|
4
|
+
export type EnsureExecutable<
|
|
5
|
+
States extends Machine.Machine.StateSchemas,
|
|
6
|
+
UnhandledStates extends Machine.Machine.StateIdentifier<States>,
|
|
7
|
+
OutputStates extends Machine.Machine.StateIdentifier<States>
|
|
8
|
+
> =
|
|
9
|
+
& Machine.Machine.EnsureOutputImplementations<States, OutputStates>
|
|
10
|
+
& Machine.Machine.EnsureHistoryImplementations<States, UnhandledStates>
|