effect-machine 0.16.0 → 0.17.1

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 (44) hide show
  1. package/README.md +4 -0
  2. package/dist/actor.d.ts +9 -7
  3. package/dist/actor.js +2 -2
  4. package/dist/cluster/adapters/in-memory.d.ts +4 -4
  5. package/dist/cluster/adapters/in-memory.js +2 -2
  6. package/dist/cluster/entity-machine.d.ts +2 -2
  7. package/dist/cluster/index.d.ts +2 -2
  8. package/dist/cluster/index.js +2 -2
  9. package/dist/cluster/persistence.d.ts +4 -3
  10. package/dist/cluster/persistence.js +1 -1
  11. package/dist/cluster/to-entity.d.ts +2 -2
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +1 -1
  14. package/dist/inspection.d.ts +11 -10
  15. package/dist/inspection.js +9 -23
  16. package/dist/internal/inspection.d.ts +2 -2
  17. package/dist/internal/runtime.d.ts +7 -7
  18. package/dist/internal/runtime.js +9 -14
  19. package/dist/internal/transition.d.ts +9 -9
  20. package/dist/internal/utils.d.ts +3 -3
  21. package/dist/internal/utils.js +1 -1
  22. package/dist/machine.d.ts +4 -4
  23. package/dist/machine.js +20 -2
  24. package/dist/schema.d.ts +24 -13
  25. package/dist/schema.js +8 -8
  26. package/dist/slot.d.ts +7 -5
  27. package/dist/slot.js +38 -3
  28. package/dist/testing.d.ts +7 -7
  29. package/package.json +15 -19
  30. package/v3/dist/actor.d.ts +1 -1
  31. package/v3/dist/cluster/entity-machine.d.ts +1 -1
  32. package/v3/dist/cluster/entity-machine.js +4 -3
  33. package/v3/dist/cluster/index.js +2 -2
  34. package/v3/dist/cluster/to-entity.d.ts +8 -3
  35. package/v3/dist/index.js +1 -1
  36. package/v3/dist/inspection.d.ts +2 -2
  37. package/v3/dist/inspection.js +8 -22
  38. package/v3/dist/internal/runtime.d.ts +5 -5
  39. package/v3/dist/internal/runtime.js +9 -14
  40. package/v3/dist/machine.js +2 -1
  41. package/v3/dist/schema.d.ts +25 -12
  42. package/v3/dist/schema.js +8 -7
  43. package/v3/dist/slot.d.ts +3 -2
  44. package/v3/dist/slot.js +37 -2
package/dist/schema.d.ts CHANGED
@@ -49,16 +49,23 @@ type VariantReplyBrand<Fields extends Schema.Struct.Fields> = Fields extends {
49
49
  * Empty structs: plain values with `_tag`: `State.Idle`
50
50
  * Non-empty structs require args: `State.Loading({ url })`
51
51
  *
52
- * Each variant also has a `derive` method for constructing from a source object.
52
+ * Each variant also has a `with` method for constructing from a source object,
53
+ * copying matching fields and overriding with a partial.
53
54
  * The source type uses `object` to accept branded state types without index signature issues.
54
55
  * Reply-bearing variants carry ReplyTypeBrand<R> for ask() type inference.
55
56
  */
56
57
  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 & VariantReplyBrand<D[K]> & {
57
- readonly derive: (source: object) => TaggedStructType<K, D[K]> & Brand;
58
+ readonly with: (source: object) => TaggedStructType<K, D[K]> & Brand;
58
59
  } : ((args: Schema.Struct.Type<PayloadFields<D[K]>>) => TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]>) & {
59
- readonly derive: (source: object, partial?: Partial<Schema.Struct.Type<PayloadFields<D[K]>>>) => TaggedStructType<K, D[K]> & Brand;
60
+ readonly with: (source: object, partial?: Partial<Schema.Struct.Type<PayloadFields<D[K]>>>) => TaggedStructType<K, D[K]> & Brand;
60
61
  readonly _tag: K;
61
62
  } };
63
+ /**
64
+ * Keys present in ALL variants (intersection of field names).
65
+ * Used by union-level `with` to accept only fields safe to update
66
+ * regardless of which variant the source is.
67
+ */
68
+ type SharedKeys<D extends Record<string, Schema.Struct.Fields>> = keyof D[keyof D & string] & string;
62
69
  /**
63
70
  * Pattern matching cases type
64
71
  */
@@ -87,19 +94,23 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
87
94
  <R>(value: VariantsUnion<D> & Brand, cases: MatchCases<D, R>): R;
88
95
  };
