Skip to content

Commit 16c1f3d

Browse files
committed
feat(kit): add base
1 parent 92c8176 commit 16c1f3d

10 files changed

Lines changed: 154 additions & 63 deletions

File tree

apps/docs/app/index.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { WaveKitResponse } from "@wavekit/kit";
1+
import { type WaveKitHandler, WaveKitResponse } from "@wavekit/kit";
22
import { wave } from "@wavekit/wave";
3-
import type { BunRequest } from "bun";
43
import dedent from "dedent";
54
import { createHighlighter } from "shiki";
65
import { Layout } from "../src/components/layout";
@@ -9,7 +8,7 @@ const waveKitServerCode = dedent`
98
// src/index.ts
109
import { createWaveKit } from "@wavekit/kit"
1110
12-
createWaveKit().then((routes) => {
11+
createWaveKit().then(({ routes }) => {
1312
console.log("Serving on http://localhost:3000")
1413
Bun.serve({ port: 3000, routes })
1514
})
@@ -57,25 +56,45 @@ const waveKitWaveCode = dedent`
5756

5857
const waveKitKitCode = dedent`
5958
// src/index.ts
60-
import { createWaveKit, ssgRender } from "@wavekit/kit"
59+
import { createWaveKit, ssgRender, type WaveKitHooks } from "@wavekit/kit";
60+
import { findUserByToken } from "./src/auth";
61+
62+
const hooks: WaveKitHooks = {
63+
async beforeHandler(c) {
64+
const bearerToken = c.req.headers.get("Authorization")?.split(" ")?.[1];
65+
if (!bearerToken) {
66+
return c.json({ error: "Unauthorized" }, 401);
67+
}
68+
const user = await findUserByToken(bearerToken);
69+
if (!user) {
70+
return c.json({ error: "Unauthorized" }, 401);
71+
}
72+
c.set("user", user);
73+
},
74+
};
6175
6276
// Option 1: For serving
63-
createWaveKit().then((routes) => {
64-
console.log("Serving on http://localhost:3000")
65-
Bun.serve({ port: 3000, routes })
66-
})
77+
createWaveKit({ hooks }).then(({ routes }) => {
78+
console.log("Serving on http://localhost:3000");
79+
Bun.serve({
80+
port: 3000,
81+
routes,
82+
development: process.env.NODE_ENV === "development",
83+
});
84+
});
6785
6886
// Option 2: For static generation
6987
ssgRender()
7088
`;
7189

72-
export async function GET(req: BunRequest) {
90+
export const GET: WaveKitHandler = async (c) => {
7391
const highlighter = await createHighlighter({
7492
themes: ["github-dark"],
7593
langs: ["typescript"],
7694
});
7795
return WaveKitResponse.html(
7896
Layout(
97+
{ base: c.base },
7998
wave
8099
.div({ style: "margin-top: 6rem;" }, (heroDiv) => {
81100
heroDiv
@@ -86,7 +105,8 @@ export async function GET(req: BunRequest) {
86105
ul.li("Next.js style routing")
87106
.li("Zero extra dependencies")
88107
.li("Lightweight templating engine")
89-
.li("Works flawlessly with HTMX and Alpine.js");
108+
.li("Works flawlessly with HTMX and Alpine.js")
109+
.li("API inspired by Hono, router works as in Next.js");
90110
});
91111
})
92112
.div({ style: "margin-top: 6rem;" }, (gettingStartedDiv) => {
@@ -146,4 +166,4 @@ export async function GET(req: BunRequest) {
146166
}),
147167
),
148168
);
149-
}
169+
};

apps/docs/src/components/layout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { type Child, wave } from "@wavekit/wave";
22
import { Footer } from "./footer";
33
import { Navbar } from "./navbar";
44

