effect-machine 0.13.0 → 0.15.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 +19 -11
- package/dist/actor.d.ts +18 -6
- package/dist/actor.js +151 -61
- package/dist/cluster/entity-machine.d.ts +1 -1
- package/dist/cluster/entity-machine.js +2 -1
- package/dist/cluster/to-entity.d.ts +1 -1
- package/dist/errors.d.ts +9 -2
- package/dist/errors.js +8 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/runtime.d.ts +21 -2
- package/dist/internal/runtime.js +64 -56
- package/dist/internal/transition.d.ts +9 -9
- package/dist/internal/transition.js +3 -5
- package/dist/machine.d.ts +129 -135
- package/dist/machine.js +97 -112
- package/dist/schema.d.ts +17 -1
- package/dist/schema.js +10 -0
- package/dist/slot.d.ts +112 -86
- package/dist/slot.js +92 -59
- package/dist/testing.d.ts +16 -16
- package/dist/testing.js +3 -3
- package/package.json +3 -3
- package/v3/dist/actor.d.ts +25 -12
- package/v3/dist/actor.js +174 -89
- package/v3/dist/cluster/entity-machine.d.ts +1 -1
- package/v3/dist/cluster/entity-machine.js +1 -0
- package/v3/dist/cluster/to-entity.d.ts +1 -1
- package/v3/dist/errors.d.ts +12 -3
- package/v3/dist/errors.js +10 -4
- package/v3/dist/index.d.ts +6 -6
- package/v3/dist/index.js +2 -2
- package/v3/dist/inspection.d.ts +3 -22
- package/v3/dist/inspection.js +1 -15
- package/v3/dist/internal/brands.d.ts +4 -8
- package/v3/dist/internal/inspection.js +1 -1
- package/v3/dist/internal/runtime.d.ts +27 -8
- package/v3/dist/internal/runtime.js +91 -61
- package/v3/dist/internal/transition.d.ts +10 -10
- package/v3/dist/internal/transition.js +8 -10
- package/v3/dist/internal/utils.js +5 -1
- package/v3/dist/machine.d.ts +160 -120
- package/v3/dist/machine.js +118 -115
- package/v3/dist/schema.d.ts +25 -11
- package/v3/dist/schema.js +18 -5
- package/v3/dist/slot.d.ts +112 -86
- package/v3/dist/slot.js +92 -59
- package/v3/dist/testing.d.ts +16 -16
- package/v3/dist/testing.js +7 -7
package/v3/dist/slot.d.ts
CHANGED
|
@@ -4,42 +4,120 @@ import { Context, Effect, Schema } 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.Schema.All>;
|
|
7
|
-
/** Extract the
|
|
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
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.Any | undefined;
|
|
21
|
+
/** Materialized input schema (Schema.Struct of fields, or Schema.Void for empty) */
|
|
22
|
+
readonly inputSchema: Schema.Schema.Any;
|
|
23
|
+
/** Materialized output schema (returnSchema or Schema.Void) */
|
|
24
|
+
readonly outputSchema: Schema.Schema.Any;
|
|
16
25
|
}
|
|
17
26
|
/**
|
|
18
|
-
*
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
declare const fn: {
|
|
42
|
+
<F extends Fields, S extends Schema.Schema.Any>(fields: F, returnSchema: S): SlotFnDef<F, Schema.Schema.Type<S>>;
|
|
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
|
-
*
|
|
50
|
+
* Slots schema — returned by `Slot.define()`. Passed to `Machine.make({ slots })`.
|
|
27
51
|
*/
|
|
28
|
-
|
|
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.Schema<SlotRequest<D>>;
|
|
57
|
+
/** Schema for slot results `{ _tag: "SlotResult", name, result }`. For RPC response payloads. */
|
|
58
|
+
readonly resultSchema: Schema.Schema<SlotResult<D>>;
|
|
59
|
+
/** Schema for slot invocations `{ _tag: "SlotInvocation", name, params, result }`. For persistence/logging. */
|
|
60
|
+
readonly invocationSchema: Schema.Schema<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
|
-
*
|
|
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
|
|
107
|
+
type SlotCalls<D extends SlotsDef> = { readonly [K in keyof D & string]: SlotCall<K, SlotParams<D[K]>, SlotReturn<D[K]>> };
|
|
33
108
|
/**
|
|
34
|
-
*
|
|
109
|
+
* Slot handler implementation.
|
|
110
|
+
* Receives only params — use `yield* machine.Context` for machine context.
|
|
35
111
|
*/
|
|
36
|
-
type
|
|
112
|
+
type SlotHandler<Params, Return, R = never> = (params: Params) => Return | Effect.Effect<Return, never, R>;
|
|
37
113
|
/**
|
|
38
|
-
*
|
|
114
|
+
* Handler implementations for all slots in a definition.
|
|
39
115
|
*/
|
|
40
|
-
type
|
|
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
|
|
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: Context.Tag<MachineContext<any, any, any>, MachineContext<any, any, any>>;
|
|
58
136
|
/**
|
|
59
|
-
*
|
|
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
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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
|
|
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
|
|
128
|
-
|
|
150
|
+
readonly fn: {
|
|
151
|
+
<F extends Fields, S extends Schema.Schema.Any>(fields: F, returnSchema: S): SlotFnDef<F, Schema.Schema.Type<S>>;
|
|
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 {
|
|
157
|
+
export { HasSlotKeys, MachineContext, MachineContextTag, ProvideSlots, Slot, SlotCall, SlotCalls, SlotFnDef, SlotHandler, SlotInvocation, SlotRequest, SlotResult, SlotsDef, SlotsSchema, define, fn };
|
package/v3/dist/slot.js
CHANGED
|
@@ -1,31 +1,29 @@
|
|
|
1
|
-
import { Context } from "effect";
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
2
|
//#region src/slot.ts
|
|
3
3
|
/**
|
|
4
|
-
* Slot module
|
|
4
|
+
* Slot module — unified, schema-based parameterized slots.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
15
|
-
* canRetry: { max: Schema.Number },
|
|
16
|
-
* isValid: {},
|
|
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, ({
|
|
23
|
+
* .on(State.X, Event.Y, ({ slots }) =>
|
|
26
24
|
* Effect.gen(function* () {
|
|
27
|
-
* if (yield*
|
|
28
|
-
* yield*
|
|
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 { Context } from "effect";
|
|
|
36
34
|
* @module
|
|
37
35
|
*/
|
|
38
36
|
/**
|
|
39
|
-
*
|
|
40
|
-
* Single module-level tag instead of per-machine allocation.
|
|
41
|
-
* @internal
|
|
42
|
-
*/
|
|
43
|
-
const MachineContextTag = Context.GenericTag("@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
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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
|
|
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
|
-
*
|
|
61
|
+
* Shared Context tag for all machines.
|
|
62
|
+
* Single module-level tag instead of per-machine allocation.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
const MachineContextTag = Context.GenericTag("@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
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
126
|
+
fn,
|
|
127
|
+
define
|
|
95
128
|
};
|
|
96
129
|
//#endregion
|
|
97
|
-
export {
|
|
130
|
+
export { MachineContextTag, Slot, define, fn };
|
package/v3/dist/testing.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AssertionError } from "./errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { MachineContext, ProvideSlots, SlotsDef } from "./slot.js";
|
|
3
3
|
import { Machine, MachineRef } from "./machine.js";
|
|
4
4
|
import { Effect, SubscriptionRef } from "effect";
|
|
5
5
|
|
|
6
6
|
//#region src/testing.d.ts
|
|
7
|
-
type MachineInput<S, E, R,
|
|
7
|
+
type MachineInput<S, E, R, SD extends SlotsDef = Record<string, never>> = Machine<S, E, R, any, any, SD>;
|
|
8
8
|
/**
|
|
9
9
|
* Result of simulating events through a machine
|
|
10
10
|
*/
|
|
@@ -15,7 +15,7 @@ interface SimulationResult<S> {
|
|
|
15
15
|
/**
|
|
16
16
|
* Simulate a sequence of events through a machine without running an actor.
|
|
17
17
|
* Useful for testing state transitions in isolation.
|
|
18
|
-
* Does not run onEnter/spawn/background effects, but does run
|
|
18
|
+
* Does not run onEnter/spawn/background effects, but does run slots
|
|
19
19
|
* within transition handlers.
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
@@ -36,8 +36,8 @@ declare const simulate: <S extends {
|
|
|
36
36
|
readonly _tag: string;
|
|
37
37
|
}, E extends {
|
|
38
38
|
readonly _tag: string;
|
|
39
|
-
}, R,
|
|
40
|
-
slots?:
|
|
39
|
+
}, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], options?: {
|
|
40
|
+
slots?: ProvideSlots<SD, any>;
|
|
41
41
|
} | undefined) => Effect.Effect<{
|
|
42
42
|
states: S[];
|
|
43
43
|
finalState: S;
|
|
@@ -49,8 +49,8 @@ declare const assertReaches: <S extends {
|
|
|
49
49
|
readonly _tag: string;
|
|
50
50
|
}, E extends {
|
|
51
51
|
readonly _tag: string;
|
|
52
|
-
}, R,
|
|
53
|
-
slots?:
|
|
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
54
|
} | undefined) => Effect.Effect<S, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
|
|
55
55
|
/**
|
|
56
56
|
* Assert that a machine follows a specific path of state tags
|
|
@@ -68,8 +68,8 @@ declare const assertPath: <S extends {
|
|
|
68
68
|
readonly _tag: string;
|
|
69
69
|
}, E extends {
|
|
70
70
|
readonly _tag: string;
|
|
71
|
-
}, R,
|
|
72
|
-
slots?:
|
|
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
73
|
} | undefined) => Effect.Effect<{
|
|
74
74
|
states: S[];
|
|
75
75
|
finalState: S;
|
|
@@ -91,8 +91,8 @@ declare const assertNeverReaches: <S extends {
|
|
|
91
91
|
readonly _tag: string;
|
|
92
92
|
}, E extends {
|
|
93
93
|
readonly _tag: string;
|
|
94
|
-
}, R,
|
|
95
|
-
slots?:
|
|
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
96
|
} | undefined) => Effect.Effect<{
|
|
97
97
|
states: S[];
|
|
98
98
|
finalState: S;
|
|
@@ -108,16 +108,18 @@ interface TestHarness<S, E, R> {
|
|
|
108
108
|
/**
|
|
109
109
|
* Options for creating a test harness
|
|
110
110
|
*/
|
|
111
|
-
interface TestHarnessOptions<S, E
|
|
111
|
+
interface TestHarnessOptions<S, E, SD extends SlotsDef = Record<string, never>> {
|
|
112
112
|
/**
|
|
113
113
|
* Called after each transition with the previous state, event, and new state.
|
|
114
114
|
* Useful for logging or spying on transitions.
|
|
115
115
|
*/
|
|
116
116
|
readonly onTransition?: (from: S, event: E, to: S) => void;
|
|
117
|
+
/** Slot handler implementations. */
|
|
118
|
+
readonly slots?: ProvideSlots<SD, any>;
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
119
121
|
* Create a test harness for step-by-step testing.
|
|
120
|
-
* Does not run onEnter/spawn/background effects, but does run
|
|
122
|
+
* Does not run onEnter/spawn/background effects, but does run slots
|
|
121
123
|
* within transition handlers.
|
|
122
124
|
*
|
|
123
125
|
* @example Basic usage
|
|
@@ -140,9 +142,7 @@ declare const createTestHarness: <S extends {
|
|
|
140
142
|
readonly _tag: string;
|
|
141
143
|
}, E extends {
|
|
142
144
|
readonly _tag: string;
|
|
143
|
-
}, R,
|
|
144
|
-
slots?: Record<string, any>;
|
|
145
|
-
}) | 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<{
|
|
146
146
|
state: SubscriptionRef.SubscriptionRef<S>;
|
|
147
147
|
send: (event: E) => Effect.Effect<S, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
|
|
148
148
|
getState: Effect.Effect<S, never, never>;
|
package/v3/dist/testing.js
CHANGED
|
@@ -4,19 +4,19 @@ import { executeTransition, shouldPostpone } from "./internal/transition.js";
|
|
|
4
4
|
import { materializeMachine } from "./machine.js";
|
|
5
5
|
import { Effect, SubscriptionRef } from "effect";
|
|
6
6
|
//#region src/testing.ts
|
|
7
|
-
/** Create a dummy MachineRef for testing utilities (no real send/spawn). */
|
|
8
7
|
const makeDummySelf = (label) => {
|
|
9
|
-
const dummySend = Effect.fn(
|
|
8
|
+
const dummySend = Effect.fn(label)((_event) => Effect.void);
|
|
10
9
|
return {
|
|
11
10
|
send: dummySend,
|
|
12
11
|
cast: dummySend,
|
|
13
|
-
spawn: () => Effect.die(`spawn not supported in ${label}`)
|
|
12
|
+
spawn: () => Effect.die(`spawn not supported in ${label}`),
|
|
13
|
+
reply: () => Effect.succeed(false)
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
17
|
* Simulate a sequence of events through a machine without running an actor.
|
|
18
18
|
* Useful for testing state transitions in isolation.
|
|
19
|
-
* Does not run onEnter/spawn/background effects, but does run
|
|
19
|
+
* Does not run onEnter/spawn/background effects, but does run slots
|
|
20
20
|
* within transition handlers.
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
@@ -35,7 +35,7 @@ const makeDummySelf = (label) => {
|
|
|
35
35
|
*/
|
|
36
36
|
const simulate = Effect.fn("effect-machine.simulate")(function* (input, events, options) {
|
|
37
37
|
const machine = materializeMachine(input, options?.slots);
|
|
38
|
-
const dummySelf = makeDummySelf("simulate");
|
|
38
|
+
const dummySelf = makeDummySelf("effect-machine.testing.simulate");
|
|
39
39
|
let currentState = machine.initial;
|
|
40
40
|
const states = [currentState];
|
|
41
41
|
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
@@ -122,7 +122,7 @@ const assertNeverReaches = Effect.fn("effect-machine.assertNeverReaches")(functi
|
|
|
122
122
|
});
|
|
123
123
|
/**
|
|
124
124
|
* Create a test harness for step-by-step testing.
|
|
125
|
-
* Does not run onEnter/spawn/background effects, but does run
|
|
125
|
+
* Does not run onEnter/spawn/background effects, but does run slots
|
|
126
126
|
* within transition handlers.
|
|
127
127
|
*
|
|
128
128
|
* @example Basic usage
|
|
@@ -143,7 +143,7 @@ const assertNeverReaches = Effect.fn("effect-machine.assertNeverReaches")(functi
|
|
|
143
143
|
*/
|
|
144
144
|
const createTestHarness = Effect.fn("effect-machine.createTestHarness")(function* (input, options) {
|
|
145
145
|
const machine = materializeMachine(input, options?.slots);
|
|
146
|
-
const dummySelf = makeDummySelf("harness");
|
|
146
|
+
const dummySelf = makeDummySelf("effect-machine.testing.harness");
|
|
147
147
|
const stateRef = yield* SubscriptionRef.make(machine.initial);
|
|
148
148
|
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
149
149
|
const postponed = [];
|