@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.
Files changed (57) hide show
  1. package/README.md +64 -24
  2. package/dist/Machine.d.ts +386 -304
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +66 -171
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +5 -12
  7. package/dist/internal/machine/activities.d.ts.map +1 -1
  8. package/dist/internal/machine/activities.js +47 -17
  9. package/dist/internal/machine/activities.js.map +1 -1
  10. package/dist/internal/machine/atom.d.ts +2 -2
  11. package/dist/internal/machine/atom.d.ts.map +1 -1
  12. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  13. package/dist/internal/machine/executionPlan.js +25 -0
  14. package/dist/internal/machine/executionPlan.js.map +1 -1
  15. package/dist/internal/machine/invocation.d.ts +10 -2
  16. package/dist/internal/machine/invocation.d.ts.map +1 -1
  17. package/dist/internal/machine/invocation.js +93 -34
  18. package/dist/internal/machine/invocation.js.map +1 -1
  19. package/dist/internal/machine/invocationEvent.d.ts +39 -0
  20. package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
  21. package/dist/internal/machine/invocationEvent.js +43 -0
  22. package/dist/internal/machine/invocationEvent.js.map +1 -0
  23. package/dist/internal/machine/machine.d.ts +6 -50
  24. package/dist/internal/machine/machine.d.ts.map +1 -1
  25. package/dist/internal/machine/machine.js +8 -65
  26. package/dist/internal/machine/machine.js.map +1 -1
  27. package/dist/internal/machine/planner.d.ts +2 -1
  28. package/dist/internal/machine/planner.d.ts.map +1 -1
  29. package/dist/internal/machine/planner.js +45 -2
  30. package/dist/internal/machine/planner.js.map +1 -1
  31. package/dist/internal/machine/protocol.d.ts +9 -0
  32. package/dist/internal/machine/protocol.d.ts.map +1 -1
  33. package/dist/internal/machine/protocol.js +114 -0
  34. package/dist/internal/machine/protocol.js.map +1 -1
  35. package/dist/internal/machine/symbols.d.ts +2 -0
  36. package/dist/internal/machine/symbols.d.ts.map +1 -1
  37. package/dist/internal/machine/symbols.js +2 -0
  38. package/dist/internal/machine/symbols.js.map +1 -1
  39. package/dist/internal/machine/topology.d.ts.map +1 -1
  40. package/dist/internal/machine/topology.js +23 -0
  41. package/dist/internal/machine/topology.js.map +1 -1
  42. package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
  43. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  44. package/docs/agent-guide.md +118 -93
  45. package/package.json +2 -2
  46. package/src/Machine.ts +1040 -651
  47. package/src/internal/machine/activities.ts +48 -33
  48. package/src/internal/machine/atom.ts +3 -3
  49. package/src/internal/machine/executionPlan.ts +29 -0
  50. package/src/internal/machine/invocation.ts +179 -48
  51. package/src/internal/machine/invocationEvent.ts +72 -0
  52. package/src/internal/machine/machine.ts +20 -346
  53. package/src/internal/machine/planner.ts +61 -10
  54. package/src/internal/machine/protocol.ts +149 -0
  55. package/src/internal/machine/symbols.ts +3 -0
  56. package/src/internal/machine/topology.ts +23 -0
  57. 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.EventOf<InputEvents>,
664
+ Machine.Machine.EventInputOf<InputEvents>,
665
665
  MachineRuntimeError<E, R>,
666
666
  Output,
667
667
  MachineStartError<InitialE, E, InitialR, R>