89
96
  /**
90
- * Union-level derive: copies fields from `source` into the same variant,
91
- * overriding with `partial`. Preserves the specific variant subtype.
97
+ * Copy fields from `source` into the same variant, overriding with `partial`.
98
+ * Preserves the specific variant subtype in the return.
92
99
  *
93
- * Dispatches to the per-variant `derive` based on `source._tag`.
100
+ * The partial accepts fields common to all variants, so it works safely
101
+ * when `S` is a generic type parameter (e.g., `<S extends MyState>`).
94
102
  *
95
103
  * @example
96
104
  * ```ts
97
- * // Instead of switching on _tag to call per-variant derive:
98
- * const updated = AgentLoopState.derive(state, { queue: newQueue })
99
- * // If state is StreamingState, returns StreamingState (not LoopState)
105
+ * // Per-variant field update partial accepts that variant's fields
106
+ * const next = MyState.Streaming.with(state, { draft: newDraft })
107
+ *
108
+ * // Cross-variant shared field — works with generic state
109
+ * const updateQueue = <S extends MyState>(state: S, queue: Queue): S =>
110
+ * MyState.with(state, { queue })
100
111
  * ```
101
112
  */
102
- readonly derive: <S extends VariantsUnion<D> & Brand>(source: S, partial?: Partial<Omit<S, "_tag">>) => S;
113
+ readonly with: <S extends VariantsUnion<D> & Brand>(source: S, partial?: Partial<Record<SharedKeys<D>, unknown>>) => S;
103
114
  /**
104
115
  * Reply schemas per variant tag. Only populated for event schemas
105
116
  * with variants defined via `Event.reply()`.
@@ -116,8 +127,8 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
116
127
  * The D type parameter captures the definition, creating a unique brand
117
128
  * per distinct schema definition shape.
118
129
  */
