~repos /edge-city
git clone https://pyrossh.dev/repos/edge-city.git
edge-city is a next level meta-framework for react that runs only on edge runtimes
e6ce5bd9
—
Peter John 2 years ago
implement rpc
- packages/example/bun.lockb +0 -0
- packages/example/containers/TodoList/TodoList.jsx +7 -11
- packages/example/{routes → pages}/404/page.css +0 -0
- packages/example/{routes → pages}/404/page.jsx +0 -0
- packages/example/{routes → pages}/about/page.css +0 -0
- packages/example/{routes → pages}/about/page.jsx +0 -0
- packages/example/{routes → pages}/layout.css +0 -0
- packages/example/{routes → pages}/layout.jsx +0 -1
- packages/example/{routes → pages}/page.css +0 -0
- packages/example/{routes → pages}/page.jsx +0 -0
- packages/example/{routes → pages}/page.spec.js +0 -0
- packages/example/{routes → pages}/todos/page.css +0 -0
- packages/example/{routes → pages}/todos/page.jsx +11 -9
- packages/example/routes/api/auth/index.js +0 -54
- packages/example/routes/api/index.js +0 -8
- packages/example/routes/api/todos/index.js +0 -50
- packages/example/services/auth.service.js +50 -0
- packages/example/services/todos.service.js +25 -0
- packages/parotta/cli.js +59 -47
- packages/parotta/router.js +1 -1
- packages/parotta/rpc.js +56 -0
packages/example/bun.lockb
CHANGED
|
Binary file
|
packages/example/containers/TodoList/TodoList.jsx
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Todo from "@/components/Todo/Todo";
|
|
3
|
-
|
|
4
|
-
const todos = [
|
|
5
|
-
|
|
3
|
+
import { getTodos } from "@/services/todos.service";
|
|
6
|
-
|
|
4
|
+
import { useRpc } from "parotta/rpc";
|
|
7
|
-
];
|
|
8
5
|
|
|
9
6
|
const TodoList = () => {
|
|
10
|
-
|
|
7
|
+
const { data } = useRpc(getTodos, {});
|
|
11
8
|
return (
|
|
12
9
|
<div className="todo-list">
|
|
13
|
-
<h1>Todos</h1>
|
|
14
10
|
<ul>
|
|
15
|
-
{
|
|
11
|
+
{data.map((item) => (
|
|
16
|
-
<li key={item.
|
|
12
|
+
<li key={item.id}>
|
|
17
|
-
<Todo
|
|
13
|
+
<Todo todo={item} />
|
|
18
14
|
</li>
|
|
19
15
|
))}
|
|
20
16
|
</ul>
|
|
@@ -22,4 +18,4 @@ const TodoList = () => {
|
|
|
22
18
|
);
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
export default TodoList;
|
|
21
|
+
export default TodoList;
|
packages/example/{routes → pages}/404/page.css
RENAMED
|
File without changes
|
packages/example/{routes → pages}/404/page.jsx
RENAMED
|
File without changes
|
packages/example/{routes → pages}/about/page.css
RENAMED
|
File without changes
|
packages/example/{routes → pages}/about/page.jsx
RENAMED
|
File without changes
|
packages/example/{routes → pages}/layout.css
RENAMED
|
File without changes
|
packages/example/{routes → pages}/layout.jsx
RENAMED
|
@@ -2,7 +2,6 @@ import React, { Suspense } from 'react';
|
|
|
2
2
|
import { Link } from "parotta/router";
|
|
3
3
|
import { ErrorBoundary } from "parotta/error";
|
|
4
4
|
import "./layout.css";
|
|
5
|
-
// import '@blueprintjs/core/lib/css/blueprint.css';
|
|
6
5
|
|
|
7
6
|
const Layout = ({ children }) => {
|
|
8
7
|
return (
|
packages/example/{routes → pages}/page.css
RENAMED
|
File without changes
|
packages/example/{routes → pages}/page.jsx
RENAMED
|
File without changes
|
packages/example/{routes → pages}/page.spec.js
RENAMED
|
File without changes
|
packages/example/{routes → pages}/todos/page.css
RENAMED
|
File without changes
|
packages/example/{routes → pages}/todos/page.jsx
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { useFetch } from "parotta/fetch";
|
|
3
|
-
import Todo from "@/components/Todo/Todo";
|
|
3
|
+
// import Todo from "@/components/Todo/Todo";
|
|
4
|
+
import TodoList from "@/containers/TodoList/TodoList";
|
|
4
5
|
import "./page.css";
|
|
5
6
|
|
|
6
7
|
export const Head = () => {
|
|
@@ -10,21 +11,22 @@ export const Head = () => {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const Body = () => {
|
|
13
|
-
const { data, cache } = useFetch("/api/todos");
|
|
14
|
+
// const { data, cache } = useFetch("/api/todos");
|
|
14
|
-
useEffect(() => {
|
|
15
|
+
// useEffect(() => {
|
|
15
|
-
|
|
16
|
+
// setTimeout(() => {
|
|
16
|
-
|
|
17
|
+
// cache.invalidate(/todos/);
|
|
17
|
-
|
|
18
|
+
// }, 3000)
|
|
18
|
-
}, []);
|
|
19
|
+
// }, []);
|
|
19
20
|
return (
|
|
20
21
|
<div>
|
|
21
22
|
<h1>Todos</h1>
|
|
22
23
|
<ul>
|
|
23
|
-
{data?.map((item) => (
|
|
24
|
+
{/* {data?.map((item) => (
|
|
24
25
|
<li key={item.id}>
|
|
25
26
|
<Todo todo={item} />
|
|
26
27
|
</li>
|
|
27
|
-
))}
|
|
28
|
+
))} */}
|
|
29
|
+
<TodoList />
|
|
28
30
|
</ul>
|
|
29
31
|
</div>
|
|
30
32
|
)
|
packages/example/routes/api/auth/index.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import NextAuth from "next-auth";
|
|
2
|
-
import EmailProvider from "next-auth/providers/email";
|
|
3
|
-
import GoogleProvider from "next-auth/providers/google";
|
|
4
|
-
import DrizzleAuthAdapterPG from "drizzle-auth-adaptor-pg";
|
|
5
|
-
import db from "@/db";
|
|
6
|
-
|
|
7
|
-
// GET /api/auth/signin
|
|
8
|
-
// POST /api/auth/signin/:provider
|
|
9
|
-
// GET/POST /api/auth/callback/:provider
|
|
10
|
-
// GET /api/auth/signout
|
|
11
|
-
// POST /api/auth/signout
|
|
12
|
-
// GET /api/auth/session
|
|
13
|
-
// GET /api/auth/csrf
|
|
14
|
-
// GET /api/auth/providers
|
|
15
|
-
|
|
16
|
-
// NEXTAUTH_SECRET="This is an example"
|
|
17
|
-
// NEXTAUTH_URL
|
|
18
|
-
|
|
19
|
-
// import { SessionProvider } from "next-auth/react"
|
|
20
|
-
// export default function App({
|
|
21
|
-
// Component,
|
|
22
|
-
// pageProps: { session, ...pageProps },
|
|
23
|
-
// }) {
|
|
24
|
-
// return (
|
|
25
|
-
// <SessionProvider session={session}>
|
|
26
|
-
// <Component {...pageProps} />
|
|
27
|
-
// </SessionProvider>
|
|
28
|
-
// )
|
|
29
|
-
// }
|
|
30
|
-
|
|
31
|
-
const handler = NextAuth({
|
|
32
|
-
adapter: DrizzleAuthAdapterPG(db),
|
|
33
|
-
providers: [
|
|
34
|
-
EmailProvider({
|
|
35
|
-
server: {
|
|
36
|
-
host: process.env.SMTP_HOST,
|
|
37
|
-
port: Number(process.env.SMTP_PORT),
|
|
38
|
-
auth: {
|
|
39
|
-
user: process.env.SMTP_USER,
|
|
40
|
-
pass: process.env.SMTP_PASSWORD,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
from: process.env.EMAIL_FROM,
|
|
44
|
-
}),
|
|
45
|
-
GoogleProvider({
|
|
46
|
-
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
47
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
48
|
-
}),
|
|
49
|
-
],
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export const onGet = handler
|
|
53
|
-
|
|
54
|
-
export const onPost = handler
|
packages/example/routes/api/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export const onGet = () => {
|
|
2
|
-
return new Response("ok", {
|
|
3
|
-
headers: {
|
|
4
|
-
"Content-Type": "application/json",
|
|
5
|
-
},
|
|
6
|
-
status: 200,
|
|
7
|
-
});
|
|
8
|
-
}
|
packages/example/routes/api/todos/index.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { gt, eq } from 'drizzle-orm';
|
|
2
|
-
import db, { todos } from "@/db";
|
|
3
|
-
|
|
4
|
-
export const onGet = async (req) => {
|
|
5
|
-
const url = new URL(req.url);
|
|
6
|
-
const page = parseInt(url.searchParams.get("page") || "1", 10);
|
|
7
|
-
const results = await db.select().from(todos).where(gt(todos.id, 0)).limit(page * 5).offset((page - 1) * 5);
|
|
8
|
-
return new Response(JSON.stringify(results), {
|
|
9
|
-
headers: {
|
|
10
|
-
"Content-Type": "application/json",
|
|
11
|
-
},
|
|
12
|
-
status: 200,
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const onPost = async (req) => {
|
|
17
|
-
const body = await req.body();
|
|
18
|
-
const input = JSON.parse(body);
|
|
19
|
-
const data = await db.insert(todos).values(input).returning();
|
|
20
|
-
return new Response(JSON.stringify(data), {
|
|
21
|
-
headers: {
|
|
22
|
-
"Content-Type": "application/json",
|
|
23
|
-
},
|
|
24
|
-
status: 200,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const onPatch = async (req) => {
|
|
29
|
-
const body = await req.body();
|
|
30
|
-
const input = JSON.parse(body);
|
|
31
|
-
const data = await db.update(todos).set(input).where(eq(todos.id, input.id)).returning();
|
|
32
|
-
return new Response(JSON.stringify(data), {
|
|
33
|
-
headers: {
|
|
34
|
-
"Content-Type": "application/json",
|
|
35
|
-
},
|
|
36
|
-
status: 200,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const onDelete = async (req) => {
|
|
41
|
-
const url = new URL(req.url);
|
|
42
|
-
const id = url.searchParams.get("id");
|
|
43
|
-
const data = await db.delete(todos).where(eq(todos.id, id)).returning();
|
|
44
|
-
return new Response(JSON.stringify(data), {
|
|
45
|
-
headers: {
|
|
46
|
-
"Content-Type": "application/json",
|
|
47
|
-
},
|
|
48
|
-
status: 200,
|
|
49
|
-
});
|
|
50
|
-
}
|
packages/example/services/auth.service.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// import NextAuth from "next-auth";
|
|
2
|
+
// import EmailProvider from "next-auth/providers/email";
|
|
3
|
+
// import GoogleProvider from "next-auth/providers/google";
|
|
4
|
+
// import DrizzleAuthAdapterPG from "drizzle-auth-adaptor-pg";
|
|
5
|
+
// import db from "@/db";
|
|
6
|
+
|
|
7
|
+
// GET /api/auth/signin
|
|
8
|
+
// POST /api/auth/signin/:provider
|
|
9
|
+
// GET/POST /api/auth/callback/:provider
|
|
10
|
+
// GET /api/auth/signout
|
|
11
|
+
// POST /api/auth/signout
|
|
12
|
+
// GET /api/auth/session
|
|
13
|
+
// GET /api/auth/csrf
|
|
14
|
+
// GET /api/auth/providers
|
|
15
|
+
|
|
16
|
+
// NEXTAUTH_SECRET="This is an example"
|
|
17
|
+
// NEXTAUTH_URL
|
|
18
|
+
|
|
19
|
+
// import { SessionProvider } from "next-auth/react"
|
|
20
|
+
// export default function App({
|
|
21
|
+
// Component,
|
|
22
|
+
// pageProps: { session, ...pageProps },
|
|
23
|
+
// }) {
|
|
24
|
+
// return (
|
|
25
|
+
// <SessionProvider session={session}>
|
|
26
|
+
// <Component {...pageProps} />
|
|
27
|
+
// </SessionProvider>
|
|
28
|
+
// )
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
// const handler = NextAuth({
|
|
32
|
+
// adapter: DrizzleAuthAdapterPG(db),
|
|
33
|
+
// providers: [
|
|
34
|
+
// EmailProvider({
|
|
35
|
+
// server: {
|
|
36
|
+
// host: process.env.SMTP_HOST,
|
|
37
|
+
// port: Number(process.env.SMTP_PORT),
|
|
38
|
+
// auth: {
|
|
39
|
+
// user: process.env.SMTP_USER,
|
|
40
|
+
// pass: process.env.SMTP_PASSWORD,
|
|
41
|
+
// },
|
|
42
|
+
// },
|
|
43
|
+
// from: process.env.EMAIL_FROM,
|
|
44
|
+
// }),
|
|
45
|
+
// GoogleProvider({
|
|
46
|
+
// clientId: process.env.GOOGLE_CLIENT_ID,
|
|
47
|
+
// clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
48
|
+
// }),
|
|
49
|
+
// ],
|
|
50
|
+
// });
|
packages/example/services/todos.service.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { eq, asc } from 'drizzle-orm';
|
|
2
|
+
import db, { todos } from "@/db";
|
|
3
|
+
|
|
4
|
+
export const getTodos = async () => {
|
|
5
|
+
// console.log("getTodos");
|
|
6
|
+
// return [];
|
|
7
|
+
return await db.select().from(todos).orderBy(asc(todos.id));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const createTodo = async (item) => {
|
|
11
|
+
return await db.insert(todos).values(item).returning();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const getTodo = async (id) => {
|
|
15
|
+
const results = await db.select().from(todos).where(eq(todos.id, id));
|
|
16
|
+
return results[0]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const updateTodo = async (item) => {
|
|
20
|
+
return await db.update(todos).set(item).where(eq(todos.id, item.id)).returning();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const deleteTodo = async (id) => {
|
|
24
|
+
return await db.delete(todos).where(eq(todos.id, id)).returning();
|
|
25
|
+
}
|
packages/parotta/cli.js
CHANGED
|
@@ -24,14 +24,21 @@ console.log(`running with cwd=${path.basename(process.cwd())} node_env=${process
|
|
|
24
24
|
|
|
25
25
|
const isProd = process.env.NODE_ENV === "production";
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const mapServerRoutes = () => {
|
|
28
28
|
const routes = {};
|
|
29
|
-
const dirs = walkdir.sync(path.join(process.cwd(), "
|
|
29
|
+
const dirs = walkdir.sync(path.join(process.cwd(), "pages"))
|
|
30
30
|
.map((s) => s.replace(process.cwd(), "")
|
|
31
|
-
.replace("/
|
|
31
|
+
.replace("/pages", "")
|
|
32
32
|
// .replaceAll("[", ":")
|
|
33
33
|
// .replaceAll("]", "")
|
|
34
34
|
);
|
|
35
|
+
walkdir.sync(path.join(process.cwd(), "services"))
|
|
36
|
+
.map((s) => s.replace(process.cwd(), ""))
|
|
37
|
+
.filter((s) => s.includes(".service.js"))
|
|
38
|
+
.forEach((s) => {
|
|
39
|
+
const serviceName = s.replace(".service.js", "");
|
|
40
|
+
routes[serviceName + "/*"] = { key: serviceName, service: s };
|
|
41
|
+
});
|
|
35
42
|
dirs.filter((p) => p.includes('page.jsx'))
|
|
36
43
|
.map((s) => ({ path: s, route: s.replace("/page.jsx", "") }))
|
|
37
44
|
.forEach((page) => {
|
|
@@ -44,14 +51,6 @@ const mapFiles = () => {
|
|
|
44
51
|
const key = item.route || "/";
|
|
45
52
|
routes[key].layout = item.path;
|
|
46
53
|
});
|
|
47
|
-
dirs.filter((p) => p.includes('index.js'))
|
|
48
|
-
.map((s) => s.replace(process.cwd(), ""))
|
|
49
|
-
.map((s) => ({ path: s, route: s.replace("/index.js", "") }))
|
|
50
|
-
.forEach((api) => {
|
|
51
|
-
const key = api.route || "/";
|
|
52
|
-
routes[key] = routes[key] || { key };
|
|
53
|
-
routes[key].api = api.path;
|
|
54
|
-
});
|
|
55
54
|
walkdir.sync(path.join(process.cwd(), "static"))
|
|
56
55
|
.map((s) => s.replace(process.cwd(), "").replace("/static", ""))
|
|
57
56
|
.forEach((route) => {
|
|
@@ -63,20 +62,25 @@ const mapFiles = () => {
|
|
|
63
62
|
const mapDeps = (dir) => {
|
|
64
63
|
return walkdir.sync(path.join(process.cwd(), dir))
|
|
65
64
|
.map((s) => s.replace(process.cwd(), ""))
|
|
66
|
-
.filter((s) => s.includes(".jsx"))
|
|
65
|
+
.filter((s) => s.includes(".jsx") || s.includes(".js"))
|
|
67
66
|
.reduce((acc, s) => {
|
|
67
|
+
if (s.includes(".jsx")) {
|
|
68
|
-
|
|
68
|
+
acc['@' + s.replace(".jsx", "")] = s
|
|
69
|
+
}
|
|
70
|
+
if (s.includes(".js")) {
|
|
71
|
+
acc['@' + s.replace(".js", "")] = s
|
|
72
|
+
}
|
|
69
73
|
return acc;
|
|
70
74
|
}, {});
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
const mapPages = () => walkdir.sync(path.join(process.cwd(), "
|
|
77
|
+
const mapPages = () => walkdir.sync(path.join(process.cwd(), "pages"))
|
|
74
78
|
.filter((p) => p.includes('page.jsx'))
|
|
75
79
|
.map((s) => s.replace(process.cwd(), ""))
|
|
76
|
-
.map((s) => s.replace("/
|
|
80
|
+
.map((s) => s.replace("/pages", ""))
|
|
77
81
|
.map((s) => s.replace("/page.jsx", ""));
|
|
78
82
|
|
|
79
|
-
const serverSideRoutes =
|
|
83
|
+
const serverSideRoutes = mapServerRoutes();
|
|
80
84
|
const clientSideRoutes = mapPages();
|
|
81
85
|
|
|
82
86
|
const serverRouter = createRouter({
|
|
@@ -86,9 +90,9 @@ const serverRouter = createRouter({
|
|
|
86
90
|
|
|
87
91
|
const clientRoutes = await clientSideRoutes.reduce(async (accp, r) => {
|
|
88
92
|
const acc = await accp;
|
|
89
|
-
const src = await import(`${process.cwd()}/
|
|
93
|
+
const src = await import(`${process.cwd()}/pages${r}/page.jsx`);
|
|
90
|
-
const exists = fs.existsSync(`${process.cwd()}/
|
|
94
|
+
const exists = fs.existsSync(`${process.cwd()}/pages${r}/layout.jsx`);
|
|
91
|
-
const lpath = exists ? `/
|
|
95
|
+
const lpath = exists ? `/pages${r}/layout.jsx` : `/pages/layout.jsx`;
|
|
92
96
|
const lsrc = await import(`${process.cwd()}${lpath}`);
|
|
93
97
|
acc[r === "" ? "/" : r] = {
|
|
94
98
|
Head: src.Head,
|
|
@@ -106,29 +110,16 @@ const clientRouter = createRouter({
|
|
|
106
110
|
routes: clientRoutes,
|
|
107
111
|
});
|
|
108
112
|
|
|
109
|
-
const renderApi = async (filePath, req) => {
|
|
113
|
+
const renderApi = async (key, filePath, req) => {
|
|
114
|
+
const url = new URL(req.url);
|
|
115
|
+
const params = await req.json();
|
|
116
|
+
const funcName = url.pathname.replace(`${key}/`, "");
|
|
110
|
-
const
|
|
117
|
+
const js = await import(path.join(process.cwd(), filePath));
|
|
111
|
-
switch (req.method) {
|
|
112
|
-
case "HEAD":
|
|
113
|
-
|
|
118
|
+
const result = await js[funcName](params);
|
|
114
|
-
case "OPTIONS":
|
|
115
|
-
|
|
119
|
+
return new Response(JSON.stringify(result), {
|
|
116
|
-
case "GET":
|
|
117
|
-
return routeImport.onGet(req);
|
|
118
|
-
case "POST":
|
|
119
|
-
return routeImport.onPost(req);
|
|
120
|
-
case "PUT":
|
|
121
|
-
return routeImport.onPut(req);
|
|
122
|
-
case "PATCH":
|
|
123
|
-
return routeImport.onPatch(req);
|
|
124
|
-
case "DELETE":
|
|
125
|
-
return routeImport.onDelete(req);
|
|
126
|
-
default:
|
|
127
|
-
return new Response(`{"message": "route not found"}`, {
|
|
128
|
-
|
|
120
|
+
headers: { 'Content-Type': 'application/json' },
|
|
129
|
-
|
|
121
|
+
status: 200,
|
|
130
|
-
|
|
122
|
+
});
|
|
131
|
-
}
|
|
132
123
|
}
|
|
133
124
|
|
|
134
125
|
console.log(clientRoutes)
|
|
@@ -157,6 +148,7 @@ const renderPage = async (url) => {
|
|
|
157
148
|
"parotta/router": `/parotta/router.js`,
|
|
158
149
|
"parotta/error": `/parotta/error.js`,
|
|
159
150
|
"parotta/fetch": `/parotta/fetch.js`,
|
|
151
|
+
"parotta/rpc": `/parotta/rpc.js`,
|
|
160
152
|
...nodeDeps,
|
|
161
153
|
...components,
|
|
162
154
|
...containers,
|
|
@@ -191,8 +183,8 @@ const radixRouter = createRouter({
|
|
|
191
183
|
strictTrailingSlash: true,
|
|
192
184
|
routes: {
|
|
193
185
|
${Object.keys(clientRoutes).map((r) => `"${r}": {
|
|
194
|
-
Head: React.lazy(() => import("/
|
|
186
|
+
Head: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Head }))),
|
|
195
|
-
Body: React.lazy(() => import("/
|
|
187
|
+
Body: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Body }))),
|
|
196
188
|
Layout: React.lazy(() => import("${clientRoutes[r].LayoutPath}")),
|
|
197
189
|
LayoutPath: "${clientRoutes[r].LayoutPath}",
|
|
198
190
|
}`).join(',\n ')}
|
|
@@ -255,7 +247,27 @@ const renderJs = async (src) => {
|
|
|
255
247
|
try {
|
|
256
248
|
const jsText = await Bun.file(src).text();
|
|
257
249
|
const result = await transpiler.transform(jsText);
|
|
250
|
+
// inject code which calls the api for that function
|
|
251
|
+
const lines = result.split("\n");
|
|
252
|
+
|
|
253
|
+
// replace all .service imports which rpc interface
|
|
254
|
+
let addRpcImport = false;
|
|
255
|
+
lines.forEach((ln) => {
|
|
256
|
+
if (ln.includes(".service")) {
|
|
257
|
+
addRpcImport = true;
|
|
258
|
+
const [importName, serviceName] = ln.match(/\@\/services\/(.*)\.service/);
|
|
259
|
+
const funcsText = ln.replace(`from "${importName}"`, "").replace("import", "").replace("{", "").replace("}", "").replace(";", "");
|
|
260
|
+
const funcsName = funcsText.trim().split(",");
|
|
261
|
+
funcsName.forEach((fnName) => {
|
|
262
|
+
lines.push(`const ${fnName} = rpc("${serviceName}/${fnName}")`);
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
if (addRpcImport) {
|
|
267
|
+
lines.unshift(`import rpc from "parotta/rpc"`);
|
|
268
|
+
}
|
|
269
|
+
// remove .css and .service imports
|
|
258
|
-
const filteredJsx =
|
|
270
|
+
const filteredJsx = lines.filter((ln) => !ln.includes(".css") && !ln.includes(".service")).join("\n");
|
|
259
271
|
//.replaceAll("$jsx", "React.createElement");
|
|
260
272
|
return new Response(filteredJsx, {
|
|
261
273
|
headers: {
|
|
@@ -310,8 +322,8 @@ export default {
|
|
|
310
322
|
if (match.page && req.headers.get("Accept")?.includes('text/html')) {
|
|
311
323
|
return renderPage(url);
|
|
312
324
|
}
|
|
313
|
-
if (match.
|
|
325
|
+
if (match.service) {
|
|
314
|
-
return renderApi(
|
|
326
|
+
return renderApi(match.key, match.service, req);
|
|
315
327
|
}
|
|
316
328
|
}
|
|
317
329
|
if (req.headers.get("Accept")?.includes('text/html')) {
|
packages/parotta/router.js
CHANGED
|
@@ -14,7 +14,7 @@ const getMatch = (radixRouter, pathname) => {
|
|
|
14
14
|
return matchedPage;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const getCssUrl = (pathname) => `/
|
|
17
|
+
const getCssUrl = (pathname) => `/pages${pathname === "/" ? "" : pathname}`;
|
|
18
18
|
|
|
19
19
|
export const HeadApp = ({ history, radixRouter, importMap }) => {
|
|
20
20
|
const pathname = useSyncExternalStore(history.listen, (v) => v ? v.location.pathname : history.location.pathname, () => history.location.pathname);
|
packages/parotta/rpc.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useState, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
export const domain = () => typeof window !== 'undefined' ? window.origin : "http://0.0.0.0:3000";
|
|
4
|
+
export const globalCache = new Map();
|
|
5
|
+
|
|
6
|
+
const rpc = (serviceName) => async (params = {}) => {
|
|
7
|
+
const res = await fetch(`${domain()}/services/${serviceName}`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: {
|
|
10
|
+
"Accept": "application/json",
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
},
|
|
13
|
+
body: JSON.stringify(params),
|
|
14
|
+
})
|
|
15
|
+
return await res.json();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const useCache = () => {
|
|
19
|
+
const [_, rerender] = useState(false);
|
|
20
|
+
const cache = useMemo(() => globalCache, []);
|
|
21
|
+
const get = (k) => cache.get(k)
|
|
22
|
+
const set = (k, v) => {
|
|
23
|
+
cache.set(k, v);
|
|
24
|
+
rerender((c) => !c);
|
|
25
|
+
}
|
|
26
|
+
const invalidate = (regex) => {
|
|
27
|
+
Array.from(cache.keys())
|
|
28
|
+
.filter((k) => regex.test(k))
|
|
29
|
+
.forEach((k) => {
|
|
30
|
+
fetchData(k).then((v) => set(k, v));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
get,
|
|
35
|
+
set,
|
|
36
|
+
invalidate,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const useRpc = (fn, params) => {
|
|
41
|
+
const cache = useCache();
|
|
42
|
+
const key = `${fn.name}:${JSON.stringify(params)}`;
|
|
43
|
+
const value = cache.get(key);
|
|
44
|
+
if (value) {
|
|
45
|
+
if (value instanceof Promise) {
|
|
46
|
+
throw value;
|
|
47
|
+
} else if (value instanceof Error) {
|
|
48
|
+
throw value;
|
|
49
|
+
}
|
|
50
|
+
return { data: value, cache };
|
|
51
|
+
}
|
|
52
|
+
cache.set(key, fn(params).then((v) => cache.set(key, v)));
|
|
53
|
+
throw cache.get(key);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default rpc;
|