@typeonce/effect-machine-react 0.31.2 → 0.33.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 +41 -13
- package/dist/MachineAtom.d.ts +48 -6
- package/dist/MachineAtom.d.ts.map +1 -1
- package/dist/MachineAtom.js +25 -14
- package/dist/MachineAtom.js.map +1 -1
- package/dist/internal/react.d.ts +10 -0
- package/dist/internal/react.d.ts.map +1 -0
- package/dist/internal/react.js +33 -0
- package/dist/internal/react.js.map +1 -0
- package/package.json +2 -2
- package/src/MachineAtom.ts +71 -13
- package/src/internal/react.ts +50 -0
package/README.md
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
# @typeonce/effect-machine-react
|
|
2
2
|
|
|
3
|
-
React ownership
|
|
3
|
+
React ownership and typed state rendering for
|
|
4
4
|
[`@typeonce/effect-machine`](../effect-machine/README.md).
|
|
5
5
|
|
|
6
6
|
```tsx
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
<
|
|
14
|
-
{
|
|
15
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`AtomMachine.
|
|
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
|
-
|
|
26
|
-
|
|
53
|
+
See the [React guide](../effect-machine/docs/effect-atom-react.md) for events,
|
|
54
|
+
services, child machines, and registry-owned families.
|
package/dist/MachineAtom.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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":"
|
|
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"}
|
package/dist/MachineAtom.js
CHANGED
|
@@ -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
|
|
8
|
-
import * as React from "react";
|
|
7
|
+
import * as internal from "./internal/react.js";
|
|
9
8
|
/**
|
|
10
|
-
* Creates one
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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 =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
package/dist/MachineAtom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MachineAtom.js","sourceRoot":"","sources":["../src/MachineAtom.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,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.
|
|
3
|
+
"version": "0.33.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.
|
|
37
|
+
"@typeonce/effect-machine": "^0.33.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@effect/atom-react": "4.0.0-rc.112",
|
package/src/MachineAtom.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
}
|