@typeonce/effect-machine-react 0.31.1 → 0.32.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 CHANGED
@@ -1,26 +1,54 @@
1
1
  # @typeonce/effect-machine-react
2
2
 
3
- React ownership hooks for machine atoms created by
3
+ React ownership and typed state rendering for
4
4
  [`@typeonce/effect-machine`](../effect-machine/README.md).
5
5
 
6
6
  ```tsx
7
- import { useMachineAtom } from "@typeonce/effect-machine-react"
7
+ import { createMachineContext, MachineState } from "@typeonce/effect-machine-react"
8
+ import { AtomMachine } from "@typeonce/effect-machine/reactivity"
9
+ import { Suspense } from "react"
10
+ import { AuthMachine } from "./auth-machine"
8
11
 
9
- function AuthProvider({ input, children }: Props) {
10
- const machine = useMachineAtom(() => MachineAtoms.make(AuthMachine, input))
12
+ const Auth = createMachineContext(AtomMachine.factory(AuthMachine))
11
13
 
14
+ function AuthRoute({ input }: Props) {
12
15
  return (
13
- <AuthContext.Provider value={machine}>
14
- {children}
15
- </AuthContext.Provider>
16
+ <Auth.Provider input={input}>
17
+ <Suspense fallback={<Loading />}>
18
+ <AuthForm />
19
+ </Suspense>
20
+ </Auth.Provider>
21
+ )
22
+ }
23
+
24
+ function AuthForm() {
25
+ const machine = Auth.useMachine()
26
+ return (
27
+ <MachineState machine={machine} path="Editing" inactive={null}>
28
+ {({ value }) => <EmailField email={value.email} />}
29
+ </MachineState>
16
30
  )
17
31
  }
18
32
  ```
19
33
 
20
- `useMachineAtom` strongly owns one machine atom, mounts it after commit, and
21
- does not subscribe the owner to machine state. Descendants subscribe to the
22
- specific state paths they render with `AtomMachine.select`,
23
- `AtomMachine.selectSnapshot`, or `AtomMachine.matches`.
34
+ Each Provider creates an independent machine in the current Atom registry,
35
+ including when two Providers receive equal inputs. Supply an Atom
36
+ `RegistryProvider` at the application boundary. Bind Effect services through
37
+ `AtomMachine.runtime(...)` before creating a factory when the machine needs them.
38
+
39
+ The Provider owns the reference without subscribing to state changes.
40
+ `MachineState` subscribes to its typed path and infers the callback snapshot.
41
+ Inactive paths render `inactive` (null by default); startup suspends and failures
42
+ propagate to the nearest error boundary. Keep the owner above Suspense.
43
+
44
+ Input is startup-only. Send an event to update a running workflow, or change the
45
+ Provider's React `key` to start a new one. Machines without input have a Provider
46
+ without an `input` prop.
47
+
48
+ `useMachineAtom(() => makeMachine(input))` remains available for custom owners.
49
+ Use `AtomMachine.select`, `selectSnapshot`, and `matches` for custom granular
50
+ subscriptions. Read `.result` for the whole logical snapshot or `.snapshot` for
51
+ runtime status; the bridge has no separate `.state` projection.
24
52
 
25
- Machine input is startup-only. Send an event to update a running workflow, or
26
- change the provider's React `key` to replace it with a new machine.
53
+ See the [React guide](../effect-machine/docs/effect-atom-react.md) for events,
54
+ services, child machines, and registry-owned families.
@@ -1,16 +1,58 @@
1
+ import type { Machine } from "@typeonce/effect-machine";
1
2
  import type { AtomMachine } from "@typeonce/effect-machine/reactivity";
3
+ import type * as React from "react";
2
4
  type AnyMachineAtom = AtomMachine.MachineAtom<any, never, any, any, any, any>;
3
5
  /**
4
- * Creates one machine atom for a committed React owner and mounts its machine
5
- * reference without subscribing the owner to machine state.
6
- *
7
- * The factory is startup-only. Later changes to values captured by the factory
8
- * do not replace the machine. Send an event to change a running workflow, or
9
- * change the owner's React `key` to create a new machine.
6
+ * Creates one bridge per React owner and mounts its reference without subscribing
7
+ * the owner to state changes. The factory is startup-only. Change the owner's
8
+ * React key to start a fresh machine. Keep this owner above any Suspense boundary
9
+ * that reads the machine.
10
10
  *
11
11
  * @category hooks
12
12
  * @since 0.29.0
13
13
  */
14
14
  export declare const useMachineAtom: <A extends AnyMachineAtom>(create: () => A) => A;