119
- type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Codec<VariantsUnion<D> & FullStateBrand<D>, unknown, never, never> & MachineSchemaBase<D, FullStateBrand<D>> & VariantConstructors<D, FullStateBrand<D>> & {
120
- /** Unbranded schema for persistence same structure without FullStateBrand. */readonly plain: Schema.Schema<VariantsUnion<D>>;
130
+ type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Codec<VariantsUnion<D> & FullStateBrand<D>, unknown> & MachineSchemaBase<D, FullStateBrand<D>> & VariantConstructors<D, FullStateBrand<D>> & {
131
+ /** Schema for persistence, config, and registration. */readonly schema: Schema.Schema<VariantsUnion<D> & FullStateBrand<D>>;
121
132
  };
122
133
  /**
123
134
  * Schema-first event definition (same structure as state, different brand)
@@ -125,7 +136,7 @@ type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema
125
136
  * The D type parameter captures the definition, creating a unique brand
126
137
  * per distinct schema definition shape.
127
138
  */
128
- type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Codec<VariantsUnion<D> & FullEventBrand<D>, unknown, never, never> & MachineSchemaBase<D, FullEventBrand<D>> & VariantConstructors<D, FullEventBrand<D>>;
139
+ type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Codec<VariantsUnion<D> & FullEventBrand<D>, unknown> & MachineSchemaBase<D, FullEventBrand<D>> & VariantConstructors<D, FullEventBrand<D>>;
129
140
  /**
130
141
  * Create a schema-first State definition.
131
142
  *
package/dist/schema.js CHANGED
@@ -62,7 +62,7 @@ const buildMachineSchema = (definition) => {
62
62
  _tag: tag
63
63
  });
64
64
  constructor._tag = tag;
65
- constructor.derive = (source, partial) => {
65
+ constructor.with = (source, partial) => {
66
66
  const result = { _tag: tag };
67
67
  for (const key of fieldNames) if (key in source) result[key] = source[key];
68
68
  if (partial !== void 0) for (const [key, value] of Object.entries(partial)) {
@@ -75,7 +75,7 @@ const buildMachineSchema = (definition) => {
75
75
  constructors[tag] = constructor;
76
76
  } else constructors[tag] = {
77
77
  _tag: tag,
78
- derive: () => ({ _tag: tag })
78
+ with: () => ({ _tag: tag })
79
79
  };
80
80
  }
81
81
  const variantArray = Object.values(variants);
@@ -112,21 +112,21 @@ const buildMachineSchema = (definition) => {
112
112
  */
113
113
  const createMachineSchema = (definition) => {
114
114
  const { schema, variants, constructors, _definition, replySchemas, $is, $match } = buildMachineSchema(definition);
115
- const derive = (source, partial) => {
115
+ const withFn = (source, partial) => {
116
116
  const ctor = constructors[source._tag];
117
117
  if (ctor === void 0) throw new MissingMatchHandlerError({ tag: source._tag });
118
- const deriveFn = ctor.derive;
119
- if (deriveFn === void 0) throw new MissingMatchHandlerError({ tag: source._tag });
120
- return deriveFn(source, partial);
118
+ const fn = ctor.with;
119
+ if (fn === void 0) throw new MissingMatchHandlerError({ tag: source._tag });
120
+ return fn(source, partial);
121
121
  };
122
122
  return Object.assign(Object.create(schema), {
123
123
  variants,
124
124
  _definition,
125
125
  _replySchemas: replySchemas,
126
- plain: schema,
126
+ schema,
127
127
  $is,
128
128
  $match,
129
- derive,
129
+ with: withFn,
130
130
  ...constructors
131
131
  });
132
132
  };
package/dist/slot.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ActorSystem } from "./actor.js";
1
+ import { ActorSystemService } from "./actor.js";
2
2
  import { Context, Effect, Schema } from "effect";
3
3
 
4
4
  //#region src/slot.d.ts
@@ -40,7 +40,7 @@ interface SlotFnDef<F extends Fields = Fields, Return = void> {
40
40
  */
41
41
  declare const fn: {
42
42
  <F extends Fields, Return>(fields: F, returnSchema: Schema.Schema<Return>): SlotFnDef<F, Return>;
43
- <F extends Fields>(fields: F): SlotFnDef<F, void>;
43
+ <F extends Fields>(fields: F): SlotFnDef<F>;
44
44
  };
45
45
  /**
46
46
  * Record of slot definitions. Keys are slot names, values are SlotFnDef.
@@ -125,14 +125,15 @@ interface MachineContext<State, Event, Self> {
125
125
  readonly state: State;
126
126
  readonly event: Event;
127
127
  readonly self: Self;
128
- readonly system: ActorSystem;
128
+ readonly system: ActorSystemService;
129
129
  }
130
+ declare const MachineContextTag_base: Context.ServiceClass<MachineContextTag, "effect-machine/slot/MachineContextTag", MachineContext<any, any, any>>;
130
131
  /**
131
132
  * Shared Context tag for all machines.
132
133
  * Single module-level tag instead of per-machine allocation.
133
134
  * @internal
134
135
  */
135
- declare const MachineContextTag: Context.Service<MachineContext<any, any, any>, MachineContext<any, any, any>>;
136
+ declare class MachineContextTag extends MachineContextTag_base {}
136
137
  /**
137
138
  * Define a set of slots with parameter and return schemas.
138
139
  *
@@ -149,9 +150,10 @@ declare const define: <D extends SlotsDef>(definitions: D) => SlotsSchema<D>;
149
150
  declare const Slot: {
150
151
  readonly fn: {
151
152
  <F extends Fields, Return>(fields: F, returnSchema: Schema.Schema<Return>): SlotFnDef<F, Return>;
152
- <F extends Fields>(fields: F): SlotFnDef<F, void>;
153
+ <F extends Fields>(fields: F): SlotFnDef<F>;
153
154
  };
154
155
  readonly define: <D extends SlotsDef>(definitions: D) => SlotsSchema<D>;
156
+ readonly of: <D extends SlotsDef>(slotsSchema: SlotsSchema<D>, provided: ProvideSlots<D>) => SlotCalls<D>;
155
157
  };
156
158
  //#endregion
157
159
  export { HasSlotKeys, MachineContext, MachineContextTag, ProvideSlots, Slot, SlotCall, SlotCalls, SlotFnDef, SlotHandler, SlotInvocation, SlotRequest, SlotResult, SlotsDef, SlotsSchema, define, fn };
package/dist/slot.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, Schema } from "effect";
1
+ import { Context, Effect, Schema } from "effect";
2
2
  //#region src/slot.ts
3
3
  /**
4
4
  * Slot module — unified, schema-based parameterized slots.
@@ -62,7 +62,7 @@ const fn = (fields, returnSchema) => {
62
62
  * Single module-level tag instead of per-machine allocation.
63
63
  * @internal
64
64
  */
65
- const MachineContextTag = Context.Service("@effect-machine/Context");
65
+ var MachineContextTag = class extends Context.Service()("effect-machine/slot/MachineContextTag") {};
66
66
  /**
67
67
  * Define a set of slots with parameter and return schemas.
68
68
  *
@@ -122,9 +122,44 @@ const define = (definitions) => {
122
122
  }
123
123
  };
124
124
  };
125
+ /**
126
+ * Convert raw slot handler implementations into the callable `SlotCalls` form.
127
+ *
128
+ * Handlers that return plain values are wrapped in `Effect.succeed`.
129
+ * Handlers that return Effects are called directly inside `Effect.suspend`.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const provided = yield* myExtension.slots(ctx)
134
+ * const slots = Slot.of(slotsSchema, provided)
135
+ * // slots.mySlot({ param: 1 }) returns Effect<ReturnType>
136
+ * ```
137
+ */
138
+ const of = (slotsSchema, provided) => {
139
+ const slots = {};
140
+ for (const name of Object.keys(slotsSchema.definitions)) {
141
+ const handler = provided[name];
142
+ if (handler === void 0) continue;
143
+ const call = (params) => Effect.suspend(() => {
144
+ const result = handler(params);
145
+ return Effect.isEffect(result) ? result : Effect.succeed(result);
146
+ });
147
+ Object.defineProperty(call, "_tag", {
148
+ value: "Slot",
149
+ enumerable: true
150
+ });
151
+ Object.defineProperty(call, "name", {
152
+ value: name,
153
+ enumerable: true
154
+ });
155
+ slots[name] = call;
156
+ }
157
+ return slots;
158
+ };
125
159
  const Slot = {
126
160
  fn,
127
- define
161
+ define,
162
+ of
128
163
  };
129
164
  //#endregion
130
165
  export { MachineContextTag, Slot, define, fn };
package/dist/testing.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AssertionError } from "./errors.js";
2
- import { MachineContext, ProvideSlots, SlotsDef } from "./slot.js";
3
- import { Machine, MachineRef } from "./machine.js";
2
+ import { MachineContextTag, ProvideSlots, SlotsDef } from "./slot.js";
3
+ import { Machine } from "./machine.js";
4
4
  import { Effect, SubscriptionRef } from "effect";
5
5
 
6
6
  //#region src/testing.d.ts
@@ -41,7 +41,7 @@ declare const simulate: <S extends {
41
41
  } | undefined) => Effect.Effect<{
42
42
  states: S[];
43
43
  finalState: S;
44
- }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
44
+ }, never, Exclude<R, MachineContextTag>>;
45
45
  /**
46
46
  * Assert that a machine can reach a specific state given a sequence of events
47
47
  */
