@alepha/react 0.10.6 → 0.10.7
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/dist/index.browser.js +27 -42
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +48 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -57
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/components/ClientOnly.tsx +12 -12
- package/src/components/ErrorBoundary.tsx +43 -43
- package/src/components/ErrorViewer.tsx +140 -140
- package/src/components/Link.tsx +7 -7
- package/src/components/NestedView.tsx +177 -177
- package/src/components/NotFound.tsx +19 -19
- package/src/contexts/RouterLayerContext.ts +3 -3
- package/src/descriptors/$page.ts +292 -290
- package/src/errors/Redirection.ts +5 -5
- package/src/hooks/useActive.ts +41 -41
- package/src/hooks/useAlepha.ts +7 -7
- package/src/hooks/useClient.ts +5 -5
- package/src/hooks/useInject.ts +2 -2
- package/src/hooks/useQueryParams.ts +37 -37
- package/src/hooks/useRouter.ts +1 -1
- package/src/hooks/useRouterEvents.ts +46 -46
- package/src/hooks/useRouterState.ts +5 -5
- package/src/hooks/useSchema.ts +55 -55
- package/src/hooks/useStore.ts +25 -25
- package/src/index.browser.ts +18 -18
- package/src/index.ts +49 -49
- package/src/providers/ReactBrowserProvider.ts +268 -268
- package/src/providers/ReactBrowserRendererProvider.ts +15 -15
- package/src/providers/ReactBrowserRouterProvider.ts +124 -124
- package/src/providers/ReactPageProvider.ts +616 -615
- package/src/providers/ReactServerProvider.ts +505 -505
- package/src/services/ReactRouter.ts +191 -191
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alepha/react",
|
|
3
3
|
"description": "Build server-side rendered (SSR) or single-page React applications.",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.0.0"
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"src"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@alepha/core": "0.10.
|
|
21
|
-
"@alepha/datetime": "0.10.
|
|
22
|
-
"@alepha/logger": "0.10.
|
|
23
|
-
"@alepha/router": "0.10.
|
|
24
|
-
"@alepha/server": "0.10.
|
|
25
|
-
"@alepha/server-cache": "0.10.
|
|
26
|
-
"@alepha/server-links": "0.10.
|
|
27
|
-
"@alepha/server-static": "0.10.
|
|
20
|
+
"@alepha/core": "0.10.7",
|
|
21
|
+
"@alepha/datetime": "0.10.7",
|
|
22
|
+
"@alepha/logger": "0.10.7",
|
|
23
|
+
"@alepha/router": "0.10.7",
|
|
24
|
+
"@alepha/server": "0.10.7",
|
|
25
|
+
"@alepha/server-cache": "0.10.7",
|
|
26
|
+
"@alepha/server-links": "0.10.7",
|
|
27
|
+
"@alepha/server-static": "0.10.7"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@biomejs/biome": "^2.2.6",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@types/react-dom": "^19.2.2",
|
|
33
33
|
"react": "^19.2.0",
|
|
34
34
|
"react-dom": "^19.2.0",
|
|
35
|
-
"tsdown": "^0.15.
|
|
35
|
+
"tsdown": "^0.15.9",
|
|
36
36
|
"typescript": "^5.9.3",
|
|
37
37
|
"vitest": "^3.2.4"
|
|
38
38
|
},
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
type PropsWithChildren,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
6
|
} from "react";
|
|
7
7
|
|
|
8
8
|
export interface ClientOnlyProps {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
disabled?: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -21,15 +21,15 @@ export interface ClientOnlyProps {
|
|
|
21
21
|
* - you want to prevent pre-rendering of a component
|
|
22
22
|
*/
|
|
23
23
|
const ClientOnly = (props: PropsWithChildren<ClientOnlyProps>) => {
|
|
24
|
-
|
|
24
|
+
const [mounted, setMounted] = useState(false);
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
useEffect(() => setMounted(true), []);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
if (props.disabled) {
|
|
29
|
+
return props.children;
|
|
30
|
+
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
return mounted ? props.children : props.fallback;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
export default ClientOnly;
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import React, {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
type ErrorInfo,
|
|
3
|
+
type PropsWithChildren,
|
|
4
|
+
type ReactNode,
|
|
5
5
|
} from "react";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Props for the ErrorBoundary component.
|
|
9
9
|
*/
|
|
10
10
|
export interface ErrorBoundaryProps {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Fallback React node to render when an error is caught.
|
|
13
|
+
* If not provided, a default error message will be shown.
|
|
14
|
+
*/
|
|
15
|
+
fallback: (error: Error) => ReactNode;
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Optional callback that receives the error and error info.
|
|
19
|
+
* Use this to log errors to a monitoring service.
|
|
20
|
+
*/
|
|
21
|
+
onError?: (error: Error, info: ErrorInfo) => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* State of the ErrorBoundary component.
|
|
26
26
|
*/
|
|
27
27
|
interface ErrorBoundaryState {
|
|
28
|
-
|
|
28
|
+
error?: Error;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -33,40 +33,40 @@ interface ErrorBoundaryState {
|
|
|
33
33
|
* in any part of the React component tree.
|
|
34
34
|
*/
|
|
35
35
|
export class ErrorBoundary extends React.Component<
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
PropsWithChildren<ErrorBoundaryProps>,
|
|
37
|
+
ErrorBoundaryState
|
|
38
38
|
> {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
constructor(props: ErrorBoundaryProps) {
|
|
40
|
+
super(props);
|
|
41
|
+
this.state = {};
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Update state so the next render shows the fallback UI.
|
|
46
|
+
*/
|
|
47
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
48
|
+
return {
|
|
49
|
+
error,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Lifecycle method called when an error is caught.
|
|
55
|
+
* You can log the error or perform side effects here.
|
|
56
|
+
*/
|
|
57
|
+
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
58
|
+
if (this.props.onError) {
|
|
59
|
+
this.props.onError(error, info);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
render(): ReactNode {
|
|
64
|
+
if (this.state.error) {
|
|
65
|
+
return this.props.fallback(this.state.error);
|
|
66
|
+
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
return this.props.children;
|
|
69
|
+
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export default ErrorBoundary;
|
|
@@ -2,161 +2,161 @@ import type { Alepha } from "@alepha/core";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
|
|
4
4
|
interface ErrorViewerProps {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
error: Error;
|
|
6
|
+
alepha: Alepha;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// TODO: design this better
|
|
10
10
|
|
|
11
11
|
const ErrorViewer = ({ error, alepha }: ErrorViewerProps) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const [expanded, setExpanded] = useState(false);
|
|
13
|
+
const isProduction = alepha.isProduction();
|
|
14
|
+
// const status = isHttpError(error) ? error.status : 500;
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (isProduction) {
|
|
17
|
+
return <ErrorViewerProduction />;
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const stackLines = error.stack?.split("\n") ?? [];
|
|
21
|
+
const previewLines = stackLines.slice(0, 5);
|
|
22
|
+
const hiddenLineCount = stackLines.length - previewLines.length;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
const copyToClipboard = (text: string) => {
|
|
25
|
+
navigator.clipboard.writeText(text).catch((err) => {
|
|
26
|
+
console.error("Clipboard error:", err);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
30
|
+
const styles = {
|
|
31
|
+
container: {
|
|
32
|
+
padding: "24px",
|
|
33
|
+
backgroundColor: "#FEF2F2",
|
|
34
|
+
color: "#7F1D1D",
|
|
35
|
+
border: "1px solid #FECACA",
|
|
36
|
+
borderRadius: "16px",
|
|
37
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
38
|
+
fontFamily: "monospace",
|
|
39
|
+
maxWidth: "768px",
|
|
40
|
+
margin: "40px auto",
|
|
41
|
+
},
|
|
42
|
+
heading: {
|
|
43
|
+
fontSize: "20px",
|
|
44
|
+
fontWeight: "bold",
|
|
45
|
+
marginBottom: "10px",
|
|
46
|
+
},
|
|
47
|
+
name: {
|
|
48
|
+
fontSize: "16px",
|
|
49
|
+
fontWeight: 600,
|
|
50
|
+
},
|
|
51
|
+
message: {
|
|
52
|
+
fontSize: "14px",
|
|
53
|
+
marginBottom: "16px",
|
|
54
|
+
},
|
|
55
|
+
sectionHeader: {
|
|
56
|
+
display: "flex",
|
|
57
|
+
justifyContent: "space-between",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
fontSize: "12px",
|
|
60
|
+
marginBottom: "4px",
|
|
61
|
+
color: "#991B1B",
|
|
62
|
+
},
|
|
63
|
+
copyButton: {
|
|
64
|
+
fontSize: "12px",
|
|
65
|
+
color: "#DC2626",
|
|
66
|
+
background: "none",
|
|
67
|
+
border: "none",
|
|
68
|
+
cursor: "pointer",
|
|
69
|
+
textDecoration: "underline",
|
|
70
|
+
},
|
|
71
|
+
stackContainer: {
|
|
72
|
+
backgroundColor: "#FEE2E2",
|
|
73
|
+
padding: "12px",
|
|
74
|
+
borderRadius: "8px",
|
|
75
|
+
fontSize: "13px",
|
|
76
|
+
lineHeight: "1.4",
|
|
77
|
+
overflowX: "auto" as const,
|
|
78
|
+
whiteSpace: "pre-wrap" as const,
|
|
79
|
+
},
|
|
80
|
+
expandLine: {
|
|
81
|
+
color: "#F87171",
|
|
82
|
+
cursor: "pointer",
|
|
83
|
+
marginTop: "8px",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
return (
|
|
88
|
+
<div style={styles.container}>
|
|
89
|
+
<div>
|
|
90
|
+
<div style={styles.heading}>🔥 Error</div>
|
|
91
|
+
<div style={styles.name}>{error.name}</div>
|
|
92
|
+
<div style={styles.message}>{error.message}</div>
|
|
93
|
+
</div>
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
{stackLines.length > 0 && (
|
|
96
|
+
<div>
|
|
97
|
+
<div style={styles.sectionHeader}>
|
|
98
|
+
<span>Stack trace</span>
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => copyToClipboard(error.stack!)}
|
|
101
|
+
style={styles.copyButton}
|
|
102
|
+
>
|
|
103
|
+
Copy all
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
<pre style={styles.stackContainer}>
|
|
107
|
+
{(expanded ? stackLines : previewLines).map((line, i) => (
|
|
108
|
+
<div key={i}>{line}</div>
|
|
109
|
+
))}
|
|
110
|
+
{!expanded && hiddenLineCount > 0 && (
|
|
111
|
+
<div style={styles.expandLine} onClick={() => setExpanded(true)}>
|
|
112
|
+
+ {hiddenLineCount} more lines...
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</pre>
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
export default ErrorViewer;
|
|
123
123
|
|
|
124
124
|
const ErrorViewerProduction = () => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
125
|
+
const styles = {
|
|
126
|
+
container: {
|
|
127
|
+
padding: "24px",
|
|
128
|
+
backgroundColor: "#FEF2F2",
|
|
129
|
+
color: "#7F1D1D",
|
|
130
|
+
border: "1px solid #FECACA",
|
|
131
|
+
borderRadius: "16px",
|
|
132
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
|
|
133
|
+
fontFamily: "monospace",
|
|
134
|
+
maxWidth: "768px",
|
|
135
|
+
margin: "40px auto",
|
|
136
|
+
textAlign: "center" as const,
|
|
137
|
+
},
|
|
138
|
+
heading: {
|
|
139
|
+
fontSize: "20px",
|
|
140
|
+
fontWeight: "bold",
|
|
141
|
+
marginBottom: "8px",
|
|
142
|
+
},
|
|
143
|
+
name: {
|
|
144
|
+
fontSize: "16px",
|
|
145
|
+
fontWeight: 600,
|
|
146
|
+
marginBottom: "4px",
|
|
147
|
+
},
|
|
148
|
+
message: {
|
|
149
|
+
fontSize: "14px",
|
|
150
|
+
opacity: 0.85,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
return (
|
|
155
|
+
<div style={styles.container}>
|
|
156
|
+
<div style={styles.heading}>🚨 An error occurred</div>
|
|
157
|
+
<div style={styles.message}>
|
|
158
|
+
Something went wrong. Please try again later.
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
162
|
};
|
package/src/components/Link.tsx
CHANGED
|
@@ -2,17 +2,17 @@ import type { AnchorHTMLAttributes } from "react";
|
|
|
2
2
|
import { useRouter } from "../hooks/useRouter.ts";
|
|
3
3
|
|
|
4
4
|
export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
5
|
-
|
|
5
|
+
href: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const Link = (props: LinkProps) => {
|
|
9
|
-
|
|
9
|
+
const router = useRouter();
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
return (
|
|
12
|
+
<a {...props} {...router.anchor(props.href)}>
|
|
13
|
+
{props.children}
|
|
14
|
+
</a>
|
|
15
|
+
);
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export default Link;
|