@typeonce/effect-machine 0.29.0 → 0.30.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.
@@ -71,7 +71,8 @@ Use `useMachineAtom` when one React subtree owns the workflow, including a
71
71
  machine with startup input:
72
72
 
73
73
  ```tsx
74
- const machine = useMachineAtom(() => machineAtoms.make(processMachine, input))
74
+ const makeProcessMachine = machineAtoms.factory(processMachine)
75
+ const machine = useMachineAtom(() => makeProcessMachine(input))
75
76
  ```
76
77
 
77
78
  Pass the stable machine through props or Context. Startup input is captured
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeonce/effect-machine",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Schema-first state machines and statecharts for Effect",
5
5
  "author": "Sandro Maglione",
6
6
  "repository": {
package/src/Machine.ts CHANGED
@@ -9301,6 +9301,120 @@ export const enabled: <
9301
9301
  state: Machine.Snapshot<States>
9302
9302
  ) => ReadonlyArray<Machine.TagOf<Events[number]>> = internal.enabled as any
9303
9303
 
9304
+ /**
9305
+ * Tests whether a concrete event would select at least one transition from a
9306
+ * decoded snapshot.
9307
+ *
9308
+ * **Details**
9309
+ *
9310
+ * Required handlers are accepted from their structural eligibility.
9311
+ * Declinable handlers run their resolver only far enough to decide whether
9312
+ * they accept the event. Any commands, emissions, or raised events collected
9313
+ * during that check are discarded.
9314
+ *
9315
+ * Event input is decoded through the machine's public event protocol. Invalid
9316
+ * input fails with `MachineSchemaDecodeError`. Final snapshots and valid events
9317
+ * with no accepting handler return `false`.
9318
+ *
9319
+ * **Gotchas**
9320
+ *
9321
+ * This query does not execute transitions or stabilize the resulting machine.
9322
+ * It does not run entry, exit, always, completion, child lifecycle, or command
9323
+ * effects. A `true` result therefore describes event acceptance only.
9324
+ *
9325
+ * **Example**
9326
+ *
9327
+ * ```ts
9328
+ * const canCheckout = Machine.can(checkoutMachine)
9329
+ *
9330
+ * const canSubmit = yield* canCheckout(snapshot, {
9331
+ * _tag: "SubmitOrder"
9332
+ * })
9333
+ * ```
9334
+ *
9335
+ * @category getters
9336
+ * @since 0.30.0
9337
+ */
9338
+ export const can: {
9339
+ <
9340
+ const States extends Machine.StateSchemas,
9341
+ const Events extends ReadonlyArray<Machine.TaggedSchema>,
9342
+ const Emits extends ReadonlyArray<Machine.TaggedSchema>,
9343
+ const Input extends Schema.Top = typeof Schema.Void,
9344
+ UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
9345
+ E = never,
9346
+ R = never,
9347
+ InitialE = never,
9348
+ InitialR = never,
9349
+ FinalStates extends Machine.StateIdentifier<States> = never,
9350
+ Output = never,
9351
+ OutputStates extends Machine.StateIdentifier<States> = never,
9352
+ InputEvents extends ReadonlyArray<Machine.TaggedSchema> = Events,
9353
+ ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
9354
+ >(
9355
+ machine:
9356
+ & Machine<
9357
+ States,
9358
+ Events,
9359
+ Input,
9360
+ UnhandledStates,
9361
+ E,
9362
+ R,
9363
+ InitialE,
9364
+ InitialR,
9365
+ FinalStates,
9366
+ Output,
9367
+ Emits,
9368
+ OutputStates,
9369
+ InputEvents,
9370
+ ParentEvents
9371
+ >
9372
+ & EnsureExecutable<States, UnhandledStates, OutputStates>
9373
+ & Machine.RootCompatible<ParentEvents>
9374
+ ): (
9375
+ state: Machine.Snapshot<States>,
9376
+ event: Machine.EventInputOf<InputEvents>
9377
+ ) => Effect.Effect<boolean, MachineSchemaDecodeError>
9378
+ <
9379
+ const States extends Machine.StateSchemas,
9380
+ const Events extends ReadonlyArray<Machine.TaggedSchema>,
9381
+ const Emits extends ReadonlyArray<Machine.TaggedSchema>,
9382
+ const Input extends Schema.Top = typeof Schema.Void,
9383
+ UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
9384
+ E = never,
9385
+ R = never,
9386
+ InitialE = never,
9387
+ InitialR = never,
9388
+ FinalStates extends Machine.StateIdentifier<States> = never,
9389
+ Output = never,
9390
+ OutputStates extends Machine.StateIdentifier<States> = never,
9391
+ InputEvents extends ReadonlyArray<Machine.TaggedSchema> = Events,
9392
+ ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
9393
+ >(
9394
+ machine:
9395
+ & Machine<
9396
+ States,
9397
+ Events,
9398
+ Input,
9399
+ UnhandledStates,
9400
+ E,
9401
+ R,
9402
+ InitialE,
9403
+ InitialR,
9404
+ FinalStates,
9405
+ Output,
9406
+ Emits,
9407
+ OutputStates,
9408
+ InputEvents,
9409
+ ParentEvents
9410
+ >
9411
+ & EnsureExecutable<States, UnhandledStates, OutputStates>
9412
+ & Machine.RootCompatible<ParentEvents>,
9413
+ state: Machine.Snapshot<States>,
9414
+ event: Machine.EventInputOf<InputEvents>
9415
+ ): Effect.Effect<boolean, MachineSchemaDecodeError>
9416
+ } = internal.can as any
9417
+
9304
9418
  /**
9305
9419
  * Plans the next state snapshot synchronously.
9306
9420
  *
@@ -801,6 +801,9 @@ export const make: {
801
801
  return result
802
802
  }) as any
803
803
 
804
+ export const factory =
805
+ ((machine: Machine.Machine.Any) => (...args: ReadonlyArray<unknown>) => (make as any)(machine, ...args)) as any
806
+
804
807
  export const resume: {
805
808
  <M extends Machine.Machine.Any>(
806
809
  machine:
@@ -901,19 +904,25 @@ export const familyChild = (
901
904
 
902
905
  export const bind = <Services, RuntimeError>(
903
906
  runtime: Atom.AtomRuntime<Services, RuntimeError>
904
- ): Bound<Services, RuntimeError> => ({
905
- make:
907
+ ): Bound<Services, RuntimeError> => {
908
+ const makeBound =
906
909
  ((machine: Machine.Machine.Any, ...args: ReadonlyArray<unknown>) =>
907
910
  makeWithRuntime(runtime, machine, args)) as Bound<
908
911
  Services,
909
912
  RuntimeError
910
- >["make"],
911
- resume:
912
- ((machine: Machine.Machine.Any, snapshot: Machine.Machine.Snapshot<any>) =>
913
- resumeWithRuntime(runtime, machine, snapshot)) as Bound<Services, RuntimeError>["resume"],
914
- family: ((machine: Machine.Machine.Any, options: FamilyOptions) =>
915
- makeFamily(
916
- (input) => makeWithRuntime(runtime, machine, [input]),
917
- options
918
- )) as Bound<Services, RuntimeError>["family"]
919
- })
913
+ >["make"]
914
+ return {
915
+ make: makeBound,
916
+ factory:
917
+ ((machine: Machine.Machine.Any) => (...args: ReadonlyArray<unknown>) =>
918
+ (makeBound as any)(machine, ...args)) as Bound<Services, RuntimeError>["factory"],
919
+ resume:
920
+ ((machine: Machine.Machine.Any, snapshot: Machine.Machine.Snapshot<any>) =>
921
+ resumeWithRuntime(runtime, machine, snapshot)) as Bound<Services, RuntimeError>["resume"],
922
+ family: ((machine: Machine.Machine.Any, options: FamilyOptions) =>
923
+ makeFamily(
924
+ (input) => makeWithRuntime(runtime, machine, [input]),
925
+ options
926
+ )) as Bound<Services, RuntimeError>["family"]
927
+ }
928
+ }
@@ -2176,6 +2176,8 @@ export const enabled = <
2176
2176
  state: Machine.Snapshot<States>
2177
2177
  ): ReadonlyArray<Machine.TagOf<Events[number]>> => internalPlanner.enabled(machine as any, state)
2178
2178
 
2179
+ export const can = internalPlanner.can
2180
+
2179
2181
  export const plan: <
2180
2182
  const States extends Machine.StateSchemas,
2181
2183
  const Events extends ReadonlyArray<Machine.TaggedSchema>,
@@ -1876,6 +1876,17 @@ export const enabled = <
1876
1876
  return tags
1877
1877
  }
1878
1878
 
1879
+ const canSync = (
1880
+ machine: Machine.Any,
1881
+ state: Machine.Snapshot<any>,
1882
+ event: unknown
1883
+ ): boolean => {
1884
+ const decodedEvent = decodeEventSync(machine, event)
1885
+ if (isFinalState(machine, state)) return false
1886
+ const configuration = normalizeConfigurationSync(machine, state)
1887
+ return selectEventTransitions(machine, configuration, decodedEvent as any).length > 0
1888
+ }
1889
+
1879
1890
  const microstep = <
1880
1891
  const States extends Machine.StateSchemas,
1881
1892
  const Events extends ReadonlyArray<Machine.TaggedSchema>,
@@ -2300,6 +2311,20 @@ const planningEffect = <A>(thunk: () => A): Effect.Effect<A, InfiniteTransitionE
2300
2311
  }
2301
2312
  })
2302
2313
 
2314
+ const schemaEffect = <A>(thunk: () => A): Effect.Effect<A, MachineSchemaDecodeError> =>
2315
+ Effect.suspend(() => {
2316
+ try {
2317
+ return Effect.succeed(thunk())
2318
+ } catch (error) {
2319
+ return error instanceof MachineSchemaDecodeError ? Effect.fail(error) : Effect.die(error)
2320
+ }
2321
+ })
2322
+
2323
+ export const can = (...args: readonly [Machine.Any] | readonly [Machine.Any, Machine.Snapshot<any>, unknown]) => {
2324
+ const query = (state: Machine.Snapshot<any>, event: unknown) => schemaEffect(() => canSync(args[0], state, event))
2325
+ return args.length === 1 ? query : query(args[1], args[2])
2326
+ }
2327
+
2303
2328
  export const plan = (machine: Machine.Any, state: Machine.Snapshot<any>, event: unknown) =>
2304
2329
  planningEffect(() => planSync(machine as any, state, event as any))
2305
2330
 
@@ -906,7 +906,7 @@ type ResumedMachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = Machine
906
906
  >
907
907
 
908
908
  /**
909
- * An `AtomMachine` factory with one owned Effect runtime.
909
+ * `AtomMachine` constructors bound to one owned Effect runtime.
910
910
  *
911
911
  * @category models
912
912
  * @since 0.4.0
@@ -929,6 +929,22 @@ export interface Bound<Services, RuntimeError = never> {
929
929
  ...args: MachineInputArgsOf<M>
930
930
  ) => MachineAtomOf<M, RuntimeError>
931
931
 
932
+ /**
933
+ * Specializes a machine definition into a reusable bridge constructor.
934
+ *
935
+ * Every call creates an independent machine bridge. Startup remains lazy
936
+ * and begins only when an `AtomRegistry` reads or mounts the bridge.
937
+ *
938
+ * @since 0.30.0
939
+ */
940
+ readonly factory: <M extends Machine.Machine.Any>(
941
+ machine:
942
+ & M
943
+ & EnsureBoundRequirements<Services, NoInfer<M>>
944
+ & EnsureMachineExecutable<NoInfer<M>>
945
+ & Machine.Machine.RootCompatible<Machine.Machine.ParentEvents<NoInfer<M>>>
946
+ ) => (...args: MachineInputArgsOf<M>) => MachineAtomOf<M, RuntimeError>
947
+
932
948
  /** Creates a lazy bridge from a decoded logical snapshot. */
