@pulse-js/react 0.1.3 → 0.1.5
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 +11 -1
- package/dist/index.cjs +42 -3
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +40 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -57,13 +57,23 @@ function ProtectedRoute() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (status === "fail") {
|
|
60
|
-
return <AccessDenied message={reason} />;
|
|
60
|
+
return <AccessDenied message={formatReason(reason)} />;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
return <AdminDashboard />;
|
|
64
64
|
}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
### Rendering Failure Reasons
|
|
68
|
+
|
|
69
|
+
The `reason` property in a `GuardState` can be a string or a `GuardReason` object. To render it safely in JSX, use the `formatReason` helper:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { formatReason } from "@pulse-js/react";
|
|
73
|
+
|
|
74
|
+
<p className="error">{formatReason(reason)}</p>;
|
|
75
|
+
```
|
|
76
|
+
|
|
67
77
|
## Developer Tools
|
|
68
78
|
|
|
69
79
|
Pulse provides a dedicated inspector for debugging your reactive graph. In React applications, you can enable it with zero configuration.
|
package/dist/index.cjs
CHANGED
|
@@ -20,27 +20,66 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
formatReason: () => formatReason,
|
|
24
|
+
useGuard: () => useGuard,
|
|
23
25
|
usePulse: () => usePulse
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(index_exports);
|
|
26
28
|
var import_react = require("react");
|
|
29
|
+
var isServer = typeof window === "undefined";
|
|
30
|
+
var promiseCache = /* @__PURE__ */ new WeakMap();
|
|
31
|
+
function getGuardPromise(g) {
|
|
32
|
+
if (promiseCache.has(g)) return promiseCache.get(g);
|
|
33
|
+
const promise = new Promise((resolve) => {
|
|
34
|
+
const check = () => {
|
|
35
|
+
const state = g.state();
|
|
36
|
+
if (state.status !== "pending") {
|
|
37
|
+
unsub();
|
|
38
|
+
resolve(state);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const unsub = g.subscribe(check);
|
|
42
|
+
check();
|
|
43
|
+
});
|
|
44
|
+
promiseCache.set(g, promise);
|
|
45
|
+
return promise;
|
|
46
|
+
}
|
|
27
47
|
function usePulse(unit) {
|
|
28
|
-
const isGuard = "state" in unit;
|
|
48
|
+
const isGuard = unit !== null && typeof unit === "function" && "state" in unit;
|
|
49
|
+
if (isServer) {
|
|
50
|
+
return isGuard ? unit.state() : unit();
|
|
51
|
+
}
|
|
29
52
|
if (isGuard) {
|
|
30
53
|
const g = unit;
|
|
31
54
|
return (0, import_react.useSyncExternalStore)(
|
|
32
55
|
g.subscribe,
|
|
33
|
-
g.state
|
|
56
|
+
() => g.state(),
|
|
57
|
+
() => g.state()
|
|
34
58
|
);
|
|
35
59
|
} else {
|
|
36
60
|
const s = unit;
|
|
37
61
|
return (0, import_react.useSyncExternalStore)(
|
|
38
62
|
s.subscribe,
|
|
39
|
-
s
|
|
63
|
+
() => s(),
|
|
64
|
+
() => s()
|
|
40
65
|
);
|
|
41
66
|
}
|
|
42
67
|
}
|
|
68
|
+
function useGuard(guard, options) {
|
|
69
|
+
const state = usePulse(guard);
|
|
70
|
+
if (options?.suspend && state.status === "pending") {
|
|
71
|
+
throw getGuardPromise(guard);
|
|
72
|
+
}
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
function formatReason(reason) {
|
|
76
|
+
if (!reason) return "";
|
|
77
|
+
if (typeof reason === "string") return reason;
|
|
78
|
+
return reason.toString();
|
|
79
|
+
}
|
|
43
80
|
// Annotate the CommonJS export names for ESM import in node:
|
|
44
81
|
0 && (module.exports = {
|
|
82
|
+
formatReason,
|
|
83
|
+
useGuard,
|
|
45
84
|
usePulse
|
|
46
85
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -26,5 +26,39 @@ import { Source, Guard, GuardState } from '@pulse-js/core';
|
|
|
26
26
|
*/
|
|
27
27
|
declare function usePulse<T>(unit: Source<T>): T;
|
|
28
28
|
declare function usePulse<T>(unit: Guard<T>): GuardState<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Options for useGuard hook.
|
|
31
|
+
*/
|
|
32
|
+
interface UseGuardOptions {
|
|
33
|
+
/**
|
|
34
|
+
* If true, the hook will throw a promise when the guard is in 'pending' state,
|
|
35
|
+
* triggering the nearest React Suspense boundary.
|
|
36
|
+
*/
|
|
37
|
+
suspend?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Explicit hook for Pulse Guards.
|
|
41
|
+
* Returns the current GuardState and updates on changes.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const { ok, value, reason } = useGuard(authGuard, { suspend: true });
|
|
45
|
+
*/
|
|
46
|
+
declare function useGuard<T>(guard: Guard<T>, options?: UseGuardOptions): GuardState<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Formats a guard reason for display in React components.
|
|
49
|
+
* Handles both string reasons and GuardReason objects.
|
|
50
|
+
*
|
|
51
|
+
* @param reason - The reason from a guard state
|
|
52
|
+
* @returns A string suitable for rendering in React
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { status, reason } = usePulse(myGuard);
|
|
57
|
+
* return <p>Error: {formatReason(reason)}</p>;
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function formatReason(reason: string | {
|
|
61
|
+
toString(): string;
|
|
62
|
+
} | undefined): string;
|
|
29
63
|
|
|
30
|
-
export { usePulse };
|
|
64
|
+
export { type UseGuardOptions, formatReason, useGuard, usePulse };
|
package/dist/index.d.ts
CHANGED
|
@@ -26,5 +26,39 @@ import { Source, Guard, GuardState } from '@pulse-js/core';
|
|
|
26
26
|
*/
|
|
27
27
|
declare function usePulse<T>(unit: Source<T>): T;
|
|
28
28
|
declare function usePulse<T>(unit: Guard<T>): GuardState<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Options for useGuard hook.
|
|
31
|
+
*/
|
|
32
|
+
interface UseGuardOptions {
|
|
33
|
+
/**
|
|
34
|
+
* If true, the hook will throw a promise when the guard is in 'pending' state,
|
|
35
|
+
* triggering the nearest React Suspense boundary.
|
|
36
|
+
*/
|
|
37
|
+
suspend?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Explicit hook for Pulse Guards.
|
|
41
|
+
* Returns the current GuardState and updates on changes.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const { ok, value, reason } = useGuard(authGuard, { suspend: true });
|
|
45
|
+
*/
|
|
46
|
+
declare function useGuard<T>(guard: Guard<T>, options?: UseGuardOptions): GuardState<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Formats a guard reason for display in React components.
|
|
49
|
+
* Handles both string reasons and GuardReason objects.
|
|
50
|
+
*
|
|
51
|
+
* @param reason - The reason from a guard state
|
|
52
|
+
* @returns A string suitable for rendering in React
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const { status, reason } = usePulse(myGuard);
|
|
57
|
+
* return <p>Error: {formatReason(reason)}</p>;
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function formatReason(reason: string | {
|
|
61
|
+
toString(): string;
|
|
62
|
+
} | undefined): string;
|
|
29
63
|
|
|
30
|
-
export { usePulse };
|
|
64
|
+
export { type UseGuardOptions, formatReason, useGuard, usePulse };
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,58 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { useSyncExternalStore } from "react";
|
|
3
|
+
var isServer = typeof window === "undefined";
|
|
4
|
+
var promiseCache = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function getGuardPromise(g) {
|
|
6
|
+
if (promiseCache.has(g)) return promiseCache.get(g);
|
|
7
|
+
const promise = new Promise((resolve) => {
|
|
8
|
+
const check = () => {
|
|
9
|
+
const state = g.state();
|
|
10
|
+
if (state.status !== "pending") {
|
|
11
|
+
unsub();
|
|
12
|
+
resolve(state);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const unsub = g.subscribe(check);
|
|
16
|
+
check();
|
|
17
|
+
});
|
|
18
|
+
promiseCache.set(g, promise);
|
|
19
|
+
return promise;
|
|
20
|
+
}
|
|
3
21
|
function usePulse(unit) {
|
|
4
|
-
const isGuard = "state" in unit;
|
|
22
|
+
const isGuard = unit !== null && typeof unit === "function" && "state" in unit;
|
|
23
|
+
if (isServer) {
|
|
24
|
+
return isGuard ? unit.state() : unit();
|
|
25
|
+
}
|
|
5
26
|
if (isGuard) {
|
|
6
27
|
const g = unit;
|
|
7
28
|
return useSyncExternalStore(
|
|
8
29
|
g.subscribe,
|
|
9
|
-
g.state
|
|
30
|
+
() => g.state(),
|
|
31
|
+
() => g.state()
|
|
10
32
|
);
|
|
11
33
|
} else {
|
|
12
34
|
const s = unit;
|
|
13
35
|
return useSyncExternalStore(
|
|
14
36
|
s.subscribe,
|
|
15
|
-
s
|
|
37
|
+
() => s(),
|
|
38
|
+
() => s()
|
|
16
39
|
);
|
|
17
40
|
}
|
|
18
41
|
}
|
|
42
|
+
function useGuard(guard, options) {
|
|
43
|
+
const state = usePulse(guard);
|
|
44
|
+
if (options?.suspend && state.status === "pending") {
|
|
45
|
+
throw getGuardPromise(guard);
|
|
46
|
+
}
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
function formatReason(reason) {
|
|
50
|
+
if (!reason) return "";
|
|
51
|
+
if (typeof reason === "string") return reason;
|
|
52
|
+
return reason.toString();
|
|
53
|
+
}
|
|
19
54
|
export {
|
|
55
|
+
formatReason,
|
|
56
|
+
useGuard,
|
|
20
57
|
usePulse
|
|
21
58
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-js/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"react": "^19.2.3"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@pulse-js/core": "^0.1.
|
|
32
|
-
"@pulse-js/tools": "^0.1.
|
|
31
|
+
"@pulse-js/core": "^0.1.8",
|
|
32
|
+
"@pulse-js/tools": "^0.1.5"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/bun": "latest",
|