@bemedev/app-reactjs 1.6.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 ADDED
@@ -0,0 +1,72 @@
1
+ # @bemedev/app-reactjs
2
+
3
+ ReactJS state hook binding for `@bemedev/app` state machines.
4
+
5
+ `@bemedev/app-reactjs` provides a lightweight hook to track and select
6
+ state from a `@bemedev/app` interpreter service inside ReactJS component
7
+ trees.
8
+
9
+ <br/>
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @bemedev/app-reactjs
15
+ # or
16
+ pnpm add @bemedev/app-reactjs
17
+ ```
18
+
19
+ > **Requirements:** Node.js ≥ 24 · `@bemedev/app` ≥ 1.2.0 · React ≥ 18.0.0
20
+
21
+ <br/>
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { interpret } from '@bemedev/app';
27
+ import { useService } from '@bemedev/app-reactjs';
28
+ import { myMachine } from './my.machine';
29
+
30
+ const service = interpret(myMachine, { context: { count: 0 } });
31
+ service.start();
32
+
33
+ function MyComponent() {
34
+ // 1. Select a specific slice of context
35
+ const count = useService(service, s => s.context.count);
36
+
37
+ // 2. Or track the active state value
38
+ const stateValue = useService(service, s => s.value);
39
+
40
+ // 3. Or track the whole state object
41
+ const state = useService(service);
42
+
43
+ return (
44
+ <div>
45
+ <p>Count: {count}</p>
46
+ <p>State: {JSON.stringify(stateValue)}</p>
47
+ </div>
48
+ );
49
+ }
50
+ ```
51
+
52
+ <br/>
53
+
54
+ ## API Reference
55
+
56
+ ### `useService(service, selector?)`
57
+
58
+ Creates a React state hook that updates whenever the interpreter
59
+ transitions, using deep comparison to prevent unnecessary renders.
60
+
61
+ | Parameter | Type | Description |
62
+ | ---------- | ----------------------- | ---------------------------------------------------------------------- |
63
+ | `service` | `Interpreter` | The `@bemedev/app` interpreter service instance. |
64
+ | `selector` | `(state: State) => any` | Optional selector function to slice and track specific parts of state. |
65
+
66
+ Returns the selected state or slice.
67
+
68
+ <br/>
69
+
70
+ ## License
71
+
72
+ MIT
package/lib/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_useService = require("./useService.cjs");
3
+ exports.useService = require_useService.useService;
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './useService';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { useService } from "./useService.js";
2
+ export { useService };
@@ -0,0 +1,25 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _bemedev_app_utils = require("@bemedev/app/utils");
3
+ let react = require("react");
4
+ //#region src/useService.ts
5
+ /**
6
+ * A hook that creates a React state from a service with a subscribe function.
7
+ *
8
+ * @param service - The service containing the state and subscribe method.
9
+ * @param selector - A function to select the desired slice of state.
10
+ * @returns The selected state.
11
+ */
12
+ function useService(service, selector = (s) => s) {
13
+ const [state, setState] = (0, react.useState)(() => selector(service.state));
14
+ const sub = service.subscribe((nextState) => {
15
+ setState(selector(nextState));
16
+ }, { equals: (first, next) => {
17
+ return (0, _bemedev_app_utils.deepEqual)(selector(first), selector(next));
18
+ } });
19
+ (0, react.useEffect)(() => sub.unsubscribe, [service, selector]);
20
+ return state;
21
+ }
22
+ //#endregion
23
+ exports.useService = useService;
24
+
25
+ //# sourceMappingURL=useService.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useService.cjs","names":[],"sources":["../src/useService.ts"],"sourcesContent":["import type {\n AddSubscriber_F,\n EventObject,\n PrimitiveObject,\n State,\n} from '@bemedev/app/types';\nimport { deepEqual } from '@bemedev/app/utils';\nimport { useEffect, useState } from 'react';\n\n/**\n * A hook that creates a React state from a service with a subscribe function.\n *\n * @param service - The service containing the state and subscribe method.\n * @param selector - A function to select the desired slice of state.\n * @returns The selected state.\n */\nexport function useService<\n Tc extends PrimitiveObject,\n Ta extends string,\n Eo extends EventObject,\n T = State<Eo, Tc, Ta>,\n>(\n service: {\n subscribe: AddSubscriber_F<Tc, Ta, Eo>;\n state: State<Eo, Tc, Ta>;\n },\n selector: (state: State<Eo, Tc, Ta>) => T = (s: State<Eo, Tc, Ta>) =>\n s as unknown as T,\n): T {\n const [state, setState] = useState<T>(() => selector(service.state));\n\n const sub = service.subscribe(\n nextState => {\n setState(selector(nextState));\n },\n {\n equals: (first, next) => {\n const _first = selector(first);\n const _next = selector(next);\n return deepEqual(_first, _next);\n },\n },\n );\n\n useEffect(() => sub.unsubscribe, [service, selector]);\n return state;\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAgB,WAMd,SAIA,YAA6C,MAC3C,GACC;CACH,MAAM,CAAC,OAAO,aAAA,GAAA,MAAA,SAAA,OAA8B,SAAS,QAAQ,KAAK,CAAC;CAEnE,MAAM,MAAM,QAAQ,WAClB,cAAa;EACX,SAAS,SAAS,SAAS,CAAC;CAC9B,GACA,EACE,SAAS,OAAO,SAAS;EAGvB,QAAA,GAAA,mBAAA,UAAA,CAFe,SAAS,KAEF,GADR,SAAS,IACM,CAAC;CAChC,EACF,CACF;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB,IAAI,aAAa,CAAC,SAAS,QAAQ,CAAC;CACpD,OAAO;AACT"}
@@ -0,0 +1,13 @@
1
+ import type { AddSubscriber_F, EventObject, PrimitiveObject, State } from '@bemedev/app/types';
2
+ /**
3
+ * A hook that creates a React state from a service with a subscribe function.
4
+ *
5
+ * @param service - The service containing the state and subscribe method.
6
+ * @param selector - A function to select the desired slice of state.
7
+ * @returns The selected state.
8
+ */
9
+ export declare function useService<Tc extends PrimitiveObject, Ta extends string, Eo extends EventObject, T = State<Eo, Tc, Ta>>(service: {
10
+ subscribe: AddSubscriber_F<Tc, Ta, Eo>;
11
+ state: State<Eo, Tc, Ta>;
12
+ }, selector?: (state: State<Eo, Tc, Ta>) => T): T;
13
+ //# sourceMappingURL=useService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useService.d.ts","sourceRoot":"","sources":["../src/useService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,eAAe,EACf,KAAK,EACN,MAAM,oBAAoB,CAAC;AAI5B;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,EAAE,SAAS,eAAe,EAC1B,EAAE,SAAS,MAAM,EACjB,EAAE,SAAS,WAAW,EACtB,CAAC,GAAG,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAErB,OAAO,EAAE;IACP,SAAS,EAAE,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IACvC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;CAC1B,EACD,QAAQ,GAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,CACrB,GAClB,CAAC,CAkBH"}
@@ -0,0 +1,24 @@
1
+ import { deepEqual } from "@bemedev/app/utils";
2
+ import { useEffect, useState } from "react";
3
+ //#region src/useService.ts
4
+ /**
5
+ * A hook that creates a React state from a service with a subscribe function.
6
+ *
7
+ * @param service - The service containing the state and subscribe method.
8
+ * @param selector - A function to select the desired slice of state.
9
+ * @returns The selected state.
10
+ */
11
+ function useService(service, selector = (s) => s) {
12
+ const [state, setState] = useState(() => selector(service.state));
13
+ const sub = service.subscribe((nextState) => {
14
+ setState(selector(nextState));
15
+ }, { equals: (first, next) => {
16
+ return deepEqual(selector(first), selector(next));
17
+ } });
18
+ useEffect(() => sub.unsubscribe, [service, selector]);
19
+ return state;
20
+ }
21
+ //#endregion
22
+ export { useService };
23
+
24
+ //# sourceMappingURL=useService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useService.js","names":[],"sources":["../src/useService.ts"],"sourcesContent":["import type {\n AddSubscriber_F,\n EventObject,\n PrimitiveObject,\n State,\n} from '@bemedev/app/types';\nimport { deepEqual } from '@bemedev/app/utils';\nimport { useEffect, useState } from 'react';\n\n/**\n * A hook that creates a React state from a service with a subscribe function.\n *\n * @param service - The service containing the state and subscribe method.\n * @param selector - A function to select the desired slice of state.\n * @returns The selected state.\n */\nexport function useService<\n Tc extends PrimitiveObject,\n Ta extends string,\n Eo extends EventObject,\n T = State<Eo, Tc, Ta>,\n>(\n service: {\n subscribe: AddSubscriber_F<Tc, Ta, Eo>;\n state: State<Eo, Tc, Ta>;\n },\n selector: (state: State<Eo, Tc, Ta>) => T = (s: State<Eo, Tc, Ta>) =>\n s as unknown as T,\n): T {\n const [state, setState] = useState<T>(() => selector(service.state));\n\n const sub = service.subscribe(\n nextState => {\n setState(selector(nextState));\n },\n {\n equals: (first, next) => {\n const _first = selector(first);\n const _next = selector(next);\n return deepEqual(_first, _next);\n },\n },\n );\n\n useEffect(() => sub.unsubscribe, [service, selector]);\n return state;\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,WAMd,SAIA,YAA6C,MAC3C,GACC;CACH,MAAM,CAAC,OAAO,YAAY,eAAkB,SAAS,QAAQ,KAAK,CAAC;CAEnE,MAAM,MAAM,QAAQ,WAClB,cAAa;EACX,SAAS,SAAS,SAAS,CAAC;CAC9B,GACA,EACE,SAAS,OAAO,SAAS;EAGvB,OAAO,UAFQ,SAAS,KAEF,GADR,SAAS,IACM,CAAC;CAChC,EACF,CACF;CAEA,gBAAgB,IAAI,aAAa,CAAC,SAAS,QAAQ,CAAC;CACpD,OAAO;AACT"}
package/package.json ADDED
@@ -0,0 +1,133 @@
1
+ {
2
+ "name": "@bemedev/app-reactjs",
3
+ "version": "1.6.0",
4
+ "description": "Middleware between @bemedev/app and reactjs",
5
+ "keywords": [
6
+ "app",
7
+ "bemedev",
8
+ "middleware",
9
+ "reactjs",
10
+ "react",
11
+ "state-machines"
12
+ ],
13
+ "license": "MIT",
14
+ "author": {
15
+ "name": "chlbri",
16
+ "email": "bri_lvi@icloud.com",
17
+ "url": "https://bemedev.vercel.app"
18
+ },
19
+ "maintainers": [
20
+ {
21
+ "email": "bri_lvi@icloud.com",
22
+ "name": "chlbri",
23
+ "url": "https://bemedev.vercel.app"
24
+ }
25
+ ],
26
+ "contributors": [
27
+ {
28
+ "email": "bri_lvi@icloud.com",
29
+ "name": "chlbri",
30
+ "url": "https://bemedev.vercel.app"
31
+ }
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/chlbri/app"
36
+ },
37
+ "files": [
38
+ "lib",
39
+ "public"
40
+ ],
41
+ "type": "module",
42
+ "main": "lib/index.js",
43
+ "module": "lib/index.js",
44
+ "types": "lib/index.d.ts",
45
+ "devDependencies": {
46
+ "@bemedev/decompose": "2.2.0",
47
+ "@bemedev/dev-utils": "^0.8.3",
48
+ "@bemedev/sleep": "^0.2.2",
49
+ "@size-limit/file": "^12.1.0",
50
+ "@testing-library/jest-dom": "^6.9.1",
51
+ "@testing-library/react": "^16.2.0",
52
+ "@testing-library/user-event": "^14.6.1",
53
+ "@types/node": "^26.1.0",
54
+ "@types/react": "^19.0.0",
55
+ "@types/react-dom": "^19.0.0",
56
+ "@vitest/coverage-v8": "^4.1.9",
57
+ "@vitest/ui": "4.1.9",
58
+ "glob": "^13.0.6",
59
+ "globals": "^17.7.0",
60
+ "husky": "^9.1.7",
61
+ "jsdom": "^29.1.1",
62
+ "onchange": "^7.1.0",
63
+ "oxfmt": "^0.57.0",
64
+ "oxlint": "^1.72.0",
65
+ "prettier": "^3.9.4",
66
+ "pretty-quick": "^4.2.2",
67
+ "react": "^19.0.0",
68
+ "react-dom": "^19.0.0",
69
+ "rolldown": "1.1.4",
70
+ "rolldown-plugin-dts": "^0.26.0",
71
+ "size-limit": "^12.1.0",
72
+ "tslib": "^2.8.1",
73
+ "tsx": "^4.22.4",
74
+ "typescript": "^6.0.3",
75
+ "vitest": "^4.1.9",
76
+ "@bemedev/app": "^1.6.0",
77
+ "@bemedev/app-vitest": "^1.6.0"
78
+ },
79
+ "peerDependencies": {
80
+ "@bemedev/decompose": "2.2.0",
81
+ "react": "^18.0.0 || ^19.0.0",
82
+ "@bemedev/app": "^1.6.0"
83
+ },
84
+ "size-limit": [
85
+ {
86
+ "path": "lib/*.js",
87
+ "limit": "10 KB"
88
+ },
89
+ {
90
+ "path": "lib/*.cjs",
91
+ "limit": "10 KB"
92
+ }
93
+ ],
94
+ "engines": {
95
+ "node": ">=24"
96
+ },
97
+ "scripts": {
98
+ "build": "pnpm run rm:lib && pnpm run rolldown",
99
+ "_ci": "pnpm run lint && pnpm run test",
100
+ "ci": "CI_START=$(date +%s) && pnpm run config:off && pnpm run lint && pnpm run test && echo \"\n✅ CI takes $(($(date +%s) - CI_START))s\n\"",
101
+ "ci:admin": "pnpm run rm && pnpm run upgrade && pnpm run ci",
102
+ "generate": "app-cli generate -c ./src/__tests__",
103
+ "clean": "pnpm run rm && pnpm run config",
104
+ "config": "pnpm install",
105
+ "config:off": "pnpm run config --offline",
106
+ "fmt": "oxfmt",
107
+ "fmt:check": "oxfmt --check",
108
+ "gen:app": "app generate -c ./src/__tests__",
109
+ "gen:app:watch": "pnpm run gen:app --watch",
110
+ "_lint": "oxlint",
111
+ "lint:fix": "oxlint --fix",
112
+ "lint": "pnpm run fmt && pnpm run lint:fix",
113
+ "lint:watch": "onchange --await-write-finish 5500 \"**/*\" -- pnpm run lint",
114
+ "posttest": "pnpm run fmt",
115
+ "pretest": "pnpm run build && pnpm run size",
116
+ "prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
117
+ "p-q": "pretty-quick",
118
+ "rinit": "pnpm run rm && pnpm run config",
119
+ "rinit:off": "pnpm run rm && pnpm run config:off",
120
+ "rimraf": "pnpx rimraf -rf",
121
+ "rm": "pnpm run rimraf -- node_modules pnpm-lock.yaml",
122
+ "rm:lib": "pnpm run rimraf -- lib",
123
+ "rolldown": "rolldown -c",
124
+ "size": "size-limit",
125
+ "test": "pnpm run test:coverage",
126
+ "test:only": "pnpm run test:coverage --no-coverage",
127
+ "test:coverage": "vitest run",
128
+ "test:ui": "vitest ui",
129
+ "test:watch": "vitest",
130
+ "upgrade": "pnpm upgrade --latest",
131
+ "upgrade:safe": "pnpm run upgrade"
132
+ }
133
+ }