@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 +79 -0
- package/dist/index.cjs +46 -0
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +21 -0
- package/package.json +21 -0
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
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|