@@ -51,7 +51,7 @@ declare const assertReaches: <S extends {
51
51
  readonly _tag: string;
52
52
  }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], expectedTag: string, options?: {
53
53
  slots?: ProvideSlots<SD, any>;
54
- } | undefined) => Effect.Effect<S, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
54
+ } | undefined) => Effect.Effect<S, AssertionError, Exclude<R, MachineContextTag>>;
55
55
  /**
56
56
  * Assert that a machine follows a specific path of state tags
57
57
  *
@@ -73,7 +73,7 @@ declare const assertPath: <S extends {
73
73
  } | undefined) => Effect.Effect<{
74
74
  states: S[];
75
75
  finalState: S;
76
- }, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
76
+ }, AssertionError, Exclude<R, MachineContextTag>>;
77
77
  /**
78
78
  * Assert that a machine never reaches a specific state given a sequence of events
79
79
  *
@@ -96,7 +96,7 @@ declare const assertNeverReaches: <S extends {
96
96
  } | undefined) => Effect.Effect<{
97
97
  states: S[];
98
98
  finalState: S;
99
- }, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
99
+ }, AssertionError, Exclude<R, MachineContextTag>>;
100
100
  /**
101
101
  * Create a controllable test harness for a machine
102
102
  */
@@ -144,7 +144,7 @@ declare const createTestHarness: <S extends {
144
144
  readonly _tag: string;
145
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
- send: (event: E) => Effect.Effect<S, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
147
+ send: (event: E) => Effect.Effect<S, never, Exclude<R, MachineContextTag>>;
148
148
  getState: Effect.Effect<S, never, never>;
149
149
  }, never, never>;
150
150
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-machine",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cevr/effect-machine.git"
@@ -40,11 +40,9 @@
40
40
  "access": "public"
41
41
  },
