effect-machine 0.12.0 → 0.14.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 (53) hide show
  1. package/README.md +133 -324
  2. package/dist/actor.d.ts +46 -28
  3. package/dist/actor.js +276 -315
  4. package/dist/cluster/entity-machine.d.ts +1 -1
  5. package/dist/cluster/entity-machine.js +20 -9
  6. package/dist/cluster/to-entity.d.ts +3 -3
  7. package/dist/errors.d.ts +24 -20
  8. package/dist/errors.js +10 -6
  9. package/dist/index.d.ts +5 -4
  10. package/dist/index.js +3 -2
  11. package/dist/internal/runtime.d.ts +82 -7
  12. package/dist/internal/runtime.js +162 -58
  13. package/dist/internal/transition.d.ts +12 -11
  14. package/dist/internal/transition.js +12 -14
  15. package/dist/machine.d.ts +148 -140
  16. package/dist/machine.js +141 -155
  17. package/dist/schema.d.ts +14 -0
  18. package/dist/schema.js +10 -1
  19. package/dist/slot.d.ts +112 -86
  20. package/dist/slot.js +92 -59
  21. package/dist/supervision.d.ts +97 -0
  22. package/dist/supervision.js +42 -0
  23. package/dist/testing.d.ts +21 -12
  24. package/dist/testing.js +23 -26
  25. package/package.json +7 -7
  26. package/v3/dist/actor.d.ts +53 -30
  27. package/v3/dist/actor.js +286 -311
  28. package/v3/dist/cluster/entity-machine.d.ts +1 -1
  29. package/v3/dist/cluster/entity-machine.js +5 -5
  30. package/v3/dist/cluster/to-entity.d.ts +1 -1
  31. package/v3/dist/errors.d.ts +14 -10
  32. package/v3/dist/errors.js +11 -7
  33. package/v3/dist/index.d.ts +6 -5
  34. package/v3/dist/index.js +3 -2
  35. package/v3/dist/inspection.d.ts +3 -22
  36. package/v3/dist/inspection.js +1 -15
  37. package/v3/dist/internal/brands.d.ts +4 -8
  38. package/v3/dist/internal/inspection.js +1 -1
  39. package/v3/dist/internal/runtime.d.ts +87 -10
  40. package/v3/dist/internal/runtime.js +177 -61
  41. package/v3/dist/internal/transition.d.ts +13 -12
  42. package/v3/dist/internal/transition.js +14 -16
  43. package/v3/dist/internal/utils.js +5 -1
  44. package/v3/dist/machine.d.ts +158 -143
  45. package/v3/dist/machine.js +148 -155
  46. package/v3/dist/schema.d.ts +25 -11
  47. package/v3/dist/schema.js +18 -5
  48. package/v3/dist/slot.d.ts +112 -86
  49. package/v3/dist/slot.js +92 -59
  50. package/v3/dist/supervision.d.ts +97 -0
  51. package/v3/dist/supervision.js +42 -0
  52. package/v3/dist/testing.d.ts +21 -12
  53. package/v3/dist/testing.js +23 -24
package/dist/slot.d.ts CHANGED
@@ -4,42 +4,120 @@ import { Effect, Schema, ServiceMap } from "effect";
4
4
  //#region src/slot.d.ts
5
5
  /** Schema fields definition (like Schema.Struct.Fields) */
6
6
  type Fields = Record<string, Schema.Top>;
7
- /** Extract the encoded type from schema fields (used for parameters) */
7
+ /** Extract the type from schema fields (used for parameters) */
8
8
  type FieldsToParams<F extends Fields> = keyof F extends never ? void : Schema.Schema.Type<Schema.Struct<F>>;
