@typeonce/effect-machine 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +5 -5
  2. package/src/Machine.ts +6873 -0
  3. package/src/index.ts +1 -0
  4. package/src/internal/machine/activities.ts +108 -0
  5. package/src/internal/machine/atom.ts +636 -0
  6. package/src/internal/machine/cluster.ts +394 -0
  7. package/src/internal/machine/command.ts +58 -0
  8. package/src/internal/machine/commandRuntime.ts +43 -0
  9. package/src/internal/machine/configuration.ts +1331 -0
  10. package/src/internal/machine/errors.ts +87 -0
  11. package/src/internal/machine/executionPlan.ts +996 -0
  12. package/src/internal/machine/invocation.ts +119 -0
  13. package/src/internal/machine/machine.ts +1747 -0
  14. package/src/internal/machine/planner.ts +1933 -0
  15. package/src/internal/machine/process.ts +906 -0
  16. package/src/internal/machine/protocol.ts +322 -0
  17. package/src/internal/machine/readiness.ts +10 -0
  18. package/src/internal/machine/runtime.ts +2512 -0
  19. package/src/internal/machine/serialization.ts +498 -0
  20. package/src/internal/machine/stateDefinition.ts +270 -0
  21. package/src/internal/machine/symbols.ts +2 -0
  22. package/src/internal/machine/topology.ts +479 -0
  23. package/src/internal/testing/machine/arbitrary.ts +102 -0
  24. package/src/internal/testing/machine/exploration.ts +331 -0
  25. package/src/internal/testing/machine/finiteModel.ts +1498 -0
  26. package/src/internal/testing/machine/invariant.ts +372 -0
  27. package/src/internal/testing/machine/probe.ts +79 -0
  28. package/src/internal/testing/machine/referenceModel.ts +1505 -0
  29. package/src/internal/testing/machine/runtime.ts +1710 -0
  30. package/src/internal/testing/machine/runtimeInvariant.ts +486 -0
  31. package/src/internal/testing/machine/trace.ts +150 -0
  32. package/src/internal/testing/machine/verification.ts +1890 -0
  33. package/src/testing/MachineTest.ts +2067 -0
  34. package/src/testing/index.ts +7 -0
  35. package/src/unstable/cluster/ClusterMachine.ts +390 -0
  36. package/src/unstable/cluster/index.ts +1 -0
  37. package/src/unstable/reactivity/AtomMachine.ts +649 -0
  38. 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>