42
42
  "scripts": {
43
- "typecheck": "tsgo --noEmit",
44
- "typecheck:v3": "tsc --noEmit -p v3/tsconfig.json",
45
- "lint": "concurrently -n ox,effect -c yellow,blue \"oxlint\" \"bun run lint:effect\"",
46
- "lint:ox": "oxlint",
47
- "lint:effect": "effect-language-service diagnostics --project tsconfig.json --lspconfig \"$(cat .effect-lsp.json)\"",
43
+ "typecheck": "concurrently -n v4,v3 -c blue,magenta \"tsgo --noEmit\" \"bun run typecheck:v3\"",
44
+ "typecheck:v3": "tsgo --noEmit -p v3/tsconfig.json",
45
+ "lint": "oxlint",
48
46
  "lint:fix": "oxlint --fix",
49
47
  "fmt": "oxfmt",
50
48
  "fmt:check": "oxfmt --check",
@@ -53,38 +51,36 @@
53
51
  "test:watch": "bun test --watch",
54
52
  "test:all": "bun run test && bun run test:v3",
55
53
  "gate": "concurrently -n type,lint,fmt,test,build -c blue,yellow,magenta,green,cyan \"bun run typecheck\" \"bun run lint:fix\" \"bun run fmt\" \"bun run test:all\" \"bun run build\"",
56
- "prepare": "lefthook install",
54
+ "prepare": "lefthook install && effect-tsgo patch",
57
55
  "version": "changeset version",
58
- "build": "concurrently -n v4,v3 -c cyan,magenta \"tsdown\" \"tsdown --config v3/tsdown.config.ts\"",
56
+ "build": "concurrently -n v4,v3 -c cyan,magenta \"bun --bun tsdown\" \"bun --bun tsdown --config v3/tsdown.config.ts\"",
59
57
  "release": "bun run build && changeset publish"
60
58
  },
61
- "dependencies": {
62
- "effect": "4.0.0-beta.47"
63
- },
64
59
  "devDependencies": {
65
60
  "@changesets/changelog-github": "^0.6.0",
66
61
  "@changesets/cli": "^2.30.0",
67
- "@effect/language-service": "^0.85.1",
62
+ "@effect/cluster": "0.58.2",
63
+ "@effect/rpc": "0.75.1",
64
+ "@effect/tsgo": "0.5.2",
68
65
  "@types/bun": "1.3.12",
69
- "@typescript/native-preview": "^7.0.0-dev.20260412.1",
66
+ "@typescript/native-preview": "7.0.0-dev.20260507.1",
70
67
  "concurrently": "^9.2.1",
68
+ "effect": "4.0.0-beta.64",
71
69
  "effect-bun-test": "0.3.0",
72
- "effect-v3": "npm:effect@^3.21.0",
70
+ "effect-v3": "npm:effect@3.21.2",
73
71
  "lefthook": "^2.1.5",
74
72
  "oxfmt": "^0.44.0",
75
- "oxlint": "^1.59.0",
73
+ "oxlint": "^1.63.0",
74
+ "oxlint-tsgolint": "^0.22.1",
76
75
  "tsdown": "^0.21.7",
77
76
  "typescript": "^6.0.2"
78
77
  },
79
78
  "peerDependencies": {
80
- "effect": ">=4.0.0-beta.47"
79
+ "effect": ">=4.0.0-beta.64"
81
80
  },
82
81
  "peerDependenciesMeta": {
83
82
  "effect": {
84
83
  "optional": false
85
84
  }
86
- },
87
- "overrides": {
88
- "effect": "4.0.0-beta.47"
89
85
  }
90
86
  }
@@ -220,7 +220,7 @@ declare const buildActorRefCore: <S extends {
220
220
  readonly _tag: string;
221
221
  }, E extends {
222
222
  readonly _tag: string;
223
- }, R, SD extends SlotsDef>(id: string, machine: Machine<S, E, R, any, any, SD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueueRef: Ref.Ref<Queue.Queue<QueuedEvent<E>>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, start: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, transitionsPubSub: PubSub.PubSub<TransitionInfo<S, E>> | undefined, exitDeferred: Deferred.Deferred<ActorExit<S>, never>) => ActorRef<S, E>;
223
+ }, R, SD extends SlotsDef>(id: string, machine: Machine<S, E, R, any, any, SD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueueRef: Ref.Ref<Queue.Queue<QueuedEvent<E>>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, start: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, transitionsPubSub: PubSub.PubSub<TransitionInfo<S, E>> | undefined, exitDeferred: Deferred.Deferred<ActorExit<S>>) => ActorRef<S, E>;
224
224
  /**
225
225
  * Create and start an actor for a machine.
226
226
  * Delegates to the shared runtime kernel with actor-specific lifecycle hooks.
@@ -43,7 +43,7 @@ interface EntityMachineOptions<S, E> {
43
43
  * Retry policy for defects (schedule for restarting after defect).
44
44
  * Forwarded to Entity.toLayer.
45
45
  */
46
- readonly defectRetryPolicy?: Schedule.Schedule<any, unknown>;
46
+ readonly defectRetryPolicy?: Schedule.Schedule<any>;
47
47
  /**
48
48
  * Persistence configuration. When set, requires PersistenceAdapter in R.
49
49
  */