9
9
  /**
10
- * A guard slot - callable function that returns Effect<boolean>.
11
- */
12
- interface GuardSlot<Name extends string, Params> {
13
- readonly _tag: "GuardSlot";
14
- readonly name: Name;
15
- (params: Params): Effect.Effect<boolean>;
10
+ * Definition of a single slot function.
11
+ * Created via `Slot.fn(params, returnSchema?)`.
12
+ *
13
+ * Carries both type-level information and materialized schemas
14
+ * for runtime validation and serialization.
15
+ */
16
+ interface SlotFnDef<F extends Fields = Fields, Return = void> {
17
+ readonly _tag: "SlotFnDef";
18
+ readonly fields: F;
19
+ /** Return schema — undefined means void */
20
+ readonly returnSchema: Schema.Schema<Return> | undefined;
21
+ /** Materialized input schema (Schema.Struct of fields, or Schema.Void for empty) */
22
+ readonly inputSchema: Schema.Codec<FieldsToParams<F>>;
23
+ /** Materialized output schema (returnSchema or Schema.Void) */
24
+ readonly outputSchema: Schema.Codec<Return>;
16
25
  }
17
26
  /**
18
- * An effect slot - callable function that returns Effect<void>.
27
+ * Define a single slot function with parameter schema and optional return schema.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * // Guard-like: returns boolean
32
+ * Slot.fn({ max: Schema.Number }, Schema.Boolean)
33
+ *
34
+ * // Effect-like: returns void (default)
35
+ * Slot.fn({ url: Schema.String })
36
+ *
37
+ * // No params, returns boolean
38
+ * Slot.fn({}, Schema.Boolean)
39
+ * ```
19
40
  */
20
- interface EffectSlot<Name extends string, Params> {
21
- readonly _tag: "EffectSlot";
22
- readonly name: Name;
23
- (params: Params): Effect.Effect<void>;
24
- }
41
+ declare const fn: {
42
+ <F extends Fields, Return>(fields: F, returnSchema: Schema.Schema<Return>): SlotFnDef<F, Return>;
43
+ <F extends Fields>(fields: F): SlotFnDef<F, void>;
44
+ };
45
+ /**
46
+ * Record of slot definitions. Keys are slot names, values are SlotFnDef.
47
+ */
48
+ type SlotsDef = Record<string, SlotFnDef<Fields, unknown>>;
25
49
  /**
26
- * Guard definition - name to schema fields mapping
50
+ * Slots schema returned by `Slot.define()`. Passed to `Machine.make({ slots })`.
27
51
  */
28
- type GuardsDef = Record<string, Fields>;
52
+ interface SlotsSchema<D extends SlotsDef> {
53
+ readonly _tag: "SlotsSchema";
54
+ readonly definitions: D;
55
+ /** Schema for slot requests `{ _tag: "SlotRequest", name, params }`. For RPC request payloads. */
56
+ readonly requestSchema: Schema.Codec<SlotRequest<D>>;
57
+ /** Schema for slot results `{ _tag: "SlotResult", name, result }`. For RPC response payloads. */
58
+ readonly resultSchema: Schema.Codec<SlotResult<D>>;
59
+ /** Schema for slot invocations `{ _tag: "SlotInvocation", name, params, result }`. For persistence/logging. */
60
+ readonly invocationSchema: Schema.Codec<SlotInvocation<D>>;
61
+ /** Create callable slot proxies (used by Machine internally) */
62
+ readonly _createSlots: (resolve: <N extends keyof D & string>(name: N, params: SlotParams<D[N]>) => Effect.Effect<SlotReturn<D[N]>>) => SlotCalls<D>;
63
+ }
29
64
  /**
30
- * Effect definition - name to schema fields mapping
65
+ * A serialized slot request — captures name and params (no result).
66
+ * Used for RPC request payloads.
67
+ */
68
+ type SlotRequest<D extends SlotsDef> = { readonly [K in keyof D & string]: {
69
+ readonly _tag: "SlotRequest";
70
+ readonly name: K;
71
+ readonly params: SlotParams<D[K]>;
72
+ } }[keyof D & string];
73
+ /**
74
+ * A serialized slot result — captures name and result (no params).
75
+ * Used for RPC response payloads.
76
+ */
77
+ type SlotResult<D extends SlotsDef> = { readonly [K in keyof D & string]: {
78
+ readonly _tag: "SlotResult";
79
+ readonly name: K;
80
+ readonly result: SlotReturn<D[K]>;
81
+ } }[keyof D & string];
82
+ /**
83
+ * A serialized slot invocation — captures name, params, and result.
84
+ * Used for persistence, logging, and audit trails.
85
+ */
86
+ type SlotInvocation<D extends SlotsDef> = { readonly [K in keyof D & string]: {
87
+ readonly _tag: "SlotInvocation";
88
+ readonly name: K;
89
+ readonly params: SlotParams<D[K]>;
90
+ readonly result: SlotReturn<D[K]>;
91
+ } }[keyof D & string];
92
+ /** Extract params type from a SlotFnDef */
93
+ type SlotParams<D extends SlotFnDef<Fields, unknown>> = D extends SlotFnDef<infer F, unknown> ? FieldsToParams<F> : never;
94
+ /** Extract return type from a SlotFnDef */
95
+ type SlotReturn<D extends SlotFnDef<Fields, unknown>> = D extends SlotFnDef<Fields, infer R> ? R : never;
96
+ /**
97
+ * A callable slot — function that takes params and returns Effect<Return>.
98
+ */
99
+ interface SlotCall<Name extends string, Params, Return> {
100
+ readonly _tag: "Slot";
101
+ readonly name: Name;
102
+ (params: Params): Effect.Effect<Return>;
103
+ }
104
+ /**
105
+ * Convert slot definitions to callable slot proxies.
31
106
  */
32
- type EffectsDef = Record<string, Fields>;
107
+ type SlotCalls<D extends SlotsDef> = { readonly [K in keyof D & string]: SlotCall<K, SlotParams<D[K]>, SlotReturn<D[K]>> };
33
108
  /**
34
- * Convert guard definitions to callable guard slots
109
+ * Slot handler implementation.
110
+ * Receives only params — use `yield* machine.Context` for machine context.
35
111
  */
36
- type GuardSlots<D extends GuardsDef> = { readonly [K in keyof D & string]: GuardSlot<K, FieldsToParams<D[K]>> };
112
+ type SlotHandler<Params, Return, R = never> = (params: Params) => Return | Effect.Effect<Return, never, R>;
37
113
  /**
38
- * Convert effect definitions to callable effect slots
114
+ * Handler implementations for all slots in a definition.
39
115
  */
40
- type EffectSlots<D extends EffectsDef> = { readonly [K in keyof D & string]: EffectSlot<K, FieldsToParams<D[K]>> };
116
+ type ProvideSlots<D extends SlotsDef, R = never> = { readonly [K in keyof D & string]: SlotHandler<SlotParams<D[K]>, SlotReturn<D[K]>, R> };
117
+ /** Check if a SlotsDef has any actual keys */
118
+ type HasSlotKeys<SD extends SlotsDef> = [keyof SD] extends [never] ? false : SD extends Record<string, never> ? false : true;
41
119
  /**
42
- * Type for machine context - state, event, and self reference.
120
+ * Type for machine context state, event, and self reference.
43
121
  * Shared across all machines via MachineContextTag.
44
122
  */
45
123
  interface MachineContext<State, Event, Self> {
@@ -56,76 +134,24 @@ interface MachineContext<State, Event, Self> {
56
134
  */
57
135
  declare const MachineContextTag: ServiceMap.Service<MachineContext<any, any, any>, MachineContext<any, any, any>>;
58
136
  /**
59
- * Guard handler implementation.
60
- * Receives params and context, returns Effect<boolean>.
61
- */
62
- type GuardHandler<Params, Ctx, R = never> = (params: Params, ctx: Ctx) => boolean | Effect.Effect<boolean, never, R>;
63
- /**
64
- * Effect handler implementation.
65
- * Receives params and context, returns Effect<void>.
66
- */
67
- type EffectHandler<Params, Ctx, R = never> = (params: Params, ctx: Ctx) => Effect.Effect<void, never, R>;
68
- /**
69
- * Handler types for all guards in a definition
70
- */
71
- type GuardHandlers<D extends GuardsDef, MachineCtx, R = never> = { readonly [K in keyof D & string]: GuardHandler<FieldsToParams<D[K]>, MachineCtx, R> };
72
- /**
73
- * Handler types for all effects in a definition
74
- */
75
- type EffectHandlers<D extends EffectsDef, MachineCtx, R = never> = { readonly [K in keyof D & string]: EffectHandler<FieldsToParams<D[K]>, MachineCtx, R> };
76
- /**
77
- * Guards schema - returned by Slot.Guards()
78
- */
79
- interface GuardsSchema<D extends GuardsDef> {
80
- readonly _tag: "GuardsSchema";
81
- readonly definitions: D;
82
- /** Create callable guard slots (used by Machine internally) */
83
- readonly _createSlots: (resolve: <N extends keyof D & string>(name: N, params: FieldsToParams<D[N]>) => Effect.Effect<boolean>) => GuardSlots<D>;
84
- }
85
- /**
86
- * Effects schema - returned by Slot.Effects()
87
- */
88
- interface EffectsSchema<D extends EffectsDef> {
89
- readonly _tag: "EffectsSchema";
90
- readonly definitions: D;
91
- /** Create callable effect slots (used by Machine internally) */
92
- readonly _createSlots: (resolve: <N extends keyof D & string>(name: N, params: FieldsToParams<D[N]>) => Effect.Effect<void>) => EffectSlots<D>;
93
- }
94
- /**
95
- * Create a guards schema with parameterized guard definitions.
96
- *
97
- * @example
98
- * ```ts
99
- * const MyGuards = Slot.Guards({
100
- * canRetry: { max: Schema.Number },
101
- * isValid: {},
102
- * })
103
- * ```
104
- */
105
- declare const Guards: <D extends GuardsDef>(definitions: D) => GuardsSchema<D>;
106
- /**
107
- * Create an effects schema with parameterized effect definitions.
137
+ * Define a set of slots with parameter and return schemas.
108
138
  *
109
139
  * @example
110
140
  * ```ts
111
- * const MyEffects = Slot.Effects({
112
- * fetchData: { url: Schema.String },
113
- * notify: { message: Schema.String },
141
+ * const MySlots = Slot.define({
142
+ * canRetry: Slot.fn({ max: Schema.Number }, Schema.Boolean),
143
+ * fetchData: Slot.fn({ url: Schema.String }),
144
+ * notify: Slot.fn({ message: Schema.String }),
114
145
  * })
115
146
  * ```
116
147
  */
117
- declare const Effects: <D extends EffectsDef>(definitions: D) => EffectsSchema<D>;
118
- /** Extract guard definition type from GuardsSchema */
119
- type GuardsDefOf<G> = G extends GuardsSchema<infer D> ? D : never;
120
- /** Extract effect definition type from EffectsSchema */
121
- type EffectsDefOf<E> = E extends EffectsSchema<infer D> ? D : never;
122
- /** Extract guard slots type from GuardsSchema */
123
- type GuardSlotsOf<G> = G extends GuardsSchema<infer D> ? GuardSlots<D> : never;
124
- /** Extract effect slots type from EffectsSchema */
125
- type EffectSlotsOf<E> = E extends EffectsSchema<infer D> ? EffectSlots<D> : never;
148
+ declare const define: <D extends SlotsDef>(definitions: D) => SlotsSchema<D>;
126
149
  declare const Slot: {
127
- readonly Guards: <D extends GuardsDef>(definitions: D) => GuardsSchema<D>;
128
- readonly Effects: <D extends EffectsDef>(definitions: D) => EffectsSchema<D>;
150
+ readonly fn: {
151
+ <F extends Fields, Return>(fields: F, returnSchema: Schema.Schema<Return>): SlotFnDef<F, Return>;
152
+ <F extends Fields>(fields: F): SlotFnDef<F, void>;
153
+ };
154
+ readonly define: <D extends SlotsDef>(definitions: D) => SlotsSchema<D>;
129
155
  };
130
156
  //#endregion
131
- export { EffectHandler, EffectHandlers, EffectSlot, EffectSlots, EffectSlotsOf, Effects, EffectsDef, EffectsDefOf, EffectsSchema, GuardHandler, GuardHandlers, GuardSlot, GuardSlots, GuardSlotsOf, Guards, GuardsDef, GuardsDefOf, GuardsSchema, MachineContext, MachineContextTag, Slot };
157
+ export { HasSlotKeys, MachineContext, MachineContextTag, ProvideSlots, Slot, SlotCall, SlotCalls, SlotFnDef, SlotHandler, SlotInvocation, SlotRequest, SlotResult, SlotsDef, SlotsSchema, define, fn };
package/dist/slot.js CHANGED
@@ -1,31 +1,29 @@
1
- import { ServiceMap } from "effect";
1
+ import { Schema, ServiceMap } from "effect";
2
2
  //#region src/slot.ts
3
3
  /**
4
- * Slot module - schema-based, parameterized guards and effects.
4
+ * Slot module unified, schema-based parameterized slots.
5
5
  *
6
- * Guards and Effects are defined with schemas for their parameters,
7
- * and provided implementations receive typed parameters plus machine context.
6
+ * Replaces the split Guards/Effects API with a single `Slot.define` + `Slot.fn`.
7
+ * Each slot declares its parameter schema and (optional) return schema.
8
+ * Handlers receive only params — machine context is accessed via `yield* machine.Context`.
8
9
  *
9
10
  * @example
10
11
  * ```ts
11
12
  * import { Slot } from "effect-machine"
12
13
  * import { Schema } from "effect"
13
14
  *
14
- * const MyGuards = Slot.Guards({
15
- * canRetry: { max: Schema.Number },
16
- * isValid: {}, // no params
17
- * })
18
- *
19
- * const MyEffects = Slot.Effects({
20
- * fetchData: { url: Schema.String },
21
- * notify: { message: Schema.String },
15
+ * const MySlots = Slot.define({
16
+ * canRetry: Slot.fn({ max: Schema.Number }, Schema.Boolean),
17
+ * isValid: Slot.fn({}, Schema.Boolean),
18
+ * fetchData: Slot.fn({ url: Schema.String }),
19
+ * notify: Slot.fn({ message: Schema.String }),
22
20
  * })
23
21
  *
24
22
  * // Used in handlers:
25
- * .on(State.X, Event.Y, ({ guards, effects }) =>
23
+ * .on(State.X, Event.Y, ({ slots }) =>
26
24
  * Effect.gen(function* () {
27
- * if (yield* guards.canRetry({ max: 3 })) {
28
- * yield* effects.fetchData({ url: "/api" })
25
+ * if (yield* slots.canRetry({ max: 3 })) {
26
+ * yield* slots.fetchData({ url: "/api" })
29
27
  * return State.Next
30
28
  * }
31
29
  * return state
@@ -36,62 +34,97 @@ import { ServiceMap } from "effect";
36
34
  * @module
37
35
  */
38
36
  /**
39
- * Shared Context tag for all machines.
40
- * Single module-level tag instead of per-machine allocation.
41
- * @internal
42
- */
43
- const MachineContextTag = ServiceMap.Service("@effect-machine/Context");
44
- /**
45
- * Generic slot schema factory. Used internally by Guards() and Effects().
46
- * @internal
47
- */
48
- const createSlotSchema = (tag, slotTag, definitions) => ({
49
- _tag: tag,
50
- definitions,
51
- _createSlots: (resolve) => {
52
- const slots = {};
53
- for (const name of Object.keys(definitions)) {
54
- const slot = (params) => resolve(name, params);
55
- Object.defineProperty(slot, "_tag", {
56
- value: slotTag,
57
- enumerable: true
58
- });
59
- Object.defineProperty(slot, "name", {
60
- value: name,
61
- enumerable: true
62
- });
63
- slots[name] = slot;
64
- }
65
- return slots;
66
- }
67
- });
68
- /**
69
- * Create a guards schema with parameterized guard definitions.
37
+ * Define a single slot function with parameter schema and optional return schema.
70
38
  *
71
39
  * @example
72
40
  * ```ts
73
- * const MyGuards = Slot.Guards({
74
- * canRetry: { max: Schema.Number },
75
- * isValid: {},
76
- * })
41
+ * // Guard-like: returns boolean
42
+ * Slot.fn({ max: Schema.Number }, Schema.Boolean)
43
+ *
44
+ * // Effect-like: returns void (default)
45
+ * Slot.fn({ url: Schema.String })
46
+ *
47
+ * // No params, returns boolean
48
+ * Slot.fn({}, Schema.Boolean)
77
49
  * ```
78
50
  */
79
- const Guards = (definitions) => createSlotSchema("GuardsSchema", "GuardSlot", definitions);
51
+ const fn = (fields, returnSchema) => {
52
+ return {
53
+ _tag: "SlotFnDef",
54
+ fields,
55
+ returnSchema,
56
+ inputSchema: Object.keys(fields).length > 0 ? Schema.Struct(fields) : Schema.Void,
57
+ outputSchema: returnSchema ?? Schema.Void
58
+ };
59
+ };
80
60
  /**
81
- * Create an effects schema with parameterized effect definitions.
61
+ * Shared Context tag for all machines.
62
+ * Single module-level tag instead of per-machine allocation.
63
+ * @internal
64
+ */
65
+ const MachineContextTag = ServiceMap.Service("@effect-machine/Context");
66
+ /**
67
+ * Define a set of slots with parameter and return schemas.
82
68
  *
83
69
  * @example
84
70
  * ```ts
85
- * const MyEffects = Slot.Effects({
86
- * fetchData: { url: Schema.String },
87
- * notify: { message: Schema.String },
71
+ * const MySlots = Slot.define({
72
+ * canRetry: Slot.fn({ max: Schema.Number }, Schema.Boolean),
73
+ * fetchData: Slot.fn({ url: Schema.String }),
74
+ * notify: Slot.fn({ message: Schema.String }),
88
75
  * })
89
76
  * ```
90
77
  */
91
- const Effects = (definitions) => createSlotSchema("EffectsSchema", "EffectSlot", definitions);
78
+ const define = (definitions) => {
79
+ const names = Object.keys(definitions);
80
+ const requestSchemas = [];
81
+ const resultSchemas = [];
82
+ const invocationSchemas = [];
83
+ for (const name of names) {
84
+ const def = definitions[name];
85
+ if (def === void 0) continue;
86
+ requestSchemas.push(Schema.TaggedStruct("SlotRequest", {
87
+ name: Schema.Literal(name),
88
+ params: def.inputSchema
89
+ }));
90
+ resultSchemas.push(Schema.TaggedStruct("SlotResult", {
91
+ name: Schema.Literal(name),
92
+ result: def.outputSchema
93
+ }));
94
+ invocationSchemas.push(Schema.TaggedStruct("SlotInvocation", {
95
+ name: Schema.Literal(name),
96
+ params: def.inputSchema,
97
+ result: def.outputSchema
98
+ }));
99
+ }
100
+ const buildUnion = (schemas) => schemas.length === 0 ? Schema.Never : Schema.Union(schemas);
101
+ return {
102
+ _tag: "SlotsSchema",
103
+ definitions,
104
+ requestSchema: buildUnion(requestSchemas),
105
+ resultSchema: buildUnion(resultSchemas),
106
+ invocationSchema: buildUnion(invocationSchemas),
107
+ _createSlots: (resolve) => {
108
+ const slots = {};
109
+ for (const name of names) {
110
+ const slot = (params) => resolve(name, params);
111
+ Object.defineProperty(slot, "_tag", {
112
+ value: "Slot",
113
+ enumerable: true
114
+ });
115
+ Object.defineProperty(slot, "name", {
116
+ value: name,
117
+ enumerable: true
118
+ });
119
+ slots[name] = slot;
120
+ }
121
+ return slots;
122
+ }
123
+ };
124
+ };
92
125
  const Slot = {
93
- Guards,
94
- Effects
126
+ fn,
127
+ define
95
128
  };
96
129
  //#endregion
97
- export { Effects, Guards, MachineContextTag, Slot };
130
+ export { MachineContextTag, Slot, define, fn };
@@ -0,0 +1,97 @@
1
+ import { Cause, Duration, Schedule } from "effect";
2
+
3
+ //#region src/supervision.d.ts
4
+ /**
5
+ * Where in the actor lifecycle a defect occurred.
6
+ *
7
+ * - `transition` — during event handler execution
8
+ * - `spawn` — during state spawn effect execution
9
+ * - `background` — in a background effect fiber
10
+ * - `initial-spawn` — during initial state spawn effects (before event loop)
11
+ */
12
+ type DefectPhase = "transition" | "spawn" | "background" | "initial-spawn";
13
+ /**
14
+ * Terminal exit reason for an actor generation.
15
+ *
16
+ * - `Final` — machine reached a final state normally
17
+ * - `Stopped` — explicit `actor.stop` or `actor.drain`
18
+ * - `Defect` — unhandled error in the runtime
19
+ */
20
+ type ActorExit<S> = {
21
+ readonly _tag: "Final";
22
+ readonly state: S;
23
+ } | {
24
+ readonly _tag: "Stopped";
25
+ } | {
26
+ readonly _tag: "Defect";
27
+ readonly cause: Cause.Cause<unknown>;
28
+ readonly phase: DefectPhase;
29
+ };
30
+ /** Constructors for ActorExit */
31
+ declare const ActorExit: {
32
+ readonly Final: <S>(state: S) => ActorExit<S>;
33
+ readonly Stopped: ActorExit<never>;
34
+ readonly Defect: <S = never>(cause: Cause.Cause<unknown>, phase: DefectPhase) => ActorExit<S>;
35
+ };
36
+ /**
37
+ * Phase state for supervised actors. Serializes concurrent stop/restart/drain.
38
+ *
39
+ * Transitions:
40
+ * - `Running` → crash → `Restarting` → new runtime → `Running`
41
+ * - `Running` → explicit stop/drain → `Stopping` → `Terminated`
42
+ * - `Restarting` → explicit stop → `Stopping` → `Terminated`
43
+ *
44
+ * @internal
45
+ */
46
+ type CellPhase<S> = {
47
+ readonly _tag: "Running";
48
+ readonly generation: number;
49
+ } | {
50
+ readonly _tag: "Restarting";
51
+ readonly generation: number;
52
+ } | {
53
+ readonly _tag: "Stopping";
54
+ } | {
55
+ readonly _tag: "Terminated";
56
+ readonly exit: ActorExit<S>;
57
+ };
58
+ declare namespace Supervision {
59
+ /**
60
+ * Supervision policy for actor restart behavior.
61
+ *
62
+ * `schedule` controls restart timing and budget — schedule exhaustion means terminal stop.
63
+ * `shouldRestart` optionally classifies defects — return `false` to stop immediately
64
+ * without consuming the schedule.
65
+ */
66
+ interface Policy {
67
+ /** Schedule that controls restart timing. Exhaustion = terminal stop. */
68
+ readonly schedule: Schedule.Schedule<unknown>;
69
+ /**
70
+ * Optional classifier: given a defect exit, decide whether to restart or stop immediately.
71
+ * Default: always restart (let schedule handle budget).
72
+ */
73
+ readonly shouldRestart?: (exit: Extract<ActorExit<unknown>, {
74
+ readonly _tag: "Defect";
75
+ }>) => boolean;
76
+ }
77
+ /** No supervision — crashes are terminal. */
78
+ const none: Policy;
79
+ /**
80
+ * Restart on defect with max restarts within a window, optional backoff.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * Supervision.restart() // unlimited restarts, no backoff
85
+ * Supervision.restart({ maxRestarts: 3 }) // 3 restarts then terminal
86
+ * Supervision.restart({ maxRestarts: 3, within: "1 minute" }) // 3 within 1 min
87
+ * Supervision.restart({ backoff: Schedule.exponential("100 millis") })
88
+ * ```
89
+ */
90
+ const restart: (options?: {
91
+ readonly maxRestarts?: number;
92
+ readonly within?: Duration.Input;
93
+ readonly backoff?: Schedule.Schedule<unknown>;
94
+ }) => Policy;
95
+ }
96
+ //#endregion
97
+ export { ActorExit, CellPhase, DefectPhase, Supervision };
@@ -0,0 +1,42 @@
1
+ import { Schedule } from "effect";
2
+ //#region src/supervision.ts
3
+ /**
4
+ * Supervision types for actor lifecycle management.
5
+ *
6
+ * Core concepts:
7
+ * - `ActorExit<S>` — why an actor stopped (final, explicit stop, or defect)
8
+ * - `DefectPhase` — where in the lifecycle a defect occurred
9
+ * - `Supervision.Policy` — Schedule-based restart policy
10
+ * - `CellPhase<S>` — internal phase machine for serializing stop/restart/drain
11
+ *
12
+ * @module
13
+ */
14
+ /** Constructors for ActorExit */
15
+ const ActorExit = {
16
+ Final: (state) => ({
17
+ _tag: "Final",
18
+ state
19
+ }),
20
+ Stopped: { _tag: "Stopped" },
21
+ Defect: (cause, phase) => ({
22
+ _tag: "Defect",
23
+ cause,
24
+ phase
25
+ })
26
+ };
27
+ let Supervision;
28
+ (function(_Supervision) {
29
+ _Supervision.none = { schedule: Schedule.recurs(0) };
30
+ _Supervision.restart = (options) => {
31
+ let schedule = Schedule.forever;
32
+ if (options?.maxRestarts !== void 0) {
33
+ const recurs = Schedule.recurs(options.maxRestarts);
34
+ if (options.within !== void 0) schedule = Schedule.both(recurs, Schedule.windowed(options.within));
35
+ else schedule = recurs;
36
+ }
37
+ if (options?.backoff !== void 0) schedule = Schedule.both(schedule, options.backoff);
38
+ return { schedule };
39
+ };
40
+ })(Supervision || (Supervision = {}));
41
+ //#endregion
42
+ export { ActorExit, Supervision };
package/dist/testing.d.ts CHANGED
@@ -1,11 +1,10 @@
1
- import { EffectsDef, GuardsDef, MachineContext } from "./slot.js";
2
1
  import { AssertionError } from "./errors.js";
3
- import { BuiltMachine, Machine, MachineRef } from "./machine.js";
2
+ import { MachineContext, ProvideSlots, SlotsDef } from "./slot.js";
3
+ import { Machine, MachineRef } from "./machine.js";
4
4
  import { Effect, SubscriptionRef } from "effect";
5
5
 
6
6
  //#region src/testing.d.ts
7
- /** Accept either Machine or BuiltMachine for testing utilities. */
8
- type MachineInput<S, E, R, GD extends GuardsDef, EFD extends EffectsDef> = Machine<S, E, R, any, any, GD, EFD> | BuiltMachine<S, E, R>;
7
+ type MachineInput<S, E, R, SD extends SlotsDef = Record<string, never>> = Machine<S, E, R, any, any, SD>;
9
8
  /**
10
9
  * Result of simulating events through a machine
11
10
  */
@@ -16,7 +15,7 @@ interface SimulationResult<S> {
16
15
  /**
17
16
  * Simulate a sequence of events through a machine without running an actor.
18
17
  * Useful for testing state transitions in isolation.
19
- * Does not run onEnter/spawn/background effects, but does run guard/effect slots
18
+ * Does not run onEnter/spawn/background effects, but does run slots
20
19
  * within transition handlers.
21
20
  *
22
21
  * @example
@@ -37,7 +36,9 @@ declare const simulate: <S extends {
37
36
  readonly _tag: string;
38
37
  }, E extends {
39
38
  readonly _tag: string;
40
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(input: MachineInput<S, E, R, GD, EFD>, events: readonly E[]) => Effect.Effect<{
39
+ }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], options?: {
40
+ slots?: ProvideSlots<SD, any>;
41
+ } | undefined) => Effect.Effect<{
41
42
  states: S[];
42
43
  finalState: S;
43
44
  }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
@@ -48,7 +49,9 @@ declare const assertReaches: <S extends {
48
49
  readonly _tag: string;
49
50
  }, E extends {
50
51
  readonly _tag: string;
51
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(input: MachineInput<S, E, R, GD, EFD>, events: readonly E[], expectedTag: string) => Effect.Effect<S, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
52
+ }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], expectedTag: string, options?: {
53
+ slots?: ProvideSlots<SD, any>;
54
+ } | undefined) => Effect.Effect<S, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
52
55
  /**
53
56
  * Assert that a machine follows a specific path of state tags
54
57
  *
@@ -65,7 +68,9 @@ declare const assertPath: <S extends {
65
68
  readonly _tag: string;
66
69
  }, E extends {
67
70
  readonly _tag: string;
68
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(input: MachineInput<S, E, R, GD, EFD>, events: readonly E[], expectedPath: readonly string[]) => Effect.Effect<{
71
+ }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], expectedPath: readonly string[], options?: {
72
+ slots?: ProvideSlots<SD, any>;
73
+ } | undefined) => Effect.Effect<{
69
74
  states: S[];
70
75
  finalState: S;
71
76
  }, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
@@ -86,7 +91,9 @@ declare const assertNeverReaches: <S extends {
86
91
  readonly _tag: string;
87
92
  }, E extends {
88
93
  readonly _tag: string;
89
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(input: MachineInput<S, E, R, GD, EFD>, events: readonly E[], forbiddenTag: string) => Effect.Effect<{
94
+ }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], forbiddenTag: string, options?: {
95
+ slots?: ProvideSlots<SD, any>;
96
+ } | undefined) => Effect.Effect<{
90
97
  states: S[];
91
98
  finalState: S;
92
99
  }, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
@@ -101,16 +108,18 @@ interface TestHarness<S, E, R> {
101
108
  /**
102
109
  * Options for creating a test harness
103
110
  */
104
- interface TestHarnessOptions<S, E> {
111
+ interface TestHarnessOptions<S, E, SD extends SlotsDef = Record<string, never>> {
105
112
  /**
106
113
  * Called after each transition with the previous state, event, and new state.
107
114
  * Useful for logging or spying on transitions.
108
115
  */
109
116
  readonly onTransition?: (from: S, event: E, to: S) => void;
117
+ /** Slot handler implementations. */
118
+ readonly slots?: ProvideSlots<SD, any>;
110
119
  }
111
120
  /**
112
121
  * Create a test harness for step-by-step testing.
113
- * Does not run onEnter/spawn/background effects, but does run guard/effect slots
122
+ * Does not run onEnter/spawn/background effects, but does run slots
114
123
  * within transition handlers.
115
124
  *
116
125
  * @example Basic usage
@@ -133,7 +142,7 @@ declare const createTestHarness: <S extends {
133
142
  readonly _tag: string;
134
143
  }, E extends {
135
144
  readonly _tag: string;
136
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(input: MachineInput<S, E, R, GD, EFD>, options?: TestHarnessOptions<S, E> | undefined) => Effect.Effect<{
145
+ }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, options?: TestHarnessOptions<S, E, SD> | undefined) => Effect.Effect<{
137
146
  state: SubscriptionRef.SubscriptionRef<S>;
138
147
  send: (event: E) => Effect.Effect<S, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
139
148
  getState: Effect.Effect<S, never, never>;