15
+ /** Props for a typed render of one active state path.
16
+ * @category models
17
+ * @since 0.32.0
18
+ */
19
+ export interface MachineStateProps<State extends Machine.Machine.AtomicSnapshot<string, unknown>, Event, Error, Output, StartError, Emitted, Path extends Machine.Snapshot.Path<State>> {
20
+ readonly machine: AtomMachine.MachineAtom<State, Event, Error, Output, StartError, Emitted>;
21
+ readonly path: Path;
22
+ readonly inactive?: React.ReactNode;
23
+ readonly children: (state: Machine.Snapshot.At<State, NoInfer<Path>>) => React.ReactNode;
24
+ }
25
+ /**
26
+ * Subscribes only this renderer to a state path. Startup suspends and failures
27
+ * propagate to the nearest error boundary. Inactive child states render
28
+ * `inactive`, which defaults to null.
29
+ *
30
+ * @category components
31
+ * @since 0.32.0
32
+ */
33
+ export declare const MachineState: <State extends Machine.Machine.AtomicSnapshot<string, unknown>, Event, Error, Output, StartError, Emitted, const Path extends Machine.Snapshot.Path<State>>(props: MachineStateProps<State, Event, Error, Output, StartError, Emitted, Path>) => React.ReactNode;
34
+ /** An isolated React owner and accessor for a bridge factory.
35
+ * @category models
36
+ * @since 0.32.0
37
+ */
38
+ export interface MachineContext<Args extends [] | [unknown], A extends AnyMachineAtom> {
39
+ readonly Provider: React.ComponentType<{
40
+ readonly children?: React.ReactNode;
41
+ } & (Args extends [] ? {
42
+ readonly input?: never;
43
+ } : {
44
+ readonly input: Args[0];
45
+ })>;
46
+ readonly useMachine: () => A;
47
+ }
48
+ /**
49
+ * Creates a context from an AtomMachine factory. Each Provider owns a fresh
50
+ * bridge in its current registry. Input is read once at startup; use a new React
51
+ * key for a new owner. The Provider does not subscribe to machine state.
52
+ *
53
+ * @category constructors
54
+ * @since 0.32.0
55
+ */
56
+ export declare const createMachineContext: <Args extends [] | [unknown], A extends AnyMachineAtom>(create: (...args: Args) => A) => MachineContext<Args, A>;
15
57
  export {};
16
58
  //# sourceMappingURL=MachineAtom.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MachineAtom.d.ts","sourceRoot":"","sources":["../src/MachineAtom.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AAGtE,KAAK,cAAc,GAAG,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAE7E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,cAAc,EAAE,QAAQ,MAAM,CAAC,KAAG,CAI1E,CAAA"}
