@pulse-js/react 0.1.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,79 @@
1
+ # @pulse-js/react
2
+
3
+ React integration for the Pulse reactivity ecosystem. Provides hooks and utilities to consume Pulse Sources and Guards within React components efficiently.
4
+
5
+ ## Features
6
+
7
+ - **Concurrent Mode Compatible**: Built with `useSyncExternalStore` for compatibility with React 18+ concurrent features.
8
+ - **Zero Polling**: Logic driven by direct subscriptions to the Pulse core, ensuring updates happen exactly when state changes.
9
+ - **Type Safety**: Full TypeScript support for inferred types from Sources and Guards.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @pulse-js/react @pulse-js/core
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ The primary API is the `usePulse` hook. It adapts automatically depending on whether you pass it a Source or a Guard.
20
+
21
+ ### Using Sources
22
+
23
+ When used with a **Source**, `usePulse` returns the current value and triggers a re-render whenever that value updates.
24
+
25
+ ```jsx
26
+ import { source } from "@pulse-js/core";
27
+ import { usePulse } from "@pulse-js/react";
28
+
29
+ const counter = source(0);
30
+
31
+ function Counter() {
32
+ const value = usePulse(counter);
33
+
34
+ return (
35
+ <button onClick={() => counter.update((n) => n + 1)}>Count: {value}</button>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ### Using Guards
41
+
42
+ When used with a **Guard**, `usePulse` returns the complete `GuardState` object, which includes `status`, `value`, and `reason`. This allows you to handle loading and error states declaratively.
43
+
44
+ ```tsx
45
+ import { guard } from "@pulse-js/core";
46
+ import { usePulse } from "@pulse-js/react";
47
+
48
+ const isAuthorized = guard("auth-check", async () => {
49
+ // ... async logic
50
+ });
51
+
52
+ function ProtectedRoute() {
53
+ const { status, reason } = usePulse(isAuthorized);
54
+
55
+ if (status === "pending") {
56
+ return <LoadingSpinner />;
57
+ }
58
+
59
+ if (status === "fail") {
60
+ return <AccessDenied message={reason} />;
61
+ }
62
+
63
+ return <AdminDashboard />;
64
+ }
65
+ ```
66
+
67
+ ## API
68
+
69
+ ### `usePulse<T>(unit: PulseUnit<T>): T | GuardState<T>`
70
+
71
+ - **Arguments**:
72
+ - `unit`: A Pulse Source or Guard.
73
+ - **Returns**:
74
+ - For Sources: The inner value `T`.
75
+ - For Guards: An object `{ status: 'ok' | 'fail' | 'pending', value?: T, reason?: string }`.
76
+
77
+ ## Performance
78
+
79
+ `@pulse-js/react` leverages modern React 18 patterns to ensure optimal performance. It avoids unnecessary re-renders by strictly tracking object references and using the `useSyncExternalStore` API to integrate with React's scheduling system.
package/dist/index.cjs ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ usePulse: () => usePulse
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_react = require("react");
27
+ function usePulse(unit) {
28
+ const isGuard = "state" in unit;
29
+ if (isGuard) {
30
+ const g = unit;
31
+ return (0, import_react.useSyncExternalStore)(
32
+ g.subscribe,
33
+ g.state
34
+ );
35
+ } else {
36
+ const s = unit;
37
+ return (0, import_react.useSyncExternalStore)(
38
+ s.subscribe,
39
+ s
40
+ );
41
+ }
42
+ }
43
+ // Annotate the CommonJS export names for ESM import in node:
44
+ 0 && (module.exports = {
45
+ usePulse
46
+ });
@@ -0,0 +1,30 @@
1
+ import { Source, Guard, GuardState } from '@pulse-js/core';
2
+
3
+ /**
4
+ * Hook to consume a Pulse Unit (Source or Guard) in a React component.
5
+ *
6
+ * - If passed a **Source**, it returns the current value and triggers a re-render when the value changes.
7
+ * - If passed a **Guard**, it returns the current `GuardState` (ok, fail, pending, reason, value)
8
+ * and triggers a re-render when the status or value changes.
9
+ *
10
+ * @template T The underlying type of the reactive unit.
11
+ * @param unit The Pulse Source or Guard to observe.
12
+ * @returns The current value or guard state.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // Using a Source
17
+ * const count = usePulse(countSource);
18
+ *
19
+ * // Using a Guard
20
+ * const { status, reason, value } = usePulse(authGuard);
21
+ *
22
+ * if (status === 'pending') return <Loading />;
23
+ * if (status === 'fail') return <ErrorMessage message={reason} />;
24
+ * return <Dashboard user={value} />;
25
+ * ```
26
+ */
27
+ declare function usePulse<T>(unit: Source<T>): T;
28
+ declare function usePulse<T>(unit: Guard<T>): GuardState<T>;
29
+
30
+ export { usePulse };
@@ -0,0 +1,30 @@
1
+ import { Source, Guard, GuardState } from '@pulse-js/core';
2
+
3
+ /**
4
+ * Hook to consume a Pulse Unit (Source or Guard) in a React component.
5
+ *
6
+ * - If passed a **Source**, it returns the current value and triggers a re-render when the value changes.
7
+ * - If passed a **Guard**, it returns the current `GuardState` (ok, fail, pending, reason, value)
8
+ * and triggers a re-render when the status or value changes.
9
+ *
10
+ * @template T The underlying type of the reactive unit.
11
+ * @param unit The Pulse Source or Guard to observe.
12
+ * @returns The current value or guard state.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // Using a Source
17
+ * const count = usePulse(countSource);
18
+ *
19
+ * // Using a Guard
20
+ * const { status, reason, value } = usePulse(authGuard);
21
+ *
22
+ * if (status === 'pending') return <Loading />;
23
+ * if (status === 'fail') return <ErrorMessage message={reason} />;
24
+ * return <Dashboard user={value} />;
25
+ * ```
26
+ */
27
+ declare function usePulse<T>(unit: Source<T>): T;
28
+ declare function usePulse<T>(unit: Guard<T>): GuardState<T>;
29
+
30
+ export { usePulse };
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // src/index.ts
2
+ import { useSyncExternalStore } from "react";
3
+ function usePulse(unit) {
4
+ const isGuard = "state" in unit;
5
+ if (isGuard) {
6
+ const g = unit;
7
+ return useSyncExternalStore(
8
+ g.subscribe,
9
+ g.state
10
+ );
11
+ } else {
12
+ const s = unit;
13
+ return useSyncExternalStore(
14
+ s.subscribe,
15
+ s
16
+ );
17
+ }
18
+ }
19
+ export {
20
+ usePulse
21
+ };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@pulse-js/react",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "bun x tsup src/index.ts --format esm,cjs --dts --clean --external react",
13
+ "lint": "bun x tsc --noEmit"
14
+ },
15
+ "peerDependencies": {
16
+ "react": "^19.2.3"
17
+ },
18
+ "dependencies": {
19
+ "@pulse-js/core": "^0.1.0"
20
+ }
21
+ }