@typeonce/effect-machine 0.8.0 → 0.9.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 +64 -24
- package/dist/Machine.d.ts +386 -304
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +66 -171
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +5 -12
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +47 -17
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +2 -2
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +25 -0
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +10 -2
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +93 -34
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +39 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
- package/dist/internal/machine/invocationEvent.js +43 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +6 -50
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +8 -65
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +2 -1
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +45 -2
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +9 -0
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +114 -0
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/symbols.d.ts +2 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -1
- package/dist/internal/machine/symbols.js +2 -0
- package/dist/internal/machine/symbols.js.map +1 -1
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +23 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/docs/agent-guide.md +118 -93
- package/package.json +2 -2
- package/src/Machine.ts +1040 -651
- package/src/internal/machine/activities.ts +48 -33
- package/src/internal/machine/atom.ts +3 -3
- package/src/internal/machine/executionPlan.ts +29 -0
- package/src/internal/machine/invocation.ts +179 -48
- package/src/internal/machine/invocationEvent.ts +72 -0
- package/src/internal/machine/machine.ts +20 -346
- package/src/internal/machine/planner.ts +61 -10
- package/src/internal/machine/protocol.ts +149 -0
- package/src/internal/machine/symbols.ts +3 -0
- package/src/internal/machine/topology.ts +23 -0
- package/src/unstable/reactivity/AtomMachine.ts +3 -3
|
@@ -9,6 +9,7 @@ import * as Effect from "effect/Effect"
|
|
|
9
9
|
import { hasProperty } from "effect/Predicate"
|
|
10
10
|
import * as Result from "effect/Result"
|
|
11
11
|
import * as Schema from "effect/Schema"
|
|
12
|
+
import * as SchemaAST from "effect/SchemaAST"
|
|
12
13
|
import type { Machine } from "../../Machine.js"
|
|
13
14
|
import { MachineSchemaDecodeError } from "./errors.js"
|
|
14
15
|
import { getStateNodeSchema, isStateInput } from "./topology.js"
|
|
@@ -26,6 +27,15 @@ interface MachineProtocolSchemas {
|
|
|
26
27
|
readonly trustedEvents: WeakSet<object>
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
export const EventConstructionTypeId: unique symbol = Symbol("effect/Machine/EventConstruction")
|
|
31
|
+
|
|
32
|
+
interface EventConstruction {
|
|
33
|
+
readonly [EventConstructionTypeId]: typeof EventConstructionTypeId
|
|
34
|
+
readonly _tag: PropertyKey
|
|
35
|
+
readonly schema: Machine.TaggedSchema
|
|
36
|
+
readonly input: unknown
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
type BoundaryDecoder = (value: unknown) => Effect.Effect<unknown, Schema.SchemaError, unknown>
|
|
30
40
|
|
|
31
41
|
type BoundaryResultDecoder = (value: unknown) => Result.Result<unknown, Schema.SchemaError>
|
|
@@ -111,6 +121,88 @@ export const copyProtocol = (source: Machine.Any, target: Machine.Any): void =>
|
|
|
111
121
|
export const getEventName = (event: unknown): string | undefined =>
|
|
112
122
|
hasProperty(event, "_tag") ? String(event._tag) : undefined
|
|
113
123
|
|
|
124
|
+
const makeEventConstruction = (
|
|
125
|
+
schema: Machine.TaggedSchema,
|
|
126
|
+
tag: PropertyKey,
|
|
127
|
+
input: unknown
|
|
128
|
+
): EventConstruction => ({
|
|
129
|
+
[EventConstructionTypeId]: EventConstructionTypeId,
|
|
130
|
+
_tag: tag,
|
|
131
|
+
schema,
|
|
132
|
+
input
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const isEventConstruction = (value: unknown): value is EventConstruction => hasProperty(value, EventConstructionTypeId)
|
|
136
|
+
|
|
137
|
+
export const eventConstructors = (
|
|
138
|
+
schemas: ReadonlyArray<Machine.TaggedSchema>
|
|
139
|
+
): Readonly<Record<PropertyKey, (...args: ReadonlyArray<unknown>) => EventConstruction>> => {
|
|
140
|
+
const leaves: Array<Machine.TaggedSchema> = []
|
|
141
|
+
const collect = (schema: Machine.TaggedSchema): void => {
|
|
142
|
+
if (hasProperty(schema, "cases") && typeof schema.cases === "object" && schema.cases !== null) {
|
|
143
|
+
for (const candidate of Reflect.ownKeys(schema.cases)) {
|
|
144
|
+
collect(Reflect.get(schema.cases, candidate) as Machine.TaggedSchema)
|
|
145
|
+
}
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
if (hasProperty(schema, "members") && Array.isArray(schema.members)) {
|
|
149
|
+
for (const member of schema.members) collect(member as Machine.TaggedSchema)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
leaves.push(schema)
|
|
153
|
+
}
|
|
154
|
+
for (const schema of schemas) collect(schema)
|
|
155
|
+
const constructors = Object.create(null) as Record<
|
|
156
|
+
PropertyKey,
|
|
157
|
+
(...args: ReadonlyArray<unknown>) => EventConstruction
|
|
158
|
+
>
|
|
159
|
+
const tags = (ast: SchemaAST.AST): ReadonlyArray<PropertyKey> => {
|
|
160
|
+
if (SchemaAST.isLiteral(ast)) {
|
|
161
|
+
return typeof ast.literal === "string" || typeof ast.literal === "number" ? [ast.literal] : []
|
|
162
|
+
}
|
|
163
|
+
if (SchemaAST.isUniqueSymbol(ast)) return [ast.symbol]
|
|
164
|
+
if (SchemaAST.isEnum(ast)) return ast.enums.map(([, value]) => value)
|
|
165
|
+
if (SchemaAST.isUnion(ast)) return ast.types.flatMap(tags)
|
|
166
|
+
if (SchemaAST.isSuspend(ast)) return tags(ast.thunk())
|
|
167
|
+
return []
|
|
168
|
+
}
|
|
169
|
+
const schemaTags = (schema: Machine.TaggedSchema): ReadonlyArray<PropertyKey> => {
|
|
170
|
+
const ast = SchemaAST.toType(schema.ast)
|
|
171
|
+
if (SchemaAST.isObjects(ast)) {
|
|
172
|
+
const tag = ast.propertySignatures.find(({ name }) => name === "_tag")
|
|
173
|
+
const discriminants = tag === undefined ? [] : tags(tag.type)
|
|
174
|
+
if (discriminants.length > 0) return discriminants
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
return Schema.Union([schema]).pipe(Schema.toTaggedUnion("_tag")).discriminants
|
|
178
|
+
} catch {
|
|
179
|
+
return []
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const schema of leaves) {
|
|
183
|
+
const discriminants = schemaTags(schema)
|
|
184
|
+
if (discriminants.length === 0) {
|
|
185
|
+
throw new Error("Machine event constructors require finite literal or unique symbol event tags")
|
|
186
|
+
}
|
|
187
|
+
for (const tag of discriminants) {
|
|
188
|
+
if (hasProperty(constructors, tag)) {
|
|
189
|
+
throw new Error(`Duplicate machine event constructor tag: ${String(tag)}`)
|
|
190
|
+
}
|
|
191
|
+
Object.defineProperty(constructors, tag, {
|
|
192
|
+
value: (...args: ReadonlyArray<unknown>) => {
|
|
193
|
+
const fields = args.length === 0 ? {} : args[0]
|
|
194
|
+
const input = typeof fields === "object" && fields !== null
|
|
195
|
+
? { ...fields, _tag: tag }
|
|
196
|
+
: { _tag: tag }
|
|
197
|
+
return makeEventConstruction(schema, tag, input)
|
|
198
|
+
},
|
|
199
|
+
enumerable: true
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return constructors
|
|
204
|
+
}
|
|
205
|
+
|
|
114
206
|
export const decodeBoundary = <A>(
|
|
115
207
|
machine: Machine.Any,
|
|
116
208
|
schema: Schema.Top,
|
|
@@ -195,6 +287,54 @@ export const makeEvent = <Schema extends Machine.TaggedSchema>(
|
|
|
195
287
|
return event
|
|
196
288
|
}
|
|
197
289
|
|
|
290
|
+
const eventConstructionProtocolError = (
|
|
291
|
+
machine: Machine.Any,
|
|
292
|
+
construction: EventConstruction
|
|
293
|
+
): MachineSchemaDecodeError =>
|
|
294
|
+
new MachineSchemaDecodeError({
|
|
295
|
+
machineId: machine.id,
|
|
296
|
+
boundary: "event",
|
|
297
|
+
event: String(construction._tag),
|
|
298
|
+
cause: Cause.die(new Error("Constructed event schema does not belong to the machine event protocol"))
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const decodeEventConstruction = (
|
|
302
|
+
machine: Machine.Any,
|
|
303
|
+
protocol: MachineProtocolSchemas,
|
|
304
|
+
construction: EventConstruction
|
|
305
|
+
): Effect.Effect<unknown, MachineSchemaDecodeError> => {
|
|
306
|
+
if (!protocol.eventConstructors.has(construction.schema as object)) {
|
|
307
|
+
return Effect.fail(eventConstructionProtocolError(machine, construction))
|
|
308
|
+
}
|
|
309
|
+
return construction.schema.makeEffect(construction.input as never).pipe(
|
|
310
|
+
Effect.mapError((cause) =>
|
|
311
|
+
new MachineSchemaDecodeError({
|
|
312
|
+
machineId: machine.id,
|
|
313
|
+
boundary: "event",
|
|
314
|
+
event: String(construction._tag),
|
|
315
|
+
cause: new Schema.SchemaError(cause)
|
|
316
|
+
})
|
|
317
|
+
),
|
|
318
|
+
Effect.tap((event) => Effect.sync(() => protocol.trustedEvents.add(event as object)))
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const decodeEventConstructionSync = (
|
|
323
|
+
machine: Machine.Any,
|
|
324
|
+
protocol: MachineProtocolSchemas,
|
|
325
|
+
construction: EventConstruction
|
|
326
|
+
): unknown => {
|
|
327
|
+
if (!protocol.eventConstructors.has(construction.schema as object)) {
|
|
328
|
+
throw eventConstructionProtocolError(machine, construction)
|
|
329
|
+
}
|
|
330
|
+
const event = makeBoundarySync(machine, construction.schema, construction.input, {
|
|
331
|
+
boundary: "event",
|
|
332
|
+
event: String(construction._tag)
|
|
333
|
+
})
|
|
334
|
+
protocol.trustedEvents.add(event as object)
|
|
335
|
+
return event
|
|
336
|
+
}
|
|
337
|
+
|
|
198
338
|
const isTrustedEvent = (protocol: MachineProtocolSchemas, event: unknown): boolean =>
|
|
199
339
|
typeof event === "object" && event !== null && protocol.trustedEvents.has(event)
|
|
200
340
|
|
|
@@ -210,6 +350,12 @@ export const decodeEvent = <const Events extends ReadonlyArray<Machine.TaggedSch
|
|
|
210
350
|
event: unknown
|
|
211
351
|
): Effect.Effect<Machine.EventOf<Events>, MachineSchemaDecodeError> => {
|
|
212
352
|
const protocol = getProtocolSchemas(machine)
|
|
353
|
+
if (isEventConstruction(event)) {
|
|
354
|
+
return decodeEventConstruction(machine, protocol, event) as Effect.Effect<
|
|
355
|
+
Machine.EventOf<Events>,
|
|
356
|
+
MachineSchemaDecodeError
|
|
357
|
+
>
|
|
358
|
+
}
|
|
213
359
|
if (isTrustedEvent(protocol, event)) {
|
|
214
360
|
return Effect.succeed(event as Machine.EventOf<Events>)
|
|
215
361
|
}
|
|
@@ -227,6 +373,9 @@ export const decodeEventSync = <const Events extends ReadonlyArray<Machine.Tagge
|
|
|
227
373
|
event: unknown
|
|
228
374
|
): Machine.EventOf<Events> => {
|
|
229
375
|
const protocol = getProtocolSchemas(machine)
|
|
376
|
+
if (isEventConstruction(event)) {
|
|
377
|
+
return decodeEventConstructionSync(machine, protocol, event) as Machine.EventOf<Events>
|
|
378
|
+
}
|
|
230
379
|
if (isTrustedEvent(protocol, event)) {
|
|
231
380
|
return event as Machine.EventOf<Events>
|
|
232
381
|
}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export const InitialEventTypeId: unique symbol = Symbol("effect/Machine/InitialEvent")
|
|
3
|
+
|
|
4
|
+
/** @internal Returns process logic for a child descriptor and optional input. */
|
|
5
|
+
export const ChildMachineLogicTypeId: unique symbol = Symbol("effect/Machine/ChildMachineLogic")
|
|
@@ -392,6 +392,29 @@ export const transitionDefinitions = (
|
|
|
392
392
|
targets: transitionTargets(config.onDone)
|
|
393
393
|
})
|
|
394
394
|
}
|
|
395
|
+
const invokes = config.invoke === undefined
|
|
396
|
+
? []
|
|
397
|
+
: Array.isArray(config.invoke)
|
|
398
|
+
? config.invoke
|
|
399
|
+
: [config.invoke]
|
|
400
|
+
for (const invoke of invokes) {
|
|
401
|
+
const id = "child" in invoke ? String(invoke.child.id) : String(invoke.id)
|
|
402
|
+
for (const outcome of ["done", "failure", "snapshot"] as const) {
|
|
403
|
+
const handler = outcome === "done"
|
|
404
|
+
? invoke.onDone
|
|
405
|
+
: outcome === "failure"
|
|
406
|
+
? invoke.onFailure
|
|
407
|
+
: invoke.onSnapshot
|
|
408
|
+
if (handler !== undefined) {
|
|
409
|
+
definitions.push({
|
|
410
|
+
source: node.path,
|
|
411
|
+
trigger: { type: "invoke", id, outcome },
|
|
412
|
+
reenter: typeof handler === "object" && handler !== null && handler.reenter === true,
|
|
413
|
+
targets: transitionTargets(handler)
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
395
418
|
}
|
|
396
419
|
return definitions
|
|
397
420
|
}
|
|
@@ -537,7 +537,7 @@ type MachineInputArgsOf<M extends Machine.Machine.Any> = [
|
|
|
537
537
|
|
|
538
538
|
type MachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
|
|
539
539
|
Machine.Machine.Snapshot<Machine.Machine.States<M>>,
|
|
540
|
-
Machine.Machine.InputEvent<M
|
|
540
|
+
Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
|
|
541
541
|
MachineRuntimeError<Machine.Machine.Error<M>, Machine.Machine.Services<M>>,
|
|
542
542
|
Machine.Machine.Output<M>,
|
|
543
543
|
MachineStartError<
|
|
@@ -551,7 +551,7 @@ type MachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
|
|
|
551
551
|
|
|
552
552
|
type ResumedMachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
|
|
553
553
|
Machine.Machine.Snapshot<Machine.Machine.States<M>>,
|
|
554
|
-
Machine.Machine.InputEvent<M
|
|
554
|
+
Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
|
|
555
555
|
MachineRuntimeError<Machine.Machine.Error<M>, Machine.Machine.Services<M>>,
|
|
556
556
|
Machine.Machine.Output<M>,
|
|
557
557
|
Machine.MachineSchemaDecodeError | RuntimeError
|
|
@@ -661,7 +661,7 @@ export const make: {
|
|
|
661
661
|
...args: [...Machine.Machine.InputArgs<Input>]
|
|
662
662
|
): MachineAtom<
|
|
663
663
|
Machine.Machine.Snapshot<States>,
|
|
664
|
-
Machine.Machine.
|
|
664
|
+
Machine.Machine.EventInputOf<InputEvents>,
|
|
665
665
|
MachineRuntimeError<E, R>,
|
|
666
666
|
Output,
|
|
667
667
|
MachineStartError<InitialE, E, InitialR, R>
|