revine 1.4.0 → 1.5.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 +42 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/hooks/useFetch.d.ts +14 -0
- package/dist/hooks/useFetch.d.ts.map +1 -0
- package/dist/hooks/useFetch.js +38 -0
- package/dist/runtime/bundler/revinePlugin.d.ts.map +1 -1
- package/dist/runtime/bundler/revinePlugin.js +106 -98
- package/dist/runtime/cache.d.ts +10 -0
- package/dist/runtime/cache.d.ts.map +1 -0
- package/dist/runtime/cache.js +61 -0
- package/dist/runtime/fetch.d.ts +12 -0
- package/dist/runtime/fetch.d.ts.map +1 -0
- package/dist/runtime/fetch.js +32 -0
- package/package.json +1 -1
- package/roadmap.md +28 -0
- package/src/client.ts +3 -0
- package/src/hooks/useFetch.ts +50 -0
- package/src/runtime/bundler/revinePlugin.ts +106 -99
- package/src/runtime/cache.ts +71 -0
- package/src/runtime/fetch.ts +50 -0
package/README.md
CHANGED
|
@@ -53,6 +53,48 @@ src/pages/about.tsx → /about
|
|
|
53
53
|
|
|
54
54
|
src/pages/blog/[slug].tsx → /blog/:slug
|
|
55
55
|
|
|
56
|
+
## Data Fetching & Caching
|
|
57
|
+
|
|
58
|
+
Revine includes built-in support for cached API calls, allowing you to easily store and reuse server responses.
|
|
59
|
+
|
|
60
|
+
### `revineFetch`
|
|
61
|
+
|
|
62
|
+
A wrapper around the native `fetch` API with caching capabilities.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { revineFetch } from "revine";
|
|
66
|
+
|
|
67
|
+
const data = await revineFetch("https://api.example.com/data", {
|
|
68
|
+
cacheTTL: 60000, // Cache for 1 minute (in ms)
|
|
69
|
+
persist: true // Optional: Persist to localStorage
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `useFetch` Hook
|
|
74
|
+
|
|
75
|
+
A React hook for making cached API calls within components.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useFetch } from "revine";
|
|
79
|
+
|
|
80
|
+
function MyComponent() {
|
|
81
|
+
const { data, loading, error, revalidate } = useFetch("https://api.example.com/data", {
|
|
82
|
+
cacheTTL: 300000, // 5 minutes
|
|
83
|
+
persist: true
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (loading) return <div>Loading...</div>;
|
|
87
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div>
|
|
91
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
92
|
+
<button onClick={() => revalidate()}>Refresh Data</button>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
56
98
|
## Contributing
|
|
57
99
|
|
|
58
100
|
### Clone repository
|
package/dist/client.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export type { LinkProps } from "./components/Link.js";
|
|
|
6
6
|
export { NavLink } from "./components/NavLink.js";
|
|
7
7
|
export type { NavLinkProps } from "./components/NavLink.js";
|
|
8
8
|
export { useRouter } from "./hooks/useRouter.js";
|
|
9
|
+
export { useFetch } from "./hooks/useFetch.js";
|
|
10
|
+
export { revineFetch } from "./runtime/fetch.js";
|
|
11
|
+
export type { RevineFetchOptions } from "./runtime/fetch.js";
|
|
9
12
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
10
13
|
export { env, envAll } from "./runtime/env.js";
|
|
11
14
|
export { middlewareResponse } from "./runtime/middleware.js";
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,8 @@ export { Image } from "./components/Image.js";
|
|
|
3
3
|
export { Link } from "./components/Link.js";
|
|
4
4
|
export { NavLink } from "./components/NavLink.js";
|
|
5
5
|
export { useRouter } from "./hooks/useRouter.js";
|
|
6
|
+
export { useFetch } from "./hooks/useFetch.js";
|
|
7
|
+
export { revineFetch } from "./runtime/fetch.js";
|
|
6
8
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
7
9
|
export { env, envAll } from "./runtime/env.js";
|
|
8
10
|
export { middlewareResponse } from "./runtime/middleware.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RevineFetchOptions } from "../runtime/fetch.js";
|
|
2
|
+
export interface UseFetchResult<T> {
|
|
3
|
+
data: T | null;
|
|
4
|
+
loading: boolean;
|
|
5
|
+
error: Error | null;
|
|
6
|
+
revalidate: () => Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A React hook for making cached API calls.
|
|
10
|
+
* @param url The URL to fetch
|
|
11
|
+
* @param options Fetch and cache options
|
|
12
|
+
*/
|
|
13
|
+
export declare function useFetch<T = any>(url: string, options?: RevineFetchOptions): UseFetchResult<T>;
|
|
14
|
+
//# sourceMappingURL=useFetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFetch.d.ts","sourceRoot":"","sources":["../../src/hooks/useFetch.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAC9B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,cAAc,CAAC,CAAC,CAAC,CA+BnB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { revineFetch } from "../runtime/fetch.js";
|
|
3
|
+
/**
|
|
4
|
+
* A React hook for making cached API calls.
|
|
5
|
+
* @param url The URL to fetch
|
|
6
|
+
* @param options Fetch and cache options
|
|
7
|
+
*/
|
|
8
|
+
export function useFetch(url, options = {}) {
|
|
9
|
+
const [data, setData] = useState(null);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const fetchData = async (isRevalidating = false) => {
|
|
13
|
+
try {
|
|
14
|
+
setLoading(true);
|
|
15
|
+
const result = await revineFetch(url, {
|
|
16
|
+
...options,
|
|
17
|
+
revalidate: isRevalidating || options.revalidate,
|
|
18
|
+
});
|
|
19
|
+
setData(result);
|
|
20
|
+
setError(null);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
setLoading(false);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
fetchData();
|
|
31
|
+
}, [url, JSON.stringify(options)]);
|
|
32
|
+
return {
|
|
33
|
+
data,
|
|
34
|
+
loading,
|
|
35
|
+
error,
|
|
36
|
+
revalidate: () => fetchData(true),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA2VA,wBAAgB,YAAY,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA2VA,wBAAgB,YAAY,IAAI,GAAG,CAmMlC"}
|
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
const VIRTUAL_ROUTING_ID = "\0revine:routing";
|
|
2
2
|
const errorBoundaryComponent = `
|
|
3
|
+
const overlayStyle = {
|
|
4
|
+
position: "fixed", inset: 0,
|
|
5
|
+
background: "rgba(0,0,0,0.72)",
|
|
6
|
+
backdropFilter: "blur(6px)",
|
|
7
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
8
|
+
zIndex: 9999,
|
|
9
|
+
};
|
|
10
|
+
const dialogStyle = {
|
|
11
|
+
background: "#141414",
|
|
12
|
+
border: "1px solid #2a2a2a",
|
|
13
|
+
borderRadius: "14px",
|
|
14
|
+
padding: "0",
|
|
15
|
+
maxWidth: "580px", width: "92%",
|
|
16
|
+
boxShadow: "0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.04) inset",
|
|
17
|
+
color: "#e5e5e5",
|
|
18
|
+
overflow: "hidden",
|
|
19
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
20
|
+
};
|
|
21
|
+
const topBarStyle = {
|
|
22
|
+
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
23
|
+
padding: "12px 18px", background: "#0e0e0e",
|
|
24
|
+
};
|
|
25
|
+
const brandStyle = { display: "flex", alignItems: "center", gap: "7px" };
|
|
26
|
+
const brandNameStyle = {
|
|
27
|
+
fontSize: "13px", fontWeight: 700,
|
|
28
|
+
color: "#c4b5fd", letterSpacing: "0.04em",
|
|
29
|
+
fontFamily: "system-ui, sans-serif",
|
|
30
|
+
};
|
|
31
|
+
const badgeStyle = {
|
|
32
|
+
fontSize: "11px", fontWeight: 600, color: "#f87171",
|
|
33
|
+
background: "rgba(248,113,113,0.1)",
|
|
34
|
+
border: "1px solid rgba(248,113,113,0.2)",
|
|
35
|
+
borderRadius: "999px", padding: "2px 10px", letterSpacing: "0.03em",
|
|
36
|
+
};
|
|
37
|
+
const dividerStyle = { height: "1px", background: "#1f1f1f" };
|
|
38
|
+
const headerStyle = {
|
|
39
|
+
display: "flex", alignItems: "center", gap: "10px",
|
|
40
|
+
padding: "20px 22px 0 22px",
|
|
41
|
+
};
|
|
42
|
+
const iconWrapStyle = {
|
|
43
|
+
width: "28px", height: "28px", borderRadius: "8px",
|
|
44
|
+
background: "rgba(248,113,113,0.1)",
|
|
45
|
+
border: "1px solid rgba(248,113,113,0.15)",
|
|
46
|
+
display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
47
|
+
};
|
|
48
|
+
const titleStyle = { fontSize: "15px", fontWeight: 650, color: "#fff", letterSpacing: "-0.01em" };
|
|
49
|
+
const messagePanelStyle = {
|
|
50
|
+
position: "relative", margin: "14px 22px 0 22px",
|
|
51
|
+
background: "rgba(248,113,113,0.05)",
|
|
52
|
+
border: "1px solid rgba(248,113,113,0.12)",
|
|
53
|
+
borderRadius: "8px", padding: "12px 40px 12px 14px",
|
|
54
|
+
};
|
|
55
|
+
const messageStyle = {
|
|
56
|
+
fontFamily: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
|
|
57
|
+
fontSize: "12.5px", color: "#fca5a5",
|
|
58
|
+
margin: 0, lineHeight: 1.65, wordBreak: "break-word",
|
|
59
|
+
};
|
|
60
|
+
const copyBtnStyle = {
|
|
61
|
+
position: "absolute", top: "10px", right: "10px",
|
|
62
|
+
background: "rgba(255,255,255,0.05)", border: "1px solid #2e2e2e",
|
|
63
|
+
borderRadius: "6px", width: "28px", height: "28px",
|
|
64
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
65
|
+
cursor: "pointer", transition: "background 150ms ease", flexShrink: 0,
|
|
66
|
+
};
|
|
67
|
+
const stackSectionStyle = { margin: "14px 22px 0 22px" };
|
|
68
|
+
const toggleBtnStyle = {
|
|
69
|
+
background: "none", border: "none", cursor: "pointer",
|
|
70
|
+
color: "#666", fontSize: "12px", padding: "4px 0",
|
|
71
|
+
display: "flex", alignItems: "center", gap: "6px",
|
|
72
|
+
letterSpacing: "0.02em", transition: "color 150ms ease",
|
|
73
|
+
};
|
|
74
|
+
const stackStyle = {
|
|
75
|
+
background: "#0a0a0a", border: "1px solid #222", borderRadius: "8px",
|
|
76
|
+
padding: "14px 16px", fontSize: "11px", color: "#888",
|
|
77
|
+
overflowX: "auto", lineHeight: 1.8, marginTop: "8px", marginBottom: 0,
|
|
78
|
+
whiteSpace: "pre-wrap", wordBreak: "break-all",
|
|
79
|
+
fontFamily: "ui-monospace, 'Cascadia Code', monospace",
|
|
80
|
+
};
|
|
81
|
+
const actionsStyle = {
|
|
82
|
+
display: "flex", gap: "10px", padding: "18px 22px 22px 22px", marginTop: "16px",
|
|
83
|
+
};
|
|
84
|
+
const primaryBtnStyle = {
|
|
85
|
+
flex: 1, padding: "10px 0", borderRadius: "8px", border: "none",
|
|
86
|
+
background: "linear-gradient(135deg, #7c3aed, #6d28d9)",
|
|
87
|
+
color: "#fff", fontWeight: 600, fontSize: "13px", cursor: "pointer",
|
|
88
|
+
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
89
|
+
letterSpacing: "0.01em", boxShadow: "0 2px 12px rgba(124,58,237,0.35)",
|
|
90
|
+
fontFamily: "system-ui, sans-serif",
|
|
91
|
+
};
|
|
92
|
+
const secondaryBtnStyle = {
|
|
93
|
+
flex: 1, padding: "10px 0", borderRadius: "8px",
|
|
94
|
+
border: "1px solid #2e2e2e", background: "rgba(255,255,255,0.03)",
|
|
95
|
+
color: "#999", fontSize: "13px", cursor: "pointer",
|
|
96
|
+
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
97
|
+
letterSpacing: "0.01em", fontFamily: "system-ui, sans-serif",
|
|
98
|
+
};
|
|
99
|
+
|
|
3
100
|
function RevineErrorDialog() {
|
|
4
101
|
const error = useRouteError();
|
|
5
102
|
const [expanded, setExpanded] = React.useState(false);
|
|
@@ -142,104 +239,8 @@ function RevineErrorDialog() {
|
|
|
142
239
|
)
|
|
143
240
|
);
|
|
144
241
|
}
|
|
145
|
-
|
|
146
|
-
const overlayStyle = {
|
|
147
|
-
position: "fixed", inset: 0,
|
|
148
|
-
background: "rgba(0,0,0,0.72)",
|
|
149
|
-
backdropFilter: "blur(6px)",
|
|
150
|
-
display: "flex", alignItems: "center", justifyContent: "center",
|
|
151
|
-
zIndex: 9999,
|
|
152
|
-
};
|
|
153
|
-
const dialogStyle = {
|
|
154
|
-
background: "#141414",
|
|
155
|
-
border: "1px solid #2a2a2a",
|
|
156
|
-
borderRadius: "14px",
|
|
157
|
-
padding: "0",
|
|
158
|
-
maxWidth: "580px", width: "92%",
|
|
159
|
-
boxShadow: "0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.04) inset",
|
|
160
|
-
color: "#e5e5e5",
|
|
161
|
-
overflow: "hidden",
|
|
162
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
163
|
-
};
|
|
164
|
-
const topBarStyle = {
|
|
165
|
-
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
166
|
-
padding: "12px 18px", background: "#0e0e0e",
|
|
167
|
-
};
|
|
168
|
-
const brandStyle = { display: "flex", alignItems: "center", gap: "7px" };
|
|
169
|
-
const brandNameStyle = {
|
|
170
|
-
fontSize: "13px", fontWeight: 700,
|
|
171
|
-
color: "#c4b5fd", letterSpacing: "0.04em",
|
|
172
|
-
fontFamily: "system-ui, sans-serif",
|
|
173
|
-
};
|
|
174
|
-
const badgeStyle = {
|
|
175
|
-
fontSize: "11px", fontWeight: 600, color: "#f87171",
|
|
176
|
-
background: "rgba(248,113,113,0.1)",
|
|
177
|
-
border: "1px solid rgba(248,113,113,0.2)",
|
|
178
|
-
borderRadius: "999px", padding: "2px 10px", letterSpacing: "0.03em",
|
|
179
|
-
};
|
|
180
|
-
const dividerStyle = { height: "1px", background: "#1f1f1f" };
|
|
181
|
-
const headerStyle = {
|
|
182
|
-
display: "flex", alignItems: "center", gap: "10px",
|
|
183
|
-
padding: "20px 22px 0 22px",
|
|
184
|
-
};
|
|
185
|
-
const iconWrapStyle = {
|
|
186
|
-
width: "28px", height: "28px", borderRadius: "8px",
|
|
187
|
-
background: "rgba(248,113,113,0.1)",
|
|
188
|
-
border: "1px solid rgba(248,113,113,0.15)",
|
|
189
|
-
display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
190
|
-
};
|
|
191
|
-
const titleStyle = { fontSize: "15px", fontWeight: 650, color: "#fff", letterSpacing: "-0.01em" };
|
|
192
|
-
const messagePanelStyle = {
|
|
193
|
-
position: "relative", margin: "14px 22px 0 22px",
|
|
194
|
-
background: "rgba(248,113,113,0.05)",
|
|
195
|
-
border: "1px solid rgba(248,113,113,0.12)",
|
|
196
|
-
borderRadius: "8px", padding: "12px 40px 12px 14px",
|
|
197
|
-
};
|
|
198
|
-
const messageStyle = {
|
|
199
|
-
fontFamily: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
|
|
200
|
-
fontSize: "12.5px", color: "#fca5a5",
|
|
201
|
-
margin: 0, lineHeight: 1.65, wordBreak: "break-word",
|
|
202
|
-
};
|
|
203
|
-
const copyBtnStyle = {
|
|
204
|
-
position: "absolute", top: "10px", right: "10px",
|
|
205
|
-
background: "rgba(255,255,255,0.05)", border: "1px solid #2e2e2e",
|
|
206
|
-
borderRadius: "6px", width: "28px", height: "28px",
|
|
207
|
-
display: "flex", alignItems: "center", justifyContent: "center",
|
|
208
|
-
cursor: "pointer", transition: "background 150ms ease", flexShrink: 0,
|
|
209
|
-
};
|
|
210
|
-
const stackSectionStyle = { margin: "14px 22px 0 22px" };
|
|
211
|
-
const toggleBtnStyle = {
|
|
212
|
-
background: "none", border: "none", cursor: "pointer",
|
|
213
|
-
color: "#666", fontSize: "12px", padding: "4px 0",
|
|
214
|
-
display: "flex", alignItems: "center", gap: "6px",
|
|
215
|
-
letterSpacing: "0.02em", transition: "color 150ms ease",
|
|
216
|
-
};
|
|
217
|
-
const stackStyle = {
|
|
218
|
-
background: "#0a0a0a", border: "1px solid #222", borderRadius: "8px",
|
|
219
|
-
padding: "14px 16px", fontSize: "11px", color: "#888",
|
|
220
|
-
overflowX: "auto", lineHeight: 1.8, marginTop: "8px", marginBottom: 0,
|
|
221
|
-
whiteSpace: "pre-wrap", wordBreak: "break-all",
|
|
222
|
-
fontFamily: "ui-monospace, 'Cascadia Code', monospace",
|
|
223
|
-
};
|
|
224
|
-
const actionsStyle = {
|
|
225
|
-
display: "flex", gap: "10px", padding: "18px 22px 22px 22px", marginTop: "16px",
|
|
226
|
-
};
|
|
227
|
-
const primaryBtnStyle = {
|
|
228
|
-
flex: 1, padding: "10px 0", borderRadius: "8px", border: "none",
|
|
229
|
-
background: "linear-gradient(135deg, #7c3aed, #6d28d9)",
|
|
230
|
-
color: "#fff", fontWeight: 600, fontSize: "13px", cursor: "pointer",
|
|
231
|
-
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
232
|
-
letterSpacing: "0.01em", boxShadow: "0 2px 12px rgba(124,58,237,0.35)",
|
|
233
|
-
fontFamily: "system-ui, sans-serif",
|
|
234
|
-
};
|
|
235
|
-
const secondaryBtnStyle = {
|
|
236
|
-
flex: 1, padding: "10px 0", borderRadius: "8px",
|
|
237
|
-
border: "1px solid #2e2e2e", background: "rgba(255,255,255,0.03)",
|
|
238
|
-
color: "#999", fontSize: "13px", cursor: "pointer",
|
|
239
|
-
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
240
|
-
letterSpacing: "0.01em", fontFamily: "system-ui, sans-serif",
|
|
241
|
-
};
|
|
242
242
|
`;
|
|
243
|
+
;
|
|
243
244
|
// ── Shared overlay HTML builder (used in both the inline script and module error handler)
|
|
244
245
|
// Written as a plain JS string so it can be embedded inside the injected <script> tag.
|
|
245
246
|
const overlayScriptContent = `
|
|
@@ -453,11 +454,18 @@ function wrapWithLayouts(element, layouts) {
|
|
|
453
454
|
|
|
454
455
|
function toRoutePath(filePath) {
|
|
455
456
|
let p = filePath;
|
|
456
|
-
p = p.replace(
|
|
457
|
+
p = p.replace(/\\\\\\\\/g, "/");
|
|
457
458
|
p = p.replace(/.*\\/pages\\//, "");
|
|
458
459
|
p = p.replace(/\\.tsx$/i, "");
|
|
459
460
|
p = p.replace(/\\([^)]+\\)\\//g, "");
|
|
460
461
|
p = p.replace(/\\/index$/, "");
|
|
462
|
+
|
|
463
|
+
// Handle dynamic routes: [param] -> :param
|
|
464
|
+
p = p.replace(/\\[([^ \\]]+)\\]/g, (_match, param) => {
|
|
465
|
+
if (param.startsWith("...")) return "*";
|
|
466
|
+
return ":" + param;
|
|
467
|
+
});
|
|
468
|
+
|
|
461
469
|
if (p === "index" || p === "") return "/";
|
|
462
470
|
return "/" + p;
|
|
463
471
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare class RevineCache {
|
|
2
|
+
private memoryCache;
|
|
3
|
+
set<T>(key: string, data: T, ttl: number, persist?: boolean): void;
|
|
4
|
+
get<T>(key: string): T | null;
|
|
5
|
+
delete(key: string): void;
|
|
6
|
+
clear(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare const revineCache: RevineCache;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/runtime/cache.ts"],"names":[],"mappings":"AAKA,cAAM,WAAW;IACf,OAAO,CAAC,WAAW,CAAsC;IAEzD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe;IAiBlE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IA4B7B,MAAM,CAAC,GAAG,EAAE,MAAM;IAOlB,KAAK;CAQN;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
class RevineCache {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.memoryCache = new Map();
|
|
4
|
+
}
|
|
5
|
+
set(key, data, ttl, persist = false) {
|
|
6
|
+
const entry = {
|
|
7
|
+
data,
|
|
8
|
+
expiry: Date.now() + ttl,
|
|
9
|
+
};
|
|
10
|
+
this.memoryCache.set(key, entry);
|
|
11
|
+
if (persist && typeof window !== "undefined") {
|
|
12
|
+
try {
|
|
13
|
+
localStorage.setItem(`revine_cache_${key}`, JSON.stringify(entry));
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
console.warn("Revine Cache: Failed to persist to localStorage", e);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get(key) {
|
|
21
|
+
// Check memory cache first
|
|
22
|
+
let entry = this.memoryCache.get(key);
|
|
23
|
+
// If not in memory, check localStorage
|
|
24
|
+
if (!entry && typeof window !== "undefined") {
|
|
25
|
+
try {
|
|
26
|
+
const persisted = localStorage.getItem(`revine_cache_${key}`);
|
|
27
|
+
if (persisted) {
|
|
28
|
+
entry = JSON.parse(persisted);
|
|
29
|
+
// Sync back to memory cache
|
|
30
|
+
if (entry)
|
|
31
|
+
this.memoryCache.set(key, entry);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
console.warn("Revine Cache: Failed to read from localStorage", e);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!entry)
|
|
39
|
+
return null;
|
|
40
|
+
if (Date.now() > entry.expiry) {
|
|
41
|
+
this.delete(key);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return entry.data;
|
|
45
|
+
}
|
|
46
|
+
delete(key) {
|
|
47
|
+
this.memoryCache.delete(key);
|
|
48
|
+
if (typeof window !== "undefined") {
|
|
49
|
+
localStorage.removeItem(`revine_cache_${key}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
clear() {
|
|
53
|
+
this.memoryCache.clear();
|
|
54
|
+
if (typeof window !== "undefined") {
|
|
55
|
+
Object.keys(localStorage)
|
|
56
|
+
.filter((key) => key.startsWith("revine_cache_"))
|
|
57
|
+
.forEach((key) => localStorage.removeItem(key));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export const revineCache = new RevineCache();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RevineFetchOptions extends RequestInit {
|
|
2
|
+
cacheTTL?: number;
|
|
3
|
+
persist?: boolean;
|
|
4
|
+
revalidate?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Enhanced fetch with caching capabilities.
|
|
8
|
+
* @param url The URL to fetch
|
|
9
|
+
* @param options Fetch options plus cache configuration
|
|
10
|
+
*/
|
|
11
|
+
export declare function revineFetch<T = any>(url: string, options?: RevineFetchOptions): Promise<T>;
|
|
12
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/runtime/fetch.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,GAAG,EACvC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,CAAC,CAAC,CAiCZ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { revineCache } from "./cache.js";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced fetch with caching capabilities.
|
|
4
|
+
* @param url The URL to fetch
|
|
5
|
+
* @param options Fetch options plus cache configuration
|
|
6
|
+
*/
|
|
7
|
+
export async function revineFetch(url, options = {}) {
|
|
8
|
+
const { cacheTTL = 0, persist = false, revalidate = false, ...fetchOptions } = options;
|
|
9
|
+
// Cache works if cacheTTL > 0. Usually only for GET, but GraphQL uses POST.
|
|
10
|
+
const isCacheable = cacheTTL > 0;
|
|
11
|
+
// Create a more robust cache key that includes the method and body for POST requests
|
|
12
|
+
const method = (fetchOptions.method || "GET").toUpperCase();
|
|
13
|
+
const bodyKey = fetchOptions.body ? `_body:${fetchOptions.body}` : "";
|
|
14
|
+
const cacheKey = `fetch_${method}_${url}_${JSON.stringify(fetchOptions.headers || {})}${bodyKey}`;
|
|
15
|
+
if (isCacheable && !revalidate) {
|
|
16
|
+
const cachedData = revineCache.get(cacheKey);
|
|
17
|
+
if (cachedData !== null) {
|
|
18
|
+
return cachedData;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const response = await fetch(url, fetchOptions);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
const error = new Error(`Revine Fetch Error: ${response.status} ${response.statusText}`);
|
|
24
|
+
error.status = response.status;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
if (isCacheable) {
|
|
29
|
+
revineCache.set(cacheKey, data, cacheTTL, persist);
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
package/package.json
CHANGED
package/roadmap.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
## SEO
|
|
4
|
+
- remove the index.html file from template
|
|
5
|
+
- remove irrelevant configs from root.tsx
|
|
6
|
+
- add the html like config in the root.tsx
|
|
7
|
+
- meta tags
|
|
8
|
+
- title tag
|
|
9
|
+
- favicon
|
|
10
|
+
and everything else
|
|
11
|
+
|
|
12
|
+
## Rendering
|
|
13
|
+
- add SSG, SSR, ISR, and CSR rendering
|
|
14
|
+
- add a way to configure rendering for each file or component or even function
|
|
15
|
+
|
|
16
|
+
## Image
|
|
17
|
+
- add default declaration for image files (.png, .jpg, .jpeg, .gif, .svg, .webp etc)
|
|
18
|
+
|
|
19
|
+
## File Imports
|
|
20
|
+
- add support for alias imports for public folder
|
|
21
|
+
|
|
22
|
+
## Styling
|
|
23
|
+
- add shadcn support
|
|
24
|
+
- add font support ( google fonts)
|
|
25
|
+
- add theme support with tailwind
|
|
26
|
+
|
|
27
|
+
## API calling
|
|
28
|
+
- caching the server response
|
package/src/client.ts
CHANGED
|
@@ -13,6 +13,9 @@ export type { LinkProps } from "./components/Link.js";
|
|
|
13
13
|
export { NavLink } from "./components/NavLink.js";
|
|
14
14
|
export type { NavLinkProps } from "./components/NavLink.js";
|
|
15
15
|
export { useRouter } from "./hooks/useRouter.js";
|
|
16
|
+
export { useFetch } from "./hooks/useFetch.js";
|
|
17
|
+
export { revineFetch } from "./runtime/fetch.js";
|
|
18
|
+
export type { RevineFetchOptions } from "./runtime/fetch.js";
|
|
16
19
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
17
20
|
export { env, envAll } from "./runtime/env.js";
|
|
18
21
|
export { middlewareResponse } from "./runtime/middleware.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { revineFetch, RevineFetchOptions } from "../runtime/fetch.js";
|
|
3
|
+
|
|
4
|
+
export interface UseFetchResult<T> {
|
|
5
|
+
data: T | null;
|
|
6
|
+
loading: boolean;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
revalidate: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A React hook for making cached API calls.
|
|
13
|
+
* @param url The URL to fetch
|
|
14
|
+
* @param options Fetch and cache options
|
|
15
|
+
*/
|
|
16
|
+
export function useFetch<T = any>(
|
|
17
|
+
url: string,
|
|
18
|
+
options: RevineFetchOptions = {}
|
|
19
|
+
): UseFetchResult<T> {
|
|
20
|
+
const [data, setData] = useState<T | null>(null);
|
|
21
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
22
|
+
const [error, setError] = useState<Error | null>(null);
|
|
23
|
+
|
|
24
|
+
const fetchData = async (isRevalidating: boolean = false) => {
|
|
25
|
+
try {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
const result = await revineFetch<T>(url, {
|
|
28
|
+
...options,
|
|
29
|
+
revalidate: isRevalidating || options.revalidate,
|
|
30
|
+
});
|
|
31
|
+
setData(result);
|
|
32
|
+
setError(null);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
fetchData();
|
|
42
|
+
}, [url, JSON.stringify(options)]);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
data,
|
|
46
|
+
loading,
|
|
47
|
+
error,
|
|
48
|
+
revalidate: () => fetchData(true),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -1,6 +1,103 @@
|
|
|
1
1
|
const VIRTUAL_ROUTING_ID = "\0revine:routing";
|
|
2
2
|
|
|
3
3
|
const errorBoundaryComponent = `
|
|
4
|
+
const overlayStyle = {
|
|
5
|
+
position: "fixed", inset: 0,
|
|
6
|
+
background: "rgba(0,0,0,0.72)",
|
|
7
|
+
backdropFilter: "blur(6px)",
|
|
8
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
9
|
+
zIndex: 9999,
|
|
10
|
+
};
|
|
11
|
+
const dialogStyle = {
|
|
12
|
+
background: "#141414",
|
|
13
|
+
border: "1px solid #2a2a2a",
|
|
14
|
+
borderRadius: "14px",
|
|
15
|
+
padding: "0",
|
|
16
|
+
maxWidth: "580px", width: "92%",
|
|
17
|
+
boxShadow: "0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.04) inset",
|
|
18
|
+
color: "#e5e5e5",
|
|
19
|
+
overflow: "hidden",
|
|
20
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
21
|
+
};
|
|
22
|
+
const topBarStyle = {
|
|
23
|
+
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
24
|
+
padding: "12px 18px", background: "#0e0e0e",
|
|
25
|
+
};
|
|
26
|
+
const brandStyle = { display: "flex", alignItems: "center", gap: "7px" };
|
|
27
|
+
const brandNameStyle = {
|
|
28
|
+
fontSize: "13px", fontWeight: 700,
|
|
29
|
+
color: "#c4b5fd", letterSpacing: "0.04em",
|
|
30
|
+
fontFamily: "system-ui, sans-serif",
|
|
31
|
+
};
|
|
32
|
+
const badgeStyle = {
|
|
33
|
+
fontSize: "11px", fontWeight: 600, color: "#f87171",
|
|
34
|
+
background: "rgba(248,113,113,0.1)",
|
|
35
|
+
border: "1px solid rgba(248,113,113,0.2)",
|
|
36
|
+
borderRadius: "999px", padding: "2px 10px", letterSpacing: "0.03em",
|
|
37
|
+
};
|
|
38
|
+
const dividerStyle = { height: "1px", background: "#1f1f1f" };
|
|
39
|
+
const headerStyle = {
|
|
40
|
+
display: "flex", alignItems: "center", gap: "10px",
|
|
41
|
+
padding: "20px 22px 0 22px",
|
|
42
|
+
};
|
|
43
|
+
const iconWrapStyle = {
|
|
44
|
+
width: "28px", height: "28px", borderRadius: "8px",
|
|
45
|
+
background: "rgba(248,113,113,0.1)",
|
|
46
|
+
border: "1px solid rgba(248,113,113,0.15)",
|
|
47
|
+
display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
48
|
+
};
|
|
49
|
+
const titleStyle = { fontSize: "15px", fontWeight: 650, color: "#fff", letterSpacing: "-0.01em" };
|
|
50
|
+
const messagePanelStyle = {
|
|
51
|
+
position: "relative", margin: "14px 22px 0 22px",
|
|
52
|
+
background: "rgba(248,113,113,0.05)",
|
|
53
|
+
border: "1px solid rgba(248,113,113,0.12)",
|
|
54
|
+
borderRadius: "8px", padding: "12px 40px 12px 14px",
|
|
55
|
+
};
|
|
56
|
+
const messageStyle = {
|
|
57
|
+
fontFamily: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
|
|
58
|
+
fontSize: "12.5px", color: "#fca5a5",
|
|
59
|
+
margin: 0, lineHeight: 1.65, wordBreak: "break-word",
|
|
60
|
+
};
|
|
61
|
+
const copyBtnStyle = {
|
|
62
|
+
position: "absolute", top: "10px", right: "10px",
|
|
63
|
+
background: "rgba(255,255,255,0.05)", border: "1px solid #2e2e2e",
|
|
64
|
+
borderRadius: "6px", width: "28px", height: "28px",
|
|
65
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
66
|
+
cursor: "pointer", transition: "background 150ms ease", flexShrink: 0,
|
|
67
|
+
};
|
|
68
|
+
const stackSectionStyle = { margin: "14px 22px 0 22px" };
|
|
69
|
+
const toggleBtnStyle = {
|
|
70
|
+
background: "none", border: "none", cursor: "pointer",
|
|
71
|
+
color: "#666", fontSize: "12px", padding: "4px 0",
|
|
72
|
+
display: "flex", alignItems: "center", gap: "6px",
|
|
73
|
+
letterSpacing: "0.02em", transition: "color 150ms ease",
|
|
74
|
+
};
|
|
75
|
+
const stackStyle = {
|
|
76
|
+
background: "#0a0a0a", border: "1px solid #222", borderRadius: "8px",
|
|
77
|
+
padding: "14px 16px", fontSize: "11px", color: "#888",
|
|
78
|
+
overflowX: "auto", lineHeight: 1.8, marginTop: "8px", marginBottom: 0,
|
|
79
|
+
whiteSpace: "pre-wrap", wordBreak: "break-all",
|
|
80
|
+
fontFamily: "ui-monospace, 'Cascadia Code', monospace",
|
|
81
|
+
};
|
|
82
|
+
const actionsStyle = {
|
|
83
|
+
display: "flex", gap: "10px", padding: "18px 22px 22px 22px", marginTop: "16px",
|
|
84
|
+
};
|
|
85
|
+
const primaryBtnStyle = {
|
|
86
|
+
flex: 1, padding: "10px 0", borderRadius: "8px", border: "none",
|
|
87
|
+
background: "linear-gradient(135deg, #7c3aed, #6d28d9)",
|
|
88
|
+
color: "#fff", fontWeight: 600, fontSize: "13px", cursor: "pointer",
|
|
89
|
+
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
90
|
+
letterSpacing: "0.01em", boxShadow: "0 2px 12px rgba(124,58,237,0.35)",
|
|
91
|
+
fontFamily: "system-ui, sans-serif",
|
|
92
|
+
};
|
|
93
|
+
const secondaryBtnStyle = {
|
|
94
|
+
flex: 1, padding: "10px 0", borderRadius: "8px",
|
|
95
|
+
border: "1px solid #2e2e2e", background: "rgba(255,255,255,0.03)",
|
|
96
|
+
color: "#999", fontSize: "13px", cursor: "pointer",
|
|
97
|
+
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
98
|
+
letterSpacing: "0.01em", fontFamily: "system-ui, sans-serif",
|
|
99
|
+
};
|
|
100
|
+
|
|
4
101
|
function RevineErrorDialog() {
|
|
5
102
|
const error = useRouteError();
|
|
6
103
|
const [expanded, setExpanded] = React.useState(false);
|
|
@@ -143,104 +240,7 @@ function RevineErrorDialog() {
|
|
|
143
240
|
)
|
|
144
241
|
);
|
|
145
242
|
}
|
|
146
|
-
|
|
147
|
-
const overlayStyle = {
|
|
148
|
-
position: "fixed", inset: 0,
|
|
149
|
-
background: "rgba(0,0,0,0.72)",
|
|
150
|
-
backdropFilter: "blur(6px)",
|
|
151
|
-
display: "flex", alignItems: "center", justifyContent: "center",
|
|
152
|
-
zIndex: 9999,
|
|
153
|
-
};
|
|
154
|
-
const dialogStyle = {
|
|
155
|
-
background: "#141414",
|
|
156
|
-
border: "1px solid #2a2a2a",
|
|
157
|
-
borderRadius: "14px",
|
|
158
|
-
padding: "0",
|
|
159
|
-
maxWidth: "580px", width: "92%",
|
|
160
|
-
boxShadow: "0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.04) inset",
|
|
161
|
-
color: "#e5e5e5",
|
|
162
|
-
overflow: "hidden",
|
|
163
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
164
|
-
};
|
|
165
|
-
const topBarStyle = {
|
|
166
|
-
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
167
|
-
padding: "12px 18px", background: "#0e0e0e",
|
|
168
|
-
};
|
|
169
|
-
const brandStyle = { display: "flex", alignItems: "center", gap: "7px" };
|
|
170
|
-
const brandNameStyle = {
|
|
171
|
-
fontSize: "13px", fontWeight: 700,
|
|
172
|
-
color: "#c4b5fd", letterSpacing: "0.04em",
|
|
173
|
-
fontFamily: "system-ui, sans-serif",
|
|
174
|
-
};
|
|
175
|
-
const badgeStyle = {
|
|
176
|
-
fontSize: "11px", fontWeight: 600, color: "#f87171",
|
|
177
|
-
background: "rgba(248,113,113,0.1)",
|
|
178
|
-
border: "1px solid rgba(248,113,113,0.2)",
|
|
179
|
-
borderRadius: "999px", padding: "2px 10px", letterSpacing: "0.03em",
|
|
180
|
-
};
|
|
181
|
-
const dividerStyle = { height: "1px", background: "#1f1f1f" };
|
|
182
|
-
const headerStyle = {
|
|
183
|
-
display: "flex", alignItems: "center", gap: "10px",
|
|
184
|
-
padding: "20px 22px 0 22px",
|
|
185
|
-
};
|
|
186
|
-
const iconWrapStyle = {
|
|
187
|
-
width: "28px", height: "28px", borderRadius: "8px",
|
|
188
|
-
background: "rgba(248,113,113,0.1)",
|
|
189
|
-
border: "1px solid rgba(248,113,113,0.15)",
|
|
190
|
-
display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
191
|
-
};
|
|
192
|
-
const titleStyle = { fontSize: "15px", fontWeight: 650, color: "#fff", letterSpacing: "-0.01em" };
|
|
193
|
-
const messagePanelStyle = {
|
|
194
|
-
position: "relative", margin: "14px 22px 0 22px",
|
|
195
|
-
background: "rgba(248,113,113,0.05)",
|
|
196
|
-
border: "1px solid rgba(248,113,113,0.12)",
|
|
197
|
-
borderRadius: "8px", padding: "12px 40px 12px 14px",
|
|
198
|
-
};
|
|
199
|
-
const messageStyle = {
|
|
200
|
-
fontFamily: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
|
|
201
|
-
fontSize: "12.5px", color: "#fca5a5",
|
|
202
|
-
margin: 0, lineHeight: 1.65, wordBreak: "break-word",
|
|
203
|
-
};
|
|
204
|
-
const copyBtnStyle = {
|
|
205
|
-
position: "absolute", top: "10px", right: "10px",
|
|
206
|
-
background: "rgba(255,255,255,0.05)", border: "1px solid #2e2e2e",
|
|
207
|
-
borderRadius: "6px", width: "28px", height: "28px",
|
|
208
|
-
display: "flex", alignItems: "center", justifyContent: "center",
|
|
209
|
-
cursor: "pointer", transition: "background 150ms ease", flexShrink: 0,
|
|
210
|
-
};
|
|
211
|
-
const stackSectionStyle = { margin: "14px 22px 0 22px" };
|
|
212
|
-
const toggleBtnStyle = {
|
|
213
|
-
background: "none", border: "none", cursor: "pointer",
|
|
214
|
-
color: "#666", fontSize: "12px", padding: "4px 0",
|
|
215
|
-
display: "flex", alignItems: "center", gap: "6px",
|
|
216
|
-
letterSpacing: "0.02em", transition: "color 150ms ease",
|
|
217
|
-
};
|
|
218
|
-
const stackStyle = {
|
|
219
|
-
background: "#0a0a0a", border: "1px solid #222", borderRadius: "8px",
|
|
220
|
-
padding: "14px 16px", fontSize: "11px", color: "#888",
|
|
221
|
-
overflowX: "auto", lineHeight: 1.8, marginTop: "8px", marginBottom: 0,
|
|
222
|
-
whiteSpace: "pre-wrap", wordBreak: "break-all",
|
|
223
|
-
fontFamily: "ui-monospace, 'Cascadia Code', monospace",
|
|
224
|
-
};
|
|
225
|
-
const actionsStyle = {
|
|
226
|
-
display: "flex", gap: "10px", padding: "18px 22px 22px 22px", marginTop: "16px",
|
|
227
|
-
};
|
|
228
|
-
const primaryBtnStyle = {
|
|
229
|
-
flex: 1, padding: "10px 0", borderRadius: "8px", border: "none",
|
|
230
|
-
background: "linear-gradient(135deg, #7c3aed, #6d28d9)",
|
|
231
|
-
color: "#fff", fontWeight: 600, fontSize: "13px", cursor: "pointer",
|
|
232
|
-
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
233
|
-
letterSpacing: "0.01em", boxShadow: "0 2px 12px rgba(124,58,237,0.35)",
|
|
234
|
-
fontFamily: "system-ui, sans-serif",
|
|
235
|
-
};
|
|
236
|
-
const secondaryBtnStyle = {
|
|
237
|
-
flex: 1, padding: "10px 0", borderRadius: "8px",
|
|
238
|
-
border: "1px solid #2e2e2e", background: "rgba(255,255,255,0.03)",
|
|
239
|
-
color: "#999", fontSize: "13px", cursor: "pointer",
|
|
240
|
-
display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
|
|
241
|
-
letterSpacing: "0.01em", fontFamily: "system-ui, sans-serif",
|
|
242
|
-
};
|
|
243
|
-
`;
|
|
243
|
+
`;;
|
|
244
244
|
|
|
245
245
|
// ── Shared overlay HTML builder (used in both the inline script and module error handler)
|
|
246
246
|
// Written as a plain JS string so it can be embedded inside the injected <script> tag.
|
|
@@ -468,11 +468,18 @@ function wrapWithLayouts(element, layouts) {
|
|
|
468
468
|
|
|
469
469
|
function toRoutePath(filePath) {
|
|
470
470
|
let p = filePath;
|
|
471
|
-
p = p.replace(
|
|
471
|
+
p = p.replace(/\\\\\\\\/g, "/");
|
|
472
472
|
p = p.replace(/.*\\/pages\\//, "");
|
|
473
473
|
p = p.replace(/\\.tsx$/i, "");
|
|
474
474
|
p = p.replace(/\\([^)]+\\)\\//g, "");
|
|
475
475
|
p = p.replace(/\\/index$/, "");
|
|
476
|
+
|
|
477
|
+
// Handle dynamic routes: [param] -> :param
|
|
478
|
+
p = p.replace(/\\[([^ \\]]+)\\]/g, (_match, param) => {
|
|
479
|
+
if (param.startsWith("...")) return "*";
|
|
480
|
+
return ":" + param;
|
|
481
|
+
});
|
|
482
|
+
|
|
476
483
|
if (p === "index" || p === "") return "/";
|
|
477
484
|
return "/" + p;
|
|
478
485
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type CacheEntry<T> = {
|
|
2
|
+
data: T;
|
|
3
|
+
expiry: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
class RevineCache {
|
|
7
|
+
private memoryCache = new Map<string, CacheEntry<any>>();
|
|
8
|
+
|
|
9
|
+
set<T>(key: string, data: T, ttl: number, persist: boolean = false) {
|
|
10
|
+
const entry: CacheEntry<T> = {
|
|
11
|
+
data,
|
|
12
|
+
expiry: Date.now() + ttl,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
this.memoryCache.set(key, entry);
|
|
16
|
+
|
|
17
|
+
if (persist && typeof window !== "undefined") {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem(`revine_cache_${key}`, JSON.stringify(entry));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.warn("Revine Cache: Failed to persist to localStorage", e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get<T>(key: string): T | null {
|
|
27
|
+
// Check memory cache first
|
|
28
|
+
let entry = this.memoryCache.get(key);
|
|
29
|
+
|
|
30
|
+
// If not in memory, check localStorage
|
|
31
|
+
if (!entry && typeof window !== "undefined") {
|
|
32
|
+
try {
|
|
33
|
+
const persisted = localStorage.getItem(`revine_cache_${key}`);
|
|
34
|
+
if (persisted) {
|
|
35
|
+
entry = JSON.parse(persisted);
|
|
36
|
+
// Sync back to memory cache
|
|
37
|
+
if (entry) this.memoryCache.set(key, entry);
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.warn("Revine Cache: Failed to read from localStorage", e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!entry) return null;
|
|
45
|
+
|
|
46
|
+
if (Date.now() > entry.expiry) {
|
|
47
|
+
this.delete(key);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return entry.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
delete(key: string) {
|
|
55
|
+
this.memoryCache.delete(key);
|
|
56
|
+
if (typeof window !== "undefined") {
|
|
57
|
+
localStorage.removeItem(`revine_cache_${key}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
clear() {
|
|
62
|
+
this.memoryCache.clear();
|
|
63
|
+
if (typeof window !== "undefined") {
|
|
64
|
+
Object.keys(localStorage)
|
|
65
|
+
.filter((key) => key.startsWith("revine_cache_"))
|
|
66
|
+
.forEach((key) => localStorage.removeItem(key));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const revineCache = new RevineCache();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { revineCache } from "./cache.js";
|
|
2
|
+
|
|
3
|
+
export interface RevineFetchOptions extends RequestInit {
|
|
4
|
+
cacheTTL?: number; // TTL in milliseconds
|
|
5
|
+
persist?: boolean; // Whether to persist cache to localStorage
|
|
6
|
+
revalidate?: boolean; // If true, force fetch and update cache
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enhanced fetch with caching capabilities.
|
|
11
|
+
* @param url The URL to fetch
|
|
12
|
+
* @param options Fetch options plus cache configuration
|
|
13
|
+
*/
|
|
14
|
+
export async function revineFetch<T = any>(
|
|
15
|
+
url: string,
|
|
16
|
+
options: RevineFetchOptions = {}
|
|
17
|
+
): Promise<T> {
|
|
18
|
+
const { cacheTTL = 0, persist = false, revalidate = false, ...fetchOptions } = options;
|
|
19
|
+
|
|
20
|
+
// Cache works if cacheTTL > 0. Usually only for GET, but GraphQL uses POST.
|
|
21
|
+
const isCacheable = cacheTTL > 0;
|
|
22
|
+
|
|
23
|
+
// Create a more robust cache key that includes the method and body for POST requests
|
|
24
|
+
const method = (fetchOptions.method || "GET").toUpperCase();
|
|
25
|
+
const bodyKey = fetchOptions.body ? `_body:${fetchOptions.body}` : "";
|
|
26
|
+
const cacheKey = `fetch_${method}_${url}_${JSON.stringify(fetchOptions.headers || {})}${bodyKey}`;
|
|
27
|
+
|
|
28
|
+
if (isCacheable && !revalidate) {
|
|
29
|
+
const cachedData = revineCache.get<T>(cacheKey);
|
|
30
|
+
if (cachedData !== null) {
|
|
31
|
+
return cachedData;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const response = await fetch(url, fetchOptions);
|
|
36
|
+
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
const error: any = new Error(`Revine Fetch Error: ${response.status} ${response.statusText}`);
|
|
39
|
+
error.status = response.status;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
|
|
45
|
+
if (isCacheable) {
|
|
46
|
+
revineCache.set(cacheKey, data, cacheTTL, persist);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return data;
|
|
50
|
+
}
|