@@ -3,7 +3,7 @@ import { replay } from "../machine.js";
3
3
  import { createRuntime } from "../internal/runtime.js";
4
4
  import { ActorSystem } from "../actor.js";
5
5
  import { PersistenceAdapter } from "./persistence.js";
6
- import { Effect, Option, Ref } from "effect";
6
+ import { Clock, Effect, Option, Ref } from "effect";
7
7
  import { Entity } from "@effect/cluster";
8
8
  //#region src/cluster/entity-machine.ts
9
9
  /**
@@ -56,10 +56,11 @@ const EntityMachine = { layer: (entity, machine, options) => {
56
56
  yield* Effect.addFinalizer(() => Effect.gen(function* () {
57
57
  const state = yield* runtime.getState;
58
58
  const version = yield* Ref.get(versionRef);
59
+ const now = yield* Clock.currentTimeMillis;
59
60
  yield* pAdapter.saveSnapshot(key, {
60
61
  state,
61
62
  version,
62
- timestamp: Date.now()
63
+ timestamp: now
63
64
  });
64
65
  }).pipe(Effect.catchAll(() => Effect.void)));
65
66
  }
@@ -153,7 +154,7 @@ const persistEvent = (adapter, key, versionRef, event) => Effect.gen(function* (
153
154
  const persisted = {
154
155
  event,
155
156
  version: newVersion,
156
- timestamp: Date.now()
157
+ timestamp: yield* Clock.currentTimeMillis
157
158
  };
158
159
  yield* adapter.appendEvents(key, [persisted], expectedVersion);
159
160
  yield* Ref.set(versionRef, newVersion);
@@ -1,6 +1,6 @@
1
- import { makeEntityActorRef } from "./entity-actor-ref.js";
2
1
  import { PersistenceAdapter } from "./persistence.js";
3
- import { EntityMachine } from "./entity-machine.js";
4
2
  import { toEntity } from "./to-entity.js";
3
+ import { EntityMachine } from "./entity-machine.js";
4
+ import { makeEntityActorRef } from "./entity-actor-ref.js";
5
5
  import { makeInMemoryPersistenceAdapter } from "./adapters/in-memory.js";
6
6
  export { EntityMachine, PersistenceAdapter, makeEntityActorRef, makeInMemoryPersistenceAdapter, toEntity };
@@ -1,5 +1,6 @@
1
1
  import { Machine } from "../machine.js";
2
2
  import { Schema } from "effect";
3
+ import { Entity } from "@effect/cluster";
3
4
  import { Rpc } from "@effect/rpc";
4
5
 
5
6
  //#region src/cluster/to-entity.d.ts
@@ -21,9 +22,9 @@ interface ToEntityOptions {
21
22
  */
22
23
  type EntityRpcs<StateSchema extends Schema.Schema.Any, EventSchema extends Schema.Schema.Any> = readonly [Rpc.Rpc<"Send", Schema.Struct<{
23
24
  readonly event: EventSchema;
24
- }>, StateSchema, typeof Schema.Never, never>, Rpc.Rpc<"Ask", Schema.Struct<{
25
+ }>, StateSchema, typeof Schema.Never>, Rpc.Rpc<"Ask", Schema.Struct<{
25
26
  readonly event: EventSchema;
26
- }>, typeof Schema.Unknown, typeof Schema.Never, never>, Rpc.Rpc<"GetState", typeof Schema.Void, StateSchema, typeof Schema.Never, never>];
27
+ }>, typeof Schema.Unknown, typeof Schema.Never>, Rpc.Rpc<"GetState", typeof Schema.Void, StateSchema, typeof Schema.Never>];
27
28
  /**
28
29
  * Generate an Entity definition from a machine.
29
30
  *
@@ -59,6 +60,10 @@ declare const toEntity: <S extends {
59
60
  readonly _tag: string;
60
61
  }, E extends {
61
62
  readonly _tag: string;
62
- }, R>(machine: Machine<S, E, R, any, any, any>, options: ToEntityOptions) => any;
63
+ }, R>(machine: Machine<S, E, R, any, any, any>, options: ToEntityOptions) => Entity.Entity<string, Rpc.Rpc<"Send", Schema.Struct<{
64
+ event: Schema.Schema<E, E, never>;
65
+ }>, Schema.Schema<S, S, never>, typeof Schema.Never, never> | Rpc.Rpc<"Ask", Schema.Struct<{
66
+ event: Schema.Schema<E, E, never>;
67
+ }>, typeof Schema.Unknown, typeof Schema.Never, never> | Rpc.Rpc<"GetState", typeof Schema.Void, Schema.Schema<S, S, never>, typeof Schema.Never, never>>;
63
68
  //#endregion
64
69
  export { EntityRpcs, ToEntityOptions, toEntity };
package/v3/dist/index.js CHANGED
@@ -4,6 +4,6 @@ import { Slot } from "./slot.js";
4
4
  import { machine_exports } from "./machine.js";
5
5
  import { ActorExit, Supervision } from "./supervision.js";
6
6
  import { ActorScope, ActorSystem, Default } from "./actor.js";
7
- import { Event, State } from "./schema.js";
8
7
  import { assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
8
+ import { Event, State } from "./schema.js";
9
9
  export { ActorExit, ActorScope, ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, Slot, SlotCodecError, SlotProvisionError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
@@ -93,7 +93,7 @@ type AnyInspectionEvent = InspectionEvent<{
93
93
  /**
94
94
  * Inspector interface for observing machine behavior
95
95
  */