5-
export function Layout(slot: Child) {
5+
export function Layout({ base }: { base: string }, slot: Child) {
66
return wave.html({ lang: "en" }, (html) => {
77
html
88
.head((head) => {
@@ -20,7 +20,7 @@ export function Layout(slot: Child) {
2020
})
2121
.body((body) => {
2222
body.main({ class: "container" }, [
23-
Navbar(),
23+
Navbar({ base }),
2424
slot.toString(),
2525
Footer(),
2626
]);

apps/docs/src/components/navbar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const NavItem = ({
1313
return wave.li((li) => li.a({ href, target, rel }, label));
1414
};
1515

16-
export const Navbar = () =>
16+
export const Navbar = ({ base }: { base: string }) =>
1717
wave.nav((nav) => {
1818
nav.ul([Logo]).ul([
19-
NavItem({ href: "/wavekit/docs", label: "Docs" }),
19+
NavItem({ href: `${base}/docs`, label: "Docs" }),
2020
NavItem({
2121
href: "https://github.com/getgrinta/wavekit",
2222
label: "GitHub",

apps/docs/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { createWaveKit, ssgRender } from "@wavekit/kit";
22

33
if (process.env.NODE_ENV === "development") {
4-
createWaveKit().then((routes) => {
4+
createWaveKit({ base: "/wavekit" }).then(({ routes }) => {
55
console.log("Serving on http://localhost:3000");
66
Bun.serve({ port: 3000, routes });
77
});
88
} else {
9-
ssgRender();
9+
ssgRender({ base: "/wavekit" });
1010
}

apps/docs/typedoc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
"../../packages/kit/src/index.ts",
66
"../../packages/wave/src/index.ts"
77
],
8-
"out": "build/docs"
8+
"out": "build/wavekit/docs"
99
}

packages/kit/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
11
# @wavekit/kit
22

33
The server side of WaveKit.
4+
5+
## Usage
6+
7+
```ts
8+
import { createWaveKit, type WaveKitHooks } from "@wavekit/kit";
9+
import { findUserByToken } from "./src/auth";
10+
11+
const hooks: WaveKitHooks = {
12+
async beforeHandler(c) {
13+
const bearerToken = c.req.headers.get("Authorization")?.split(" ")?.[1];
14+
if (!bearerToken) {
15+
return c.json({ error: "Unauthorized" }, 401);
16+
}
17+
const user = await findUserByToken(bearerToken);
18+
if (!user) {
19+
return c.json({ error: "Unauthorized" }, 401);
20+
}
21+
c.set("user", user);
22+
},
23+
};
24+
25+
createWaveKit({ hooks }).then(({ routes }) => {
26+
console.log("Serving on http://localhost:3000");
27+
Bun.serve({
28+
port: 3000,
29+
routes,
30+
development: process.env.NODE_ENV === "development",
31+
});
32+
});
33+
```

packages/kit/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export type {
88
CreateWaveKitProps,
99
WaveKitHandler,
1010
WaveKitContext,
11+
WaveKitHooks,
1112
} from "./server";

packages/kit/src/server.spec.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
import { expect, it } from "bun:test";
22
import path from "node:path";
3-
import { buildRoutes, createWaveKit } from "./server";
3+
import { createWaveKit } from "./server";
44

55
const TEST_DIR = path.join(__dirname, "..", "test", "app");
66

7-
const FAKE_ROUTES = {
8-
"/html.test": path.join(TEST_DIR, "html.test.ts"),
9-
};
10-
11-
it("should prepare routes", async () => {
12-
const routes = await buildRoutes({ routes: FAKE_ROUTES });
13-
expect(routes["/html.test"]).toHaveProperty("GET");
14-
});
15-
167
it("should create wavekit config", async () => {
17-
const routes = await createWaveKit({ routesDir: TEST_DIR });
8+
const { routes } = await createWaveKit({ routesDir: TEST_DIR });
189
expect(routes["/html.test"]).toHaveProperty("GET");
1910
});

packages/kit/src/server.ts

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,44 @@ export type WaveKitContext = {
1717
json: typeof WaveKitResponse.json;
1818
redirect: typeof WaveKitResponse.redirect;
1919
store: Map<string, unknown>;
20+
base: string;
2021
};
2122

2223
export type WaveKitHandler = (
2324
c: WaveKitContext,
24-
) => Response | Promise<Response>;
25+
) => WaveKitResponse | Promise<WaveKitResponse>;
2526

26-
type BuildRoutesProps = {
27-
routes: Record<string, string>;
27+
export type WaveKit = {
28+
routes: Record<string, RouterTypes.RouteHandlerObject<string>>;
29+
store: Map<string, unknown>;
30+
};
31+
32+
export type WaveKitHooks = {
33+
beforeHandler?: (
34+
c: WaveKitContext,
35+
) => Promise<WaveKitContext | WaveKitResponse>;
36+
afterHandler?: (response: WaveKitResponse) => Promise<WaveKitResponse>;
37+
};
38+
39+
export type CreateWaveKitProps = {
40+
base?: string | undefined;
41+
routesDir?: string | undefined;
42+
hooks?: WaveKitHooks;
2843
};
2944

30-
export async function buildRoutes({
31-
routes,
32-
}: BuildRoutesProps): Promise<
33-
Record<string, RouterTypes.RouteHandlerObject<string>>
34-
> {
45+
export async function createWaveKit({
46+
base,
47+
routesDir,
48+
hooks,
49+
}: CreateWaveKitProps = {}): Promise<WaveKit> {
50+
const safeBase = (base ?? "/") === "/" ? "" : (base ?? "");
51+
const router = new Bun.FileSystemRouter({
52+
style: "nextjs",
53+
dir: routesDir ?? defaultRoutesDir,
54+
assetPrefix: "_public",
55+
});
3556
const contextStore = new Map();
36-
const rawRoutes = Object.entries(routes);
57+
const rawRoutes = Object.entries(router.routes);
3758
const routesWithHandlers = rawRoutes.map(async ([path, handlerPath]) => {
3859
const handler = (await import(
3960
handlerPath
@@ -44,43 +65,51 @@ export async function buildRoutes({
4465
method as RouterTypes.HTTPMethod
4566
] as unknown as WaveKitHandler;
4667
if (!methodHandler) return;
47-
contextHandler[method as RouterTypes.HTTPMethod] = (
68+
contextHandler[method as RouterTypes.HTTPMethod] = async (
4869
req: BunRequest,
4970
server: Server,
5071
) => {
5172
const res = new WaveKitResponse();
52-
const context = {
73+
let context = {
5374
req,
5475
res,
5576
html: WaveKitResponse.html,
5677
json: WaveKitResponse.json,
5778
redirect: WaveKitResponse.redirect,
5879
store: contextStore,
80+
base: safeBase,
5981
};
60-
return methodHandler(context);
82+
let beforeHandlerResult: WaveKitResponse | WaveKitContext;
83+
if (hooks?.beforeHandler) {
84+
beforeHandlerResult = await hooks.beforeHandler(context);
85+
if (beforeHandlerResult instanceof Response)
86+
return beforeHandlerResult;
87+
context = beforeHandlerResult as WaveKitContext;
88+
}
89+
let result: WaveKitResponse = new WaveKitResponse();
90+
result = await methodHandler(context);
91+
if (
92+
hooks?.afterHandler &&
93+
result &&
94+
result instanceof WaveKitResponse
95+
) {
96+
result = await hooks.afterHandler(result);
97+
}
98+
return result;
6199
};
62100
}
63-
return [path, contextHandler];
101+
return [safeBase + path, contextHandler];
64102
});
65-
return Object.fromEntries(
103+
const filteredRoutes = Object.fromEntries(
66104
(await Promise.all(routesWithHandlers)).filter(Boolean) as [
67105
string,
68106
RouterTypes.RouteHandlerObject<string>,
69107
][],
70108
);
71-
}
72-
73-
export type CreateWaveKitProps = {
74-
routesDir?: string | undefined;
75-
};
76-
77-
export async function createWaveKit({ routesDir }: CreateWaveKitProps = {}) {
78-
const router = new Bun.FileSystemRouter({
79-
style: "nextjs",
80-
dir: routesDir ?? defaultRoutesDir,
81-
assetPrefix: "_public",
82-
});
83-
return buildRoutes({ routes: router.routes });
109+
return {
110+
routes: filteredRoutes,
111+
store: contextStore,
112+
};
84113
}
85114

86115
function sanitizeRoutePath(routePath: string) {
@@ -89,13 +118,19 @@ function sanitizeRoutePath(routePath: string) {
89118
}
90119

91120
export type SsgRenderProps = {
121+
base?: string | undefined;
92122
routesDir?: string | undefined;
93123
outDir?: string | undefined;
94124
};
95125

96-
export async function ssgRender({ routesDir, outDir }: SsgRenderProps = {}) {
97-
const routes = await createWaveKit({ routesDir });
126+
export async function ssgRender({
127+
base,
128+
routesDir,
129+
outDir,
130+
}: SsgRenderProps = {}) {
131+
const { routes } = await createWaveKit({ base, routesDir });
98132
const server = Bun.serve({
133+
port: 3000,
99134
routes,
100135
});
101136
let renderedRoutes = 0;

packages/kit/test/server.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import path from "node:path";
2-
import { createWaveKit } from "../src";
2+
import { type WaveKitHooks, createWaveKit } from "../src";
33

4-
createWaveKit({ routesDir: path.join(process.cwd(), "test", "app") }).then(
5-
(routes) => {
6-
Bun.serve({ routes });
4+
const hooksEnabled = false;
5+
6+
const hooks: WaveKitHooks = {
7+
async beforeHandler(c) {
8+
if (hooksEnabled) {
9+
// Explicitly return a Response
10+
return c.json({ hooks: "work" });
11+
}
12+
// Explicitly return the context
13+
return c;
714
},
8-
);
15+
};
16+
17+
createWaveKit({
18+
routesDir: path.join(process.cwd(), "test", "app"),
19+
hooks,
20+
}).then(({ routes }) => {
21+
Bun.serve({ routes });
22+
});

0 commit comments

Comments
 (0)