effect-machine 0.3.1 → 0.4.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 +24 -0
- package/dist/_virtual/_rolldown/runtime.js +18 -0
- package/dist/actor.d.ts +256 -0
- package/dist/actor.js +402 -0
- package/dist/cluster/entity-machine.d.ts +90 -0
- package/dist/cluster/entity-machine.js +80 -0
- package/dist/cluster/index.d.ts +3 -0
- package/dist/cluster/index.js +4 -0
- package/dist/cluster/to-entity.d.ts +64 -0
- package/dist/cluster/to-entity.js +53 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.js +38 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/inspection.d.ts +125 -0
- package/dist/inspection.js +50 -0
- package/dist/internal/brands.d.ts +40 -0
- package/dist/internal/brands.js +0 -0
- package/dist/internal/inspection.d.ts +11 -0
- package/dist/internal/inspection.js +15 -0
- package/dist/internal/transition.d.ts +160 -0
- package/dist/internal/transition.js +238 -0
- package/dist/internal/utils.d.ts +60 -0
- package/dist/internal/utils.js +46 -0
- package/dist/machine.d.ts +278 -0
- package/dist/machine.js +317 -0
- package/{src/persistence/adapter.ts → dist/persistence/adapter.d.ts} +40 -72
- package/dist/persistence/adapter.js +27 -0
- package/dist/persistence/adapters/in-memory.d.ts +32 -0
- package/dist/persistence/adapters/in-memory.js +176 -0
- package/dist/persistence/index.d.ts +5 -0
- package/dist/persistence/index.js +6 -0
- package/dist/persistence/persistent-actor.d.ts +50 -0
- package/dist/persistence/persistent-actor.js +358 -0
- package/{src/persistence/persistent-machine.ts → dist/persistence/persistent-machine.d.ts} +28 -54
- package/dist/persistence/persistent-machine.js +24 -0
- package/dist/schema.d.ts +141 -0
- package/dist/schema.js +165 -0
- package/dist/slot.d.ts +130 -0
- package/dist/slot.js +99 -0
- package/dist/testing.d.ts +142 -0
- package/dist/testing.js +138 -0
- package/package.json +28 -14
- package/src/actor.ts +0 -1058
- package/src/cluster/entity-machine.ts +0 -201
- package/src/cluster/index.ts +0 -43
- package/src/cluster/to-entity.ts +0 -99
- package/src/errors.ts +0 -64
- package/src/index.ts +0 -105
- package/src/inspection.ts +0 -178
- package/src/internal/brands.ts +0 -51
- package/src/internal/inspection.ts +0 -18
- package/src/internal/transition.ts +0 -489
- package/src/internal/utils.ts +0 -80
- package/src/machine.ts +0 -836
- package/src/persistence/adapters/in-memory.ts +0 -294
- package/src/persistence/index.ts +0 -24
- package/src/persistence/persistent-actor.ts +0 -791
- package/src/schema.ts +0 -362
- package/src/slot.ts +0 -281
- package/src/testing.ts +0 -284
- package/tsconfig.json +0 -65
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import type { StateBrand, EventBrand } from "../internal/brands.js";
|
|
5
|
-
import { MissingSchemaError } from "../errors.js";
|
|
6
|
-
|
|
7
|
-
// Branded type constraints
|
|
8
|
-
type BrandedState = { readonly _tag: string } & StateBrand;
|
|
9
|
-
type BrandedEvent = { readonly _tag: string } & EventBrand;
|
|
1
|
+
import { EventBrand, StateBrand } from "../internal/brands.js";
|
|
2
|
+
import { Machine } from "../machine.js";
|
|
3
|
+
import { Schedule, Schema } from "effect";
|
|
10
4
|
|
|
5
|
+
//#region src/persistence/persistent-machine.d.ts
|
|
6
|
+
type BrandedState = {
|
|
7
|
+
readonly _tag: string;
|
|
8
|
+
} & StateBrand;
|
|
9
|
+
type BrandedEvent = {
|
|
10
|
+
readonly _tag: string;
|
|
11
|
+
} & EventBrand;
|
|
11
12
|
/**
|
|
12
13
|
* Configuration for persistence behavior (after resolution).
|
|
13
14
|
* Schemas are required at runtime - the persist function ensures this.
|
|
@@ -15,7 +16,7 @@ type BrandedEvent = { readonly _tag: string } & EventBrand;
|
|
|
15
16
|
* Note: Schema types S and E should match the structural shape of the machine's
|
|
16
17
|
* state and event types (without brands). The schemas don't know about brands.
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
interface PersistenceConfig<S, E, SSI = unknown, ESI = unknown> {
|
|
19
20
|
/**
|
|
20
21
|
* Schedule controlling when snapshots are taken.
|
|
21
22
|
* Input is the new state after each transition.
|
|
@@ -26,25 +27,21 @@ export interface PersistenceConfig<S, E, SSI = unknown, ESI = unknown> {
|
|
|
26
27
|
* - Schedule.recurs(100) — every N transitions
|
|
27
28
|
*/
|
|
28
29
|
readonly snapshotSchedule: Schedule.Schedule<unknown, S>;
|
|
29
|
-
|
|
30
30
|
/**
|
|
31
31
|
* Whether to journal events for replay capability.
|
|
32
32
|
* When true, all events are appended to the event log.
|
|
33
33
|
*/
|
|
34
34
|
readonly journalEvents: boolean;
|
|
35
|
-
|
|
36
35
|
/**
|
|
37
36
|
* Schema for serializing/deserializing state.
|
|
38
37
|
* Always present at runtime (resolved from config or machine).
|
|
39
38
|
*/
|
|
40
39
|
readonly stateSchema: Schema.Schema<S, SSI, never>;
|
|
41
|
-
|
|
42
40
|
/**
|
|
43
41
|
* Schema for serializing/deserializing events.
|
|
44
42
|
* Always present at runtime (resolved from config or machine).
|
|
45
43
|
*/
|
|
46
44
|
readonly eventSchema: Schema.Schema<E, ESI, never>;
|
|
47
|
-
|
|
48
45
|
/**
|
|
49
46
|
* User-provided identifier for the machine type.
|
|
50
47
|
* Used for filtering actors in restoreAll.
|
|
@@ -52,32 +49,27 @@ export interface PersistenceConfig<S, E, SSI = unknown, ESI = unknown> {
|
|
|
52
49
|
*/
|
|
53
50
|
readonly machineType?: string;
|
|
54
51
|
}
|
|
55
|
-
|
|
56
52
|
/**
|
|
57
53
|
* Machine with persistence configuration attached.
|
|
58
54
|
* Spawn auto-detects this and returns PersistentActorRef.
|
|
59
55
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
> {
|
|
56
|
+
interface PersistentMachine<S extends {
|
|
57
|
+
readonly _tag: string;
|
|
58
|
+
}, E extends {
|
|
59
|
+
readonly _tag: string;
|
|
60
|
+
}, R = never> {
|
|
65
61
|
readonly _tag: "PersistentMachine";
|
|
66
62
|
readonly machine: Machine<S, E, R>;
|
|
67
63
|
readonly persistence: PersistenceConfig<S, E>;
|
|
68
64
|
}
|
|
69
|
-
|
|
70
65
|
/**
|
|
71
66
|
* Type guard to check if a value is a PersistentMachine
|
|
72
67
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"_tag" in value &&
|
|
79
|
-
(value as { _tag: unknown })._tag === "PersistentMachine";
|
|
80
|
-
|
|
68
|
+
declare const isPersistentMachine: (value: unknown) => value is PersistentMachine<{
|
|
69
|
+
readonly _tag: string;
|
|
70
|
+
}, {
|
|
71
|
+
readonly _tag: string;
|
|
72
|
+
}, unknown>;
|
|
81
73
|
/**
|
|
82
74
|
* Attach persistence configuration to a machine.
|
|
83
75
|
*
|
|
@@ -101,31 +93,13 @@ export const isPersistentMachine = (
|
|
|
101
93
|
* );
|
|
102
94
|
* ```
|
|
103
95
|
*/
|
|
104
|
-
|
|
105
|
-
readonly snapshotSchedule: Schedule.Schedule<unknown, {
|
|
96
|
+
interface WithPersistenceConfig {
|
|
97
|
+
readonly snapshotSchedule: Schedule.Schedule<unknown, {
|
|
98
|
+
readonly _tag: string;
|
|
99
|
+
}>;
|
|
106
100
|
readonly journalEvents: boolean;
|
|
107
101
|
readonly machineType?: string;
|
|
108
102
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<S extends BrandedState, E extends BrandedEvent, R>(
|
|
113
|
-
machine: Machine<S, E, R>,
|
|
114
|
-
): PersistentMachine<S, E, R> => {
|
|
115
|
-
const stateSchema = machine.stateSchema;
|
|
116
|
-
const eventSchema = machine.eventSchema;
|
|
117
|
-
|
|
118
|
-
if (stateSchema === undefined || eventSchema === undefined) {
|
|
119
|
-
throw new MissingSchemaError({ operation: "persist" });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
_tag: "PersistentMachine",
|
|
124
|
-
machine,
|
|
125
|
-
persistence: {
|
|
126
|
-
...config,
|
|
127
|
-
stateSchema,
|
|
128
|
-
eventSchema,
|
|
129
|
-
} as unknown as PersistenceConfig<S, E>,
|
|
130
|
-
};
|
|
131
|
-
};
|
|
103
|
+
declare const persist: (config: WithPersistenceConfig) => <S extends BrandedState, E extends BrandedEvent, R>(machine: Machine<S, E, R>) => PersistentMachine<S, E, R>;
|
|
104
|
+
//#endregion
|
|
105
|
+
export { PersistenceConfig, PersistentMachine, WithPersistenceConfig, isPersistentMachine, persist };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MissingSchemaError } from "../errors.js";
|
|
2
|
+
|
|
3
|
+
//#region src/persistence/persistent-machine.ts
|
|
4
|
+
/**
|
|
5
|
+
* Type guard to check if a value is a PersistentMachine
|
|
6
|
+
*/
|
|
7
|
+
const isPersistentMachine = (value) => typeof value === "object" && value !== null && "_tag" in value && value._tag === "PersistentMachine";
|
|
8
|
+
const persist = (config) => (machine) => {
|
|
9
|
+
const stateSchema = machine.stateSchema;
|
|
10
|
+
const eventSchema = machine.eventSchema;
|
|
11
|
+
if (stateSchema === void 0 || eventSchema === void 0) throw new MissingSchemaError({ operation: "persist" });
|
|
12
|
+
return {
|
|
13
|
+
_tag: "PersistentMachine",
|
|
14
|
+
machine,
|
|
15
|
+
persistence: {
|
|
16
|
+
...config,
|
|
17
|
+
stateSchema,
|
|
18
|
+
eventSchema
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { isPersistentMachine, persist };
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { FullEventBrand, FullStateBrand } from "./internal/brands.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/schema.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Extract the TypeScript type from a TaggedStruct schema
|
|
7
|
+
*/
|
|
8
|
+
type TaggedStructType<Tag extends string, Fields extends Schema.Struct.Fields> = Schema.Schema.Type<Schema.TaggedStruct<Tag, Fields>>;
|
|
9
|
+
/**
|
|
10
|
+
* Build variant schemas type from definition
|
|
11
|
+
*/
|
|
12
|
+
type VariantSchemas<D extends Record<string, Schema.Struct.Fields>> = { readonly [K in keyof D & string]: Schema.TaggedStruct<K, D[K]> };
|
|
13
|
+
/**
|
|
14
|
+
* Build union type from variant schemas.
|
|
15
|
+
* Used for constraining fluent method type params.
|
|
16
|
+
*/
|
|
17
|
+
type VariantsUnion<D extends Record<string, Schema.Struct.Fields>> = { [K in keyof D & string]: TaggedStructType<K, D[K]> }[keyof D & string];
|
|
18
|
+
/**
|
|
19
|
+
* Check if fields are empty (no required properties)
|
|
20
|
+
*/
|
|
21
|
+
type IsEmptyFields<Fields extends Schema.Struct.Fields> = keyof Fields extends never ? true : false;
|
|
22
|
+
/**
|
|
23
|
+
* Constructor functions for each variant.
|
|
24
|
+
* Empty structs: plain values with `_tag`: `State.Idle`
|
|
25
|
+
* Non-empty structs require args: `State.Loading({ url })`
|
|
26
|
+
*
|
|
27
|
+
* Each variant also has a `derive` method for constructing from a source object.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Constructor functions for each variant.
|
|
31
|
+
* Empty structs: plain values with `_tag`: `State.Idle`
|
|
32
|
+
* Non-empty structs require args: `State.Loading({ url })`
|
|
33
|
+
*
|
|
34
|
+
* Each variant also has a `derive` method for constructing from a source object.
|
|
35
|
+
* The source type uses `object` to accept branded state types without index signature issues.
|
|
36
|
+
*/
|
|
37
|
+
type VariantConstructors<D extends Record<string, Schema.Struct.Fields>, Brand> = { readonly [K in keyof D & string]: IsEmptyFields<D[K]> extends true ? TaggedStructType<K, D[K]> & Brand & {
|
|
38
|
+
readonly derive: (source: object) => TaggedStructType<K, D[K]> & Brand;
|
|
39
|
+
} : ((args: Schema.Struct.Constructor<D[K]>) => TaggedStructType<K, D[K]> & Brand) & {
|
|
40
|
+
readonly derive: (source: object, partial?: Partial<Schema.Struct.Constructor<D[K]>>) => TaggedStructType<K, D[K]> & Brand;
|
|
41
|
+
readonly _tag: K;
|
|
42
|
+
} };
|
|
43
|
+
/**
|
|
44
|
+
* Pattern matching cases type
|
|
45
|
+
*/
|
|
46
|
+
type MatchCases<D extends Record<string, Schema.Struct.Fields>, R> = { readonly [K in keyof D & string]: (value: TaggedStructType<K, D[K]>) => R };
|
|
47
|
+
/**
|
|
48
|
+
* Base schema interface with pattern matching helpers
|
|
49
|
+
*/
|
|
50
|
+
interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Brand> {
|
|
51
|
+
/**
|
|
52
|
+
* Raw definition record for introspection
|
|
53
|
+
*/
|
|
54
|
+
readonly _definition: D;
|
|
55
|
+
/**
|
|
56
|
+
* Per-variant schemas for fine-grained operations
|
|
57
|
+
*/
|
|
58
|
+
readonly variants: VariantSchemas<D>;
|
|
59
|
+
/**
|
|
60
|
+
* Type guard: `OrderState.$is("Pending")(value)`
|
|
61
|
+
*/
|
|
62
|
+
readonly $is: <Tag extends keyof D & string>(tag: Tag) => (u: unknown) => u is TaggedStructType<Tag, D[Tag]> & Brand;
|
|
63
|
+
/**
|
|
64
|
+
* Pattern matching (curried and uncurried)
|
|
65
|
+
*/
|
|
66
|
+
readonly $match: {
|
|
67
|
+
<R>(cases: MatchCases<D, R>): (value: VariantsUnion<D> & Brand) => R;
|
|
68
|
+
<R>(value: VariantsUnion<D> & Brand, cases: MatchCases<D, R>): R;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Schema-first state definition that provides:
|
|
73
|
+
* - Schema for encode/decode/validate
|
|
74
|
+
* - Variant constructors: `OrderState.Pending({ orderId: "x" })`
|
|
75
|
+
* - Pattern matching: `$is`, `$match`
|
|
76
|
+
* - Type inference: `typeof OrderState.Type`
|
|
77
|
+
*
|
|
78
|
+
* The D type parameter captures the definition, creating a unique brand
|
|
79
|
+
* per distinct schema definition shape.
|
|
80
|
+
*/
|
|
81
|
+
type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullStateBrand<D>, VariantsUnion<D>, never> & MachineSchemaBase<D, FullStateBrand<D>> & VariantConstructors<D, FullStateBrand<D>>;
|
|
82
|
+
/**
|
|
83
|
+
* Schema-first event definition (same structure as state, different brand)
|
|
84
|
+
*
|
|
85
|
+
* The D type parameter captures the definition, creating a unique brand
|
|
86
|
+
* per distinct schema definition shape.
|
|
87
|
+
*/
|
|
88
|
+
type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullEventBrand<D>, VariantsUnion<D>, never> & MachineSchemaBase<D, FullEventBrand<D>> & VariantConstructors<D, FullEventBrand<D>>;
|
|
89
|
+
/**
|
|
90
|
+
* Create a schema-first State definition.
|
|
91
|
+
*
|
|
92
|
+
* The schema's definition type D creates a unique brand, preventing
|
|
93
|
+
* accidental use of constructors from different state schemas
|
|
94
|
+
* (unless they have identical definitions).
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const OrderState = MachineSchema.State({
|
|
99
|
+
* Pending: { orderId: Schema.String },
|
|
100
|
+
* Shipped: { trackingId: Schema.String },
|
|
101
|
+
* })
|
|
102
|
+
*
|
|
103
|
+
* type OrderState = typeof OrderState.Type
|
|
104
|
+
*
|
|
105
|
+
* // Construct
|
|
106
|
+
* const s = OrderState.Pending({ orderId: "123" })
|
|
107
|
+
*
|
|
108
|
+
* // Pattern match
|
|
109
|
+
* OrderState.$match(s, {
|
|
110
|
+
* Pending: (v) => v.orderId,
|
|
111
|
+
* Shipped: (v) => v.trackingId,
|
|
112
|
+
* })
|
|
113
|
+
*
|
|
114
|
+
* // Validate
|
|
115
|
+
* Schema.decodeUnknownSync(OrderState)(rawJson)
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare const State: <const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineStateSchema<D>;
|
|
119
|
+
/**
|
|
120
|
+
* Create a schema-first Event definition.
|
|
121
|
+
*
|
|
122
|
+
* The schema's definition type D creates a unique brand, preventing
|
|
123
|
+
* accidental use of constructors from different event schemas
|
|
124
|
+
* (unless they have identical definitions).
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const OrderEvent = MachineSchema.Event({
|
|
129
|
+
* Ship: { trackingId: Schema.String },
|
|
130
|
+
* Cancel: {},
|
|
131
|
+
* })
|
|
132
|
+
*
|
|
133
|
+
* type OrderEvent = typeof OrderEvent.Type
|
|
134
|
+
*
|
|
135
|
+
* // Construct
|
|
136
|
+
* const e = OrderEvent.Ship({ trackingId: "abc" })
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
declare const Event: <const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineEventSchema<D>;
|
|
140
|
+
//#endregion
|
|
141
|
+
export { Event, MachineEventSchema, MachineStateSchema, State, VariantsUnion };
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { InvalidSchemaError, MissingMatchHandlerError } from "./errors.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/schema.ts
|
|
5
|
+
/**
|
|
6
|
+
* Schema-first State/Event definitions for effect-machine.
|
|
7
|
+
*
|
|
8
|
+
* MachineSchema provides a single source of truth that combines:
|
|
9
|
+
* - Schema for validation/serialization
|
|
10
|
+
* - Variant constructors (like Data.taggedEnum)
|
|
11
|
+
* - $is and $match helpers for pattern matching
|
|
12
|
+
* - Brand integration for compile-time safety
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { State, Event, Machine } from "effect-machine"
|
|
17
|
+
*
|
|
18
|
+
* // Define schema-first state
|
|
19
|
+
* const OrderState = State({
|
|
20
|
+
* Pending: { orderId: Schema.String },
|
|
21
|
+
* Shipped: { trackingId: Schema.String },
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* // Infer type from schema
|
|
25
|
+
* type OrderState = typeof OrderState.Type
|
|
26
|
+
*
|
|
27
|
+
* // Use constructors
|
|
28
|
+
* const pending = OrderState.Pending({ orderId: "123" })
|
|
29
|
+
*
|
|
30
|
+
* // Pattern match
|
|
31
|
+
* OrderState.$match(state, {
|
|
32
|
+
* Pending: (s) => `Order ${s.orderId} pending`,
|
|
33
|
+
* Shipped: (s) => `Shipped: ${s.trackingId}`,
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* // Use as Schema for persistence/cluster
|
|
37
|
+
* machine.pipe(Machine.persist({ stateSchema: OrderState, ... }))
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @module
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Build a schema-first definition from a record of tag -> fields
|
|
44
|
+
*/
|
|
45
|
+
const buildMachineSchema = (definition) => {
|
|
46
|
+
const variants = {};
|
|
47
|
+
const constructors = {};
|
|
48
|
+
for (const tag of Object.keys(definition)) {
|
|
49
|
+
const fields = definition[tag];
|
|
50
|
+
if (fields === void 0) continue;
|
|
51
|
+
variants[tag] = Schema.TaggedStruct(tag, fields);
|
|
52
|
+
const fieldNames = new Set(Object.keys(fields));
|
|
53
|
+
if (fieldNames.size > 0) {
|
|
54
|
+
const constructor = (args) => ({
|
|
55
|
+
...args,
|
|
56
|
+
_tag: tag
|
|
57
|
+
});
|
|
58
|
+
constructor._tag = tag;
|
|
59
|
+
constructor.derive = (source, partial) => {
|
|
60
|
+
const result = { _tag: tag };
|
|
61
|
+
for (const key of fieldNames) if (key in source) result[key] = source[key];
|
|
62
|
+
if (partial !== void 0) for (const [key, value] of Object.entries(partial)) result[key] = value;
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
constructors[tag] = constructor;
|
|
66
|
+
} else constructors[tag] = {
|
|
67
|
+
_tag: tag,
|
|
68
|
+
derive: () => ({ _tag: tag })
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const variantArray = Object.values(variants);
|
|
72
|
+
if (variantArray.length === 0) throw new InvalidSchemaError();
|
|
73
|
+
const unionSchema = variantArray.length === 1 ? variantArray[0] : Schema.Union(...variantArray);
|
|
74
|
+
const $is = (tag) => (u) => typeof u === "object" && u !== null && "_tag" in u && u._tag === tag;
|
|
75
|
+
const $match = (valueOrCases, maybeCases) => {
|
|
76
|
+
if (maybeCases !== void 0) {
|
|
77
|
+
const value = valueOrCases;
|
|
78
|
+
const handler = maybeCases[value._tag];
|
|
79
|
+
if (handler === void 0) throw new MissingMatchHandlerError({ tag: value._tag });
|
|
80
|
+
return handler(value);
|
|
81
|
+
}
|
|
82
|
+
const cases = valueOrCases;
|
|
83
|
+
return (value) => {
|
|
84
|
+
const handler = cases[value._tag];
|
|
85
|
+
if (handler === void 0) throw new MissingMatchHandlerError({ tag: value._tag });
|
|
86
|
+
return handler(value);
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
schema: unionSchema,
|
|
91
|
+
variants,
|
|
92
|
+
constructors,
|
|
93
|
+
_definition: definition,
|
|
94
|
+
$is,
|
|
95
|
+
$match
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Internal helper to create a machine schema (shared by State and Event).
|
|
100
|
+
* Builds the schema object with variants, constructors, $is, and $match.
|
|
101
|
+
*/
|
|
102
|
+
const createMachineSchema = (definition) => {
|
|
103
|
+
const { schema, variants, constructors, _definition, $is, $match } = buildMachineSchema(definition);
|
|
104
|
+
return Object.assign(Object.create(schema), {
|
|
105
|
+
variants,
|
|
106
|
+
_definition,
|
|
107
|
+
$is,
|
|
108
|
+
$match,
|
|
109
|
+
...constructors
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Create a schema-first State definition.
|
|
114
|
+
*
|
|
115
|
+
* The schema's definition type D creates a unique brand, preventing
|
|
116
|
+
* accidental use of constructors from different state schemas
|
|
117
|
+
* (unless they have identical definitions).
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* const OrderState = MachineSchema.State({
|
|
122
|
+
* Pending: { orderId: Schema.String },
|
|
123
|
+
* Shipped: { trackingId: Schema.String },
|
|
124
|
+
* })
|
|
125
|
+
*
|
|
126
|
+
* type OrderState = typeof OrderState.Type
|
|
127
|
+
*
|
|
128
|
+
* // Construct
|
|
129
|
+
* const s = OrderState.Pending({ orderId: "123" })
|
|
130
|
+
*
|
|
131
|
+
* // Pattern match
|
|
132
|
+
* OrderState.$match(s, {
|
|
133
|
+
* Pending: (v) => v.orderId,
|
|
134
|
+
* Shipped: (v) => v.trackingId,
|
|
135
|
+
* })
|
|
136
|
+
*
|
|
137
|
+
* // Validate
|
|
138
|
+
* Schema.decodeUnknownSync(OrderState)(rawJson)
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
const State = (definition) => createMachineSchema(definition);
|
|
142
|
+
/**
|
|
143
|
+
* Create a schema-first Event definition.
|
|
144
|
+
*
|
|
145
|
+
* The schema's definition type D creates a unique brand, preventing
|
|
146
|
+
* accidental use of constructors from different event schemas
|
|
147
|
+
* (unless they have identical definitions).
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const OrderEvent = MachineSchema.Event({
|
|
152
|
+
* Ship: { trackingId: Schema.String },
|
|
153
|
+
* Cancel: {},
|
|
154
|
+
* })
|
|
155
|
+
*
|
|
156
|
+
* type OrderEvent = typeof OrderEvent.Type
|
|
157
|
+
*
|
|
158
|
+
* // Construct
|
|
159
|
+
* const e = OrderEvent.Ship({ trackingId: "abc" })
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
const Event = (definition) => createMachineSchema(definition);
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
export { Event, State };
|
package/dist/slot.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ActorSystem } from "./actor.js";
|
|
2
|
+
import { Context, Effect, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/slot.d.ts
|
|
5
|
+
/** Schema fields definition (like Schema.Struct.Fields) */
|
|
6
|
+
type Fields = Record<string, Schema.Schema.All>;
|
|
7
|
+
/** Extract the encoded type from schema fields (used for parameters) */
|
|
8
|
+
type FieldsToParams<F extends Fields> = keyof F extends never ? void : Schema.Schema.Type<Schema.Struct<F>>;
|
|
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>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* An effect slot - callable function that returns Effect<void>.
|
|
19
|
+
*/
|
|
20
|
+
interface EffectSlot<Name extends string, Params> {
|
|
21
|
+
readonly _tag: "EffectSlot";
|
|
22
|
+
readonly name: Name;
|
|
23
|
+
(params: Params): Effect.Effect<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Guard definition - name to schema fields mapping
|
|
27
|
+
*/
|
|
28
|
+
type GuardsDef = Record<string, Fields>;
|
|
29
|
+
/**
|
|
30
|
+
* Effect definition - name to schema fields mapping
|
|
31
|
+
*/
|
|
32
|
+
type EffectsDef = Record<string, Fields>;
|
|
33
|
+
/**
|
|
34
|
+
* Convert guard definitions to callable guard slots
|
|
35
|
+
*/
|
|
36
|
+
type GuardSlots<D extends GuardsDef> = { readonly [K in keyof D & string]: GuardSlot<K, FieldsToParams<D[K]>> };
|
|
37
|
+
/**
|
|
38
|
+
* Convert effect definitions to callable effect slots
|
|
39
|
+
*/
|
|
40
|
+
type EffectSlots<D extends EffectsDef> = { readonly [K in keyof D & string]: EffectSlot<K, FieldsToParams<D[K]>> };
|
|
41
|
+
/**
|
|
42
|
+
* Type for machine context - state, event, and self reference.
|
|
43
|
+
* Shared across all machines via MachineContextTag.
|
|
44
|
+
*/
|
|
45
|
+
interface MachineContext<State, Event, Self> {
|
|
46
|
+
readonly state: State;
|
|
47
|
+
readonly event: Event;
|
|
48
|
+
readonly self: Self;
|
|
49
|
+
readonly system: ActorSystem;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Shared Context tag for all machines.
|
|
53
|
+
* Single module-level tag instead of per-machine allocation.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
declare const MachineContextTag: Context.Tag<MachineContext<any, any, any>, MachineContext<any, any, any>>;
|
|
57
|
+
/**
|
|
58
|
+
* Guard handler implementation.
|
|
59
|
+
* Receives params and context, returns Effect<boolean>.
|
|
60
|
+
*/
|
|
61
|
+
type GuardHandler<Params, Ctx, R = never> = (params: Params, ctx: Ctx) => boolean | Effect.Effect<boolean, never, R>;
|
|
62
|
+
/**
|
|
63
|
+
* Effect handler implementation.
|
|
64
|
+
* Receives params and context, returns Effect<void>.
|
|
65
|
+
*/
|
|
66
|
+
type EffectHandler<Params, Ctx, R = never> = (params: Params, ctx: Ctx) => Effect.Effect<void, never, R>;
|
|
67
|
+
/**
|
|
68
|
+
* Handler types for all guards in a definition
|
|
69
|
+
*/
|
|
70
|
+
type GuardHandlers<D extends GuardsDef, MachineCtx, R = never> = { readonly [K in keyof D & string]: GuardHandler<FieldsToParams<D[K]>, MachineCtx, R> };
|
|
71
|
+
/**
|
|
72
|
+
* Handler types for all effects in a definition
|
|
73
|
+
*/
|
|
74
|
+
type EffectHandlers<D extends EffectsDef, MachineCtx, R = never> = { readonly [K in keyof D & string]: EffectHandler<FieldsToParams<D[K]>, MachineCtx, R> };
|
|
75
|
+
/**
|
|
76
|
+
* Guards schema - returned by Slot.Guards()
|
|
77
|
+
*/
|
|
78
|
+
interface GuardsSchema<D extends GuardsDef> {
|
|
79
|
+
readonly _tag: "GuardsSchema";
|
|
80
|
+
readonly definitions: D;
|
|
81
|
+
/** Create callable guard slots (used by Machine internally) */
|
|
82
|
+
readonly _createSlots: (resolve: <N extends keyof D & string>(name: N, params: FieldsToParams<D[N]>) => Effect.Effect<boolean>) => GuardSlots<D>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Effects schema - returned by Slot.Effects()
|
|
86
|
+
*/
|
|
87
|
+
interface EffectsSchema<D extends EffectsDef> {
|
|
88
|
+
readonly _tag: "EffectsSchema";
|
|
89
|
+
readonly definitions: D;
|
|
90
|
+
/** Create callable effect slots (used by Machine internally) */
|
|
91
|
+
readonly _createSlots: (resolve: <N extends keyof D & string>(name: N, params: FieldsToParams<D[N]>) => Effect.Effect<void>) => EffectSlots<D>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a guards schema with parameterized guard definitions.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const MyGuards = Slot.Guards({
|
|
99
|
+
* canRetry: { max: Schema.Number },
|
|
100
|
+
* isValid: {},
|
|
101
|
+
* })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare const Guards: <D extends GuardsDef>(definitions: D) => GuardsSchema<D>;
|
|
105
|
+
/**
|
|
106
|
+
* Create an effects schema with parameterized effect definitions.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const MyEffects = Slot.Effects({
|
|
111
|
+
* fetchData: { url: Schema.String },
|
|
112
|
+
* notify: { message: Schema.String },
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare const Effects: <D extends EffectsDef>(definitions: D) => EffectsSchema<D>;
|
|
117
|
+
/** Extract guard definition type from GuardsSchema */
|
|
118
|
+
type GuardsDefOf<G> = G extends GuardsSchema<infer D> ? D : never;
|
|
119
|
+
/** Extract effect definition type from EffectsSchema */
|
|
120
|
+
type EffectsDefOf<E> = E extends EffectsSchema<infer D> ? D : never;
|
|
121
|
+
/** Extract guard slots type from GuardsSchema */
|
|
122
|
+
type GuardSlotsOf<G> = G extends GuardsSchema<infer D> ? GuardSlots<D> : never;
|
|
123
|
+
/** Extract effect slots type from EffectsSchema */
|
|
124
|
+
type EffectSlotsOf<E> = E extends EffectsSchema<infer D> ? EffectSlots<D> : never;
|
|
125
|
+
declare const Slot: {
|
|
126
|
+
readonly Guards: <D extends GuardsDef>(definitions: D) => GuardsSchema<D>;
|
|
127
|
+
readonly Effects: <D extends EffectsDef>(definitions: D) => EffectsSchema<D>;
|
|
128
|
+
};
|
|
129
|
+
//#endregion
|
|
130
|
+
export { EffectHandler, EffectHandlers, EffectSlot, EffectSlots, EffectSlotsOf, Effects, EffectsDef, EffectsDefOf, EffectsSchema, GuardHandler, GuardHandlers, GuardSlot, GuardSlots, GuardSlotsOf, Guards, GuardsDef, GuardsDefOf, GuardsSchema, MachineContext, MachineContextTag, Slot };
|
package/dist/slot.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/slot.ts
|
|
4
|
+
/**
|
|
5
|
+
* Slot module - schema-based, parameterized guards and effects.
|
|
6
|
+
*
|
|
7
|
+
* Guards and Effects are defined with schemas for their parameters,
|
|
8
|
+
* and provided implementations receive typed parameters plus machine context.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { Slot } from "effect-machine"
|
|
13
|
+
* import { Schema } from "effect"
|
|
14
|
+
*
|
|
15
|
+
* const MyGuards = Slot.Guards({
|
|
16
|
+
* canRetry: { max: Schema.Number },
|
|
17
|
+
* isValid: {}, // no params
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* const MyEffects = Slot.Effects({
|
|
21
|
+
* fetchData: { url: Schema.String },
|
|
22
|
+
* notify: { message: Schema.String },
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // Used in handlers:
|
|
26
|
+
* .on(State.X, Event.Y, ({ guards, effects }) =>
|
|
27
|
+
* Effect.gen(function* () {
|
|
28
|
+
* if (yield* guards.canRetry({ max: 3 })) {
|
|
29
|
+
* yield* effects.fetchData({ url: "/api" })
|
|
30
|
+
* return State.Next
|
|
31
|
+
* }
|
|
32
|
+
* return state
|
|
33
|
+
* })
|
|
34
|
+
* )
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @module
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Shared Context tag for all machines.
|
|
41
|
+
* Single module-level tag instead of per-machine allocation.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
const MachineContextTag = Context.GenericTag("@effect-machine/Context");
|
|
45
|
+
/**
|
|
46
|
+
* Generic slot schema factory. Used internally by Guards() and Effects().
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
const createSlotSchema = (tag, slotTag, definitions) => ({
|
|
50
|
+
_tag: tag,
|
|
51
|
+
definitions,
|
|
52
|
+
_createSlots: (resolve) => {
|
|
53
|
+
const slots = {};
|
|
54
|
+
for (const name of Object.keys(definitions)) {
|
|
55
|
+
const slot = (params) => resolve(name, params);
|
|
56
|
+
Object.defineProperty(slot, "_tag", {
|
|
57
|
+
value: slotTag,
|
|
58
|
+
enumerable: true
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(slot, "name", {
|
|
61
|
+
value: name,
|
|
62
|
+
enumerable: true
|
|
63
|
+
});
|
|
64
|
+
slots[name] = slot;
|
|
65
|
+
}
|
|
66
|
+
return slots;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Create a guards schema with parameterized guard definitions.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const MyGuards = Slot.Guards({
|
|
75
|
+
* canRetry: { max: Schema.Number },
|
|
76
|
+
* isValid: {},
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
const Guards = (definitions) => createSlotSchema("GuardsSchema", "GuardSlot", definitions);
|
|
81
|
+
/**
|
|
82
|
+
* Create an effects schema with parameterized effect definitions.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const MyEffects = Slot.Effects({
|
|
87
|
+
* fetchData: { url: Schema.String },
|
|
88
|
+
* notify: { message: Schema.String },
|
|
89
|
+
* })
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
const Effects = (definitions) => createSlotSchema("EffectsSchema", "EffectSlot", definitions);
|
|
93
|
+
const Slot = {
|
|
94
|
+
Guards,
|
|
95
|
+
Effects
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { Effects, Guards, MachineContextTag, Slot };
|