96
- type InspectorHandler<S, E> = (event: InspectionEvent<S, E>) => void | Effect.Effect<void, never, never>;
96
+ type InspectorHandler<S, E> = (event: InspectionEvent<S, E>) => void | Effect.Effect<void>;
97
97
  interface Inspector<S, E> {
98
98
  readonly onInspect: InspectorHandler<S, E>;
99
99
  }
@@ -119,7 +119,7 @@ declare const makeInspectorEffect: <S = {
119
119
  readonly _tag: string;
120
120
  }, E = {
121
121
  readonly _tag: string;
122
- }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void, never, never>) => Inspector<ResolveType<S>, ResolveType<E>>;
122
+ }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void>) => Inspector<ResolveType<S>, ResolveType<E>>;
123
123
  declare const combineInspectors: <S, E>(...inspectors: ReadonlyArray<Inspector<S, E>>) => Inspector<S, E>;
124
124
  interface TracingInspectorOptions<S, E> {
125
125
  readonly spanName?: string | ((event: InspectionEvent<S, E>) => string);
@@ -106,30 +106,16 @@ const tracingInspector = (options) => ({ onInspect: (event) => {
106
106
  /**
107
107
  * Console inspector that logs events in a readable format
108
108
  */
109
- const consoleInspector = () => makeInspector((event) => {
109
+ const consoleInspector = () => makeInspectorEffect((event) => {
110
110
  const prefix = `[${event.actorId}]`;
111
111
  switch (event.type) {
112
- case "@machine.spawn":
113
- console.log(prefix, "spawned →", event.initialState._tag);
114
- break;
115
- case "@machine.event":
116
- console.log(prefix, "received", event.event._tag, "in", event.state._tag);
117
- break;
118
- case "@machine.transition":
119
- console.log(prefix, event.fromState._tag, "→", event.toState._tag);
120
- break;
121
- case "@machine.effect":
122
- console.log(prefix, event.effectType, "effect in", event.state._tag);
123
- break;
124
- case "@machine.task":
125
- console.log(prefix, "task", event.phase, event.taskName ?? "<unnamed>", "in", event.state._tag);
126
- break;
127
- case "@machine.error":
128
- console.log(prefix, "error in", event.phase, event.state._tag, "-", event.error);
129
- break;
130
- case "@machine.stop":
131
- console.log(prefix, "stopped in", event.finalState._tag);
132
- break;
112
+ case "@machine.spawn": return Effect.log(`${prefix} spawned -> ${event.initialState._tag}`);
113
+ case "@machine.event": return Effect.log(`${prefix} received ${event.event._tag} in ${event.state._tag}`);
114
+ case "@machine.transition": return Effect.log(`${prefix} ${event.fromState._tag} -> ${event.toState._tag}`);
115
+ case "@machine.effect": return Effect.log(`${prefix} ${event.effectType} effect in ${event.state._tag}`);
116
+ case "@machine.task": return Effect.log(`${prefix} task ${event.phase} ${event.taskName ?? "<unnamed>"} in ${event.state._tag}`);
117
+ case "@machine.error": return Effect.log(`${prefix} error in ${event.phase} ${event.state._tag} - ${String(event.error)}`);
118
+ case "@machine.stop": return Effect.log(`${prefix} stopped in ${event.finalState._tag}`);
133
119
  }
134
120
  });
135
121
  /**
@@ -1,8 +1,8 @@
1
1
  import { NoReplyError } from "../errors.js";
2
- import { MachineContext, SlotsDef } from "../slot.js";
2
+ import { SlotsDef } from "../slot.js";
3
3
  import { ActorExit } from "../supervision.js";
4
4
  import { ProcessEventHooks, ProcessEventResult } from "./transition.js";
5
- import { Machine, MachineRef } from "../machine.js";
5
+ import { Machine } from "../machine.js";
6
6
  import { ActorSystem } from "../actor.js";
7
7
  import { Deferred, Effect, Queue, Ref, Scope, SubscriptionRef } from "effect";
8
8
 
@@ -27,7 +27,7 @@ type RuntimeQueuedEvent<E> = {
27
27
  readonly reply: Deferred.Deferred<unknown, NoReplyError>;
28
28
  } | {
29
29
  readonly _tag: "drain";
30
- readonly done: Deferred.Deferred<void, never>;
30
+ readonly done: Deferred.Deferred<void>;
31
31
  };
32
32
  /**
33
33
  * Resources owned by the actor cell (stable across generations).
@@ -69,7 +69,7 @@ interface RuntimeHandle<S, E> {
69
69
  * Exit deferred — set exactly once with the exit reason when the runtime stops.
70
70
  * Final state → ActorExit.Final, explicit stop → ActorExit.Stopped, defect → ActorExit.Defect.
71
71
  */
72
- readonly exitDeferred: Deferred.Deferred<ActorExit<S>, never>;
72
+ readonly exitDeferred: Deferred.Deferred<ActorExit<S>>;
73
73
  /**
74
74
  * Actor scope — owns background fibers for this generation.
75
75
  * Closing this scope interrupts all background fibers.
@@ -145,7 +145,7 @@ declare const createRuntime: <S extends {
145
145
  readonly _tag: string;
146
146
  }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, system: ActorSystem, config: RuntimeConfig<S, E>) => Effect.Effect<{
147
147
  stop: Effect.Effect<void, never, never>;
148
- start: Effect.Effect<void, unknown, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
148
+ start: Effect.Effect<void, unknown, unknown>;
149
149
  send: (event: E) => Effect.Effect<void>;
150
150
  sendWait: (event: E) => Effect.Effect<void, unknown>;
151
151
  ask: (event: E) => Effect.Effect<unknown, NoReplyError>;
@@ -105,15 +105,13 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
105
105
  }
106
106
  if (lifecycle?.onInitialSpawnEffects !== void 0) yield* lifecycle.onInitialSpawnEffects(machine.initial);
107
107
  const initialSpawnDefectSignal = (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid);
108
- yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchAllCause((cause) => {
109
- return Effect.gen(function* () {
110
- yield* Ref.set(stoppedRef, true);
111
- yield* Scope.close(stateScopeRef.current, Exit.void);
112
- yield* Scope.close(actorScope, Exit.void);
113
- yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
114
- return yield* Effect.failCause(cause);
115
- });
116
- }));
108
+ yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchAllCause((cause) => Effect.gen(function* () {
109
+ yield* Ref.set(stoppedRef, true);
110
+ yield* Scope.close(stateScopeRef.current, Exit.void);
111
+ yield* Scope.close(actorScope, Exit.void);
112
+ yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
113
+ return yield* Effect.failCause(cause);
114
+ })));
117
115
  if (machine.finalStates.has(machine.initial._tag)) {
118
116
  if (lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(machine.initial);
119
117
  yield* Ref.set(stoppedRef, true);
@@ -262,13 +260,10 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
262
260
  if (result.hasReply) {
263
261
  const replySchema = machine._replySchemas?.get(event._tag);
264
262
  if (replySchema !== void 0) {
265
- let decoded;
266
- try {
267
- decoded = Schema.decodeUnknownSync(replySchema)(result.reply);
268
- } catch (decodeError) {
263
+ const decoded = yield* Schema.decodeUnknown(replySchema)(result.reply).pipe(Effect.catchAll((decodeError) => Effect.gen(function* () {
269
264
  yield* Deferred.die(queued.reply, decodeError);
270
265
  return yield* Effect.die(decodeError);
271
- }
266
+ })));
272
267
  yield* Deferred.succeed(queued.reply, decoded);
273
268
  } else yield* Deferred.succeed(queued.reply, result.reply);
274
269
  } else if (result.deferReply && deferredReplyRef !== void 0) deferredReplyRef.current = queued.reply;
@@ -178,7 +178,8 @@ var Machine = class Machine {
178
178
  this._slots = this._slotsSchema !== void 0 ? this._slotsSchema._createSlots(resolve) : {};
179
179
  }
180
180
  from(stateOrStates, build) {
181
- build(new TransitionScope(this, Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates]));
181
+ const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
182
+ build(new TransitionScope(this, states));
182
183
  return this;
183
184
  }
184
185
  /** @internal */