1
+ {"version":3,"file":"MachineAtom.d.ts","sourceRoot":"","sources":["../src/MachineAtom.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAGnC,KAAK,cAAc,GAAG,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAE7E;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,EAAE,CAAC,CAAC,SAAS,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAA2B,CAAA;AAEvG;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAChC,KAAK,SAAS,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,KAAK,EACL,KAAK,EACL,MAAM,EACN,UAAU,EACV,OAAO,EACP,IAAI,SAAS,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;IAEzC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IAC3F,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;CACzF;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,CACzB,KAAK,SAAS,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,KAAK,EACL,KAAK,EACL,MAAM,EACN,UAAU,EACV,OAAO,EACP,KAAK,CAAC,IAAI,SAAS,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAE/C,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,KAC7E,KAAK,CAAC,SAAiC,CAAA;AAE5C;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,cAAc;IACnF,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,aAAa,CAClC;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE,GACvC,CAAC,IAAI,SAAS,EAAE,GAAG;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC,CAC/E,CAAA;IACD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;CAC7B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,CAAC,IAAI,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,cAAc,EACvF,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,KACzB,cAAc,CAAC,IAAI,EAAE,CAAC,CAAiC,CAAA"}
@@ -1,25 +1,36 @@
1
1
  /**
2
- * React ownership for machine atoms.
2
+ * React ownership and state rendering for machine atoms.
3
3
  *
4
4
  * @since 0.29.0
5
5
  */
6
6
  "use client";
7
- import { useAtomMount } from "@effect/atom-react";
8
- import * as React from "react";
7
+ import * as internal from "./internal/react.js";
9
8
  /**
10
- * Creates one machine atom for a committed React owner and mounts its machine
11
- * reference without subscribing the owner to machine state.
12
- *
13
- * The factory is startup-only. Later changes to values captured by the factory
14
- * do not replace the machine. Send an event to change a running workflow, or
15
- * change the owner's React `key` to create a new machine.
9
+ * Creates one bridge per React owner and mounts its reference without subscribing
10
+ * the owner to state changes. The factory is startup-only. Change the owner's
11
+ * React key to start a fresh machine. Keep this owner above any Suspense boundary
12
+ * that reads the machine.
16
13
  *
17
14
  * @category hooks
18
15
  * @since 0.29.0
19
16
  */
20
- export const useMachineAtom = (create) => {
21
- const [machine] = React.useState(create);
22
- useAtomMount(machine.ref);
23
- return machine;
24
- };
17
+ export const useMachineAtom = internal.useMachineAtom;
18
+ /**
19
+ * Subscribes only this renderer to a state path. Startup suspends and failures
20
+ * propagate to the nearest error boundary. Inactive child states render
21
+ * `inactive`, which defaults to null.
22
+ *
23
+ * @category components
24
+ * @since 0.32.0
25
+ */
26
+ export const MachineState = internal.MachineState;
27
+ /**
28
+ * Creates a context from an AtomMachine factory. Each Provider owns a fresh
29
+ * bridge in its current registry. Input is read once at startup; use a new React
30
+ * key for a new owner. The Provider does not subscribe to machine state.
31
+ *
32
+ * @category constructors
33
+ * @since 0.32.0
34
+ */
35
+ export const createMachineContext = internal.createMachineContext;
25
36
  //# sourceMappingURL=MachineAtom.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MachineAtom.js","sourceRoot":"","sources":["../src/MachineAtom.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,CAAA;AAEZ,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAA2B,MAAe,EAAK,EAAE;IAC7E,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACxC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACzB,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
1
+ {"version":3,"file":"MachineAtom.js","sourceRoot":"","sources":["../src/MachineAtom.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,CAAA;AAKZ,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAA;AAI/C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAqD,QAAQ,CAAC,cAAc,CAAA;AAqBvG;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAUF,QAAQ,CAAC,YAAY,CAAA;AAc5C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAEF,QAAQ,CAAC,oBAAoB,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { Machine } from "@typeonce/effect-machine";
2
+ import { AtomMachine } from "@typeonce/effect-machine/reactivity";
3
+ import * as React from "react";
4
+ import type { MachineContext, MachineStateProps } from "../MachineAtom.js";
5
+ type AnyMachineAtom = AtomMachine.MachineAtom<any, never, any, any, any, any>;
6
+ export declare const useMachineAtom: <A extends AnyMachineAtom>(create: () => A) => A;
7
+ export declare const MachineState: <State extends Machine.Machine.AtomicSnapshot<string, unknown>, Event, Error, Output, StartError, Emitted, const Path extends Machine.Snapshot.Path<State>>(props: MachineStateProps<State, Event, Error, Output, StartError, Emitted, Path>) => React.ReactNode;
8
+ export declare const createMachineContext: <Args extends [] | [unknown], A extends AnyMachineAtom>(create: (...args: Args) => A) => MachineContext<Args, A>;
9
+ export {};
10
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/internal/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE1E,KAAK,cAAc,GAAG,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAE7E,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,cAAc,EAAE,QAAQ,MAAM,CAAC,KAAG,CAI1E,CAAA;AAED,eAAO,MAAM,YAAY,GACvB,KAAK,SAAS,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,KAAK,EACL,KAAK,EACL,MAAM,EACN,UAAU,EACV,OAAO,EACP,KAAK,CAAC,IAAI,SAAS,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAE/C,OAAO,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,KAC/E,KAAK,CAAC,SAOR,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,IAAI,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,cAAc,EACxF,QAAQ,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,KAC3B,cAAc,CAAC,IAAI,EAAE,CAAC,CAaxB,CAAA"}
@@ -0,0 +1,33 @@
1
+ import { useAtomMount, useAtomSuspense } from "@effect/atom-react";
2
+ import { AtomMachine } from "@typeonce/effect-machine/reactivity";
3
+ import { Option } from "effect";
4
+ import * as React from "react";
5
+ export const useMachineAtom = (create) => {
6
+ const [machine] = React.useState(create);
7
+ useAtomMount(machine.ref);
8
+ return machine;
9
+ };
10
+ export const MachineState = (props) => {
11
+ const selected = React.useMemo(() => AtomMachine.selectSnapshot(props.machine, props.path), [
12
+ props.machine,
13
+ props.path
14
+ ]);
15
+ const snapshot = useAtomSuspense(selected).value;
16
+ return Option.isSome(snapshot) ? props.children(snapshot.value) : props.inactive ?? null;
17
+ };
18
+ export const createMachineContext = (create) => {
19
+ const Context = React.createContext(undefined);
20
+ const Provider = (props) => {
21
+ // Input belongs to this committed owner. A new React key creates a new owner.
22
+ const machine = useMachineAtom(() => create(...("input" in props ? [props.input] : [])));
23
+ return React.createElement(Context.Provider, { value: machine }, props.children);
24
+ };
25
+ const useMachine = () => {
26
+ const machine = React.useContext(Context);
27
+ if (machine === undefined)
28
+ throw new Error("Machine context must be read inside its Provider");
29
+ return machine;
30
+ };
31
+ return Object.freeze({ Provider, useMachine });
32
+ };
33
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/internal/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAElE,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,CAAC,MAAM,cAAc,GAAG,CAA2B,MAAe,EAAK,EAAE;IAC7E,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACxC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACzB,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAS1B,KAAgF,EAC/D,EAAE;IACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;QAC1F,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,IAAI;KACX,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAA;IAChD,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAA;AAC1F,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,MAA4B,EACH,EAAE;IAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAgB,SAAS,CAAC,CAAA;IAC7D,MAAM,QAAQ,GAAG,CAAC,KAAwE,EAAmB,EAAE;QAC7G,8EAA8E;QAC9E,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAS,CAAC,CAAC,CAAA;QAChG,OAAO,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;IAClF,CAAC,CAAA;IACD,MAAM,UAAU,GAAG,GAAM,EAAE;QACzB,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;QACzC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;QAC9F,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAA4B,CAAA;AAC3E,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeonce/effect-machine-react",
3
- "version": "0.31.1",
3
+ "version": "0.32.0",
4
4
  "description": "React ownership hooks for Effect Machine atoms",
5
5
  "author": "Sandro Maglione",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "provenance": true
35
35
  },
36
36
  "dependencies": {
37
- "@typeonce/effect-machine": "^0.31.1"
37
+ "@typeonce/effect-machine": "^0.32.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@effect/atom-react": "4.0.0-rc.112",
@@ -1,29 +1,87 @@
1
1
  /**
2
- * React ownership for machine atoms.
2
+ * React ownership and state rendering for machine atoms.
3
3
  *
4
4
  * @since 0.29.0
5
5
  */
6
6
  "use client"
7
7
 
8
- import { useAtomMount } from "@effect/atom-react"
8
+ import type { Machine } from "@typeonce/effect-machine"
9
9
  import type { AtomMachine } from "@typeonce/effect-machine/reactivity"
10
- import * as React from "react"
10
+ import type * as React from "react"
11
+ import * as internal from "./internal/react.js"
11
12
 
12
13
  type AnyMachineAtom = AtomMachine.MachineAtom<any, never, any, any, any, any>
13
14
 
14
15
  /**
15
- * Creates one machine atom for a committed React owner and mounts its machine
16
- * reference without subscribing the owner to machine state.
17
- *
18
- * The factory is startup-only. Later changes to values captured by the factory
19
- * do not replace the machine. Send an event to change a running workflow, or
20
- * change the owner's React `key` to create a new machine.
16
+ * Creates one bridge per React owner and mounts its reference without subscribing
17
+ * the owner to state changes. The factory is startup-only. Change the owner's
18
+ * React key to start a fresh machine. Keep this owner above any Suspense boundary
19
+ * that reads the machine.
21
20
  *
22
21
  * @category hooks
23
22
  * @since 0.29.0
24
23
  */
25
- export const useMachineAtom = <A extends AnyMachineAtom>(create: () => A): A => {
26
- const [machine] = React.useState(create)
27
- useAtomMount(machine.ref)
28
- return machine
24
+ export const useMachineAtom: <A extends AnyMachineAtom>(create: () => A) => A = internal.useMachineAtom
25
+
26
+ /** Props for a typed render of one active state path.
27
+ * @category models
28
+ * @since 0.32.0
29
+ */
30
+ export interface MachineStateProps<
31
+ State extends Machine.Machine.AtomicSnapshot<string, unknown>,
32
+ Event,
33
+ Error,
34
+ Output,
35
+ StartError,
36
+ Emitted,
37
+ Path extends Machine.Snapshot.Path<State>
38
+ > {
39
+ readonly machine: AtomMachine.MachineAtom<State, Event, Error, Output, StartError, Emitted>
40
+ readonly path: Path
41
+ readonly inactive?: React.ReactNode
42
+ readonly children: (state: Machine.Snapshot.At<State, NoInfer<Path>>) => React.ReactNode
43
+ }
44
+
45
+ /**
46
+ * Subscribes only this renderer to a state path. Startup suspends and failures
47
+ * propagate to the nearest error boundary. Inactive child states render
48
+ * `inactive`, which defaults to null.
49
+ *
50
+ * @category components
51
+ * @since 0.32.0
52
+ */
53
+ export const MachineState: <
54
+ State extends Machine.Machine.AtomicSnapshot<string, unknown>,
55
+ Event,
56
+ Error,
57
+ Output,
58
+ StartError,
59
+ Emitted,
60
+ const Path extends Machine.Snapshot.Path<State>
61
+ >(
62
+ props: MachineStateProps<State, Event, Error, Output, StartError, Emitted, Path>
63
+ ) => React.ReactNode = internal.MachineState
64
+
65
+ /** An isolated React owner and accessor for a bridge factory.
66
+ * @category models
67
+ * @since 0.32.0
68
+ */
69
+ export interface MachineContext<Args extends [] | [unknown], A extends AnyMachineAtom> {
70
+ readonly Provider: React.ComponentType<
71
+ & { readonly children?: React.ReactNode }
72
+ & (Args extends [] ? { readonly input?: never } : { readonly input: Args[0] })
73
+ >
74
+ readonly useMachine: () => A
29
75
  }
76
+
77
+ /**
78
+ * Creates a context from an AtomMachine factory. Each Provider owns a fresh
79
+ * bridge in its current registry. Input is read once at startup; use a new React
80
+ * key for a new owner. The Provider does not subscribe to machine state.
81
+ *
82
+ * @category constructors
83
+ * @since 0.32.0
84
+ */
85
+ export const createMachineContext: <Args extends [] | [unknown], A extends AnyMachineAtom>(
86
+ create: (...args: Args) => A
87
+ ) => MachineContext<Args, A> = internal.createMachineContext
@@ -0,0 +1,50 @@
1
+ import { useAtomMount, useAtomSuspense } from "@effect/atom-react"
2
+ import type { Machine } from "@typeonce/effect-machine"
3
+ import { AtomMachine } from "@typeonce/effect-machine/reactivity"
4
+ import { Option } from "effect"
5
+ import * as React from "react"
6
+ import type { MachineContext, MachineStateProps } from "../MachineAtom.js"
7
+
8
+ type AnyMachineAtom = AtomMachine.MachineAtom<any, never, any, any, any, any>
9
+
10
+ export const useMachineAtom = <A extends AnyMachineAtom>(create: () => A): A => {
11
+ const [machine] = React.useState(create)
12
+ useAtomMount(machine.ref)
13
+ return machine
14
+ }
15
+
16
+ export const MachineState = <
17
+ State extends Machine.Machine.AtomicSnapshot<string, unknown>,
18
+ Event,
19
+ Error,
20
+ Output,
21
+ StartError,
22
+ Emitted,
23
+ const Path extends Machine.Snapshot.Path<State>
24
+ >(
25
+ props: MachineStateProps<State, Event, Error, Output, StartError, Emitted, Path>
26
+ ): React.ReactNode => {
27
+ const selected = React.useMemo(() => AtomMachine.selectSnapshot(props.machine, props.path), [
28
+ props.machine,
29
+ props.path
30
+ ])
31
+ const snapshot = useAtomSuspense(selected).value
32
+ return Option.isSome(snapshot) ? props.children(snapshot.value) : props.inactive ?? null
33
+ }
34
+
35
+ export const createMachineContext = <Args extends [] | [unknown], A extends AnyMachineAtom>(
36
+ create: (...args: Args) => A
37
+ ): MachineContext<Args, A> => {
38
+ const Context = React.createContext<A | undefined>(undefined)
39
+ const Provider = (props: { readonly input?: unknown; readonly children?: React.ReactNode }): React.ReactNode => {
40
+ // Input belongs to this committed owner. A new React key creates a new owner.
41
+ const machine = useMachineAtom(() => create(...("input" in props ? [props.input] : []) as Args))
42
+ return React.createElement(Context.Provider, { value: machine }, props.children)
43
+ }
44
+ const useMachine = (): A => {
45
+ const machine = React.useContext(Context)
46
+ if (machine === undefined) throw new Error("Machine context must be read inside its Provider")
47
+ return machine
48
+ }
49
+ return Object.freeze({ Provider, useMachine }) as MachineContext<Args, A>
50
+ }