933
949
  readonly resume: <M extends Machine.Machine.Any>(
934
950
  machine:
@@ -1140,6 +1156,33 @@ export const make: {
1140
1156
  >
1141
1157
  } = internal.make
1142
1158
 
1159
+ /**
1160
+ * Specializes a machine definition into a reusable bridge constructor.
1161
+ *
1162
+ * The returned function preserves the machine's startup input arity and exact
1163
+ * bridge type. Every call creates a fresh `MachineAtom`; it does not cache by
1164
+ * input or start the machine before an `AtomRegistry` reads or mounts it.
1165
+ *
1166
+ * **Example**
1167
+ *
1168
+ * ```ts
1169
+ * const makeSearchMachine = AtomMachine.factory(searchMachine)
1170
+ * const search = makeSearchMachine({ query: "effect" })
1171
+ *
1172
+ * type SearchMachineAtom = ReturnType<typeof makeSearchMachine>
1173
+ * ```
1174
+ *
1175
+ * @category constructors
1176
+ * @since 0.30.0
1177
+ */
1178
+ export const factory: <M extends Machine.Machine.Any>(
1179
+ machine:
1180
+ & M
1181
+ & EnsureNoExternalRequirements<MachineRequirementsOf<NoInfer<M>>>
1182
+ & EnsureMachineExecutable<NoInfer<M>>
1183
+ & Machine.Machine.RootCompatible<Machine.Machine.ParentEvents<NoInfer<M>>>
1184
+ ) => (...args: MachineInputArgsOf<M>) => MachineAtomOf<M, never> = internal.factory
1185
+
1143
1186
  /**
1144
1187
  * Creates a lazy atom bridge from a decoded logical snapshot.
1145
1188
  *
@@ -1162,11 +1205,12 @@ export const resume: {
1162
1205
  } = internal.resume
1163
1206
 
1164
1207
  /**
1165
- * Creates an `AtomMachine` factory that owns a shared Effect runtime.
1208
+ * Binds `AtomMachine` constructors to a shared Effect runtime.
1166
1209
  *
1167
1210
  * Use this when an application runs many machines from the same service layer.
1168
- * The returned factory keeps runtime provisioning at the composition boundary,
1169
- * while every call to `make` still creates an independent machine bridge.
1211
+ * The returned interface keeps runtime provisioning at the composition seam,
1212
+ * while every call to `make` or a specialized `factory` still creates an
1213
+ * independent machine bridge.
1170
1214
  *
1171
1215
  * @category constructors
1172
1216
  * @since 0.4.0