weifuwu 0.2.1 → 0.2.3
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 +168 -0
- package/dist/compress.d.ts +6 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1420 -0
- package/dist/middleware.d.ts +21 -0
- package/dist/rate-limit.d.ts +8 -0
- package/dist/router.d.ts +55 -0
- package/dist/serve.d.ts +19 -0
- package/dist/static.d.ts +7 -0
- package/dist/tsx.d.ts +17 -0
- package/dist/types.d.ts +9 -0
- package/dist/upload.d.ts +14 -0
- package/dist/validate.d.ts +9 -0
- package/package.json +14 -2
- package/AGENTS.md +0 -105
- package/compress.ts +0 -69
- package/cookie.ts +0 -58
- package/index.ts +0 -21
- package/middleware.ts +0 -178
- package/rate-limit.ts +0 -68
- package/router.ts +0 -702
- package/serve.ts +0 -126
- package/static.ts +0 -113
- package/test/compress.test.ts +0 -106
- package/test/cookie.test.ts +0 -79
- package/test/fixtures/pages/about/page.tsx +0 -3
- package/test/fixtures/pages/blog/[slug]/load.ts +0 -3
- package/test/fixtures/pages/blog/[slug]/page.tsx +0 -3
- package/test/fixtures/pages/blog/[slug]/route.ts +0 -7
- package/test/fixtures/pages/blog/layout.tsx +0 -3
- package/test/fixtures/pages/layout.tsx +0 -12
- package/test/fixtures/pages/page.tsx +0 -3
- package/test/middleware.test.ts +0 -407
- package/test/rate-limit.test.ts +0 -94
- package/test/static.test.ts +0 -93
- package/test/tsx.test.ts +0 -285
- package/test/unode.test.ts +0 -401
- package/test/upload.test.ts +0 -130
- package/test/validate.test.ts +0 -133
- package/tsconfig.json +0 -13
- package/tsx.ts +0 -354
- package/types.ts +0 -23
- package/upload.ts +0 -101
- package/validate.ts +0 -88
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- **Trie router** — static > param > wildcard, sub-router mounting, path params
|
|
9
9
|
- **Middleware** — global, path-scoped, route-level — onion model, short-circuit
|
|
10
10
|
- **Built-in middleware** — `auth()`, `cors()`, `logger()`, `rateLimit()`, `compress()`
|
|
11
|
+
- **React SSR + Hydration** — `tsx({ dir })` — page.tsx / load.ts / layout.tsx / route.ts
|
|
11
12
|
- **WebSocket** — `router.ws()` with upgrade middleware (auth before connect)
|
|
12
13
|
- **GraphQL** — `router.graphql()` with GraphiQL IDE
|
|
13
14
|
- **AI streaming** — `router.ai()` via Vercel AI SDK
|
|
@@ -27,6 +28,158 @@ import { serve } from 'weifuwu'
|
|
|
27
28
|
serve((req, ctx) => new Response('Hello, World!'), { port: 3000 })
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
## React pages with tsx()
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { serve, Router } from 'weifuwu'
|
|
35
|
+
import { tsx } from 'weifuwu/tsx'
|
|
36
|
+
|
|
37
|
+
const app = new Router()
|
|
38
|
+
app.use('/', await tsx({ dir: './pages/' }))
|
|
39
|
+
|
|
40
|
+
serve(app.handler(), { port: 3000 })
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### File conventions
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pages/
|
|
47
|
+
page.tsx → GET / (React component, default export)
|
|
48
|
+
layout.tsx → root layout (HTML shell, receives req/ctx, NOT hydrated)
|
|
49
|
+
about/page.tsx → GET /about
|
|
50
|
+
blog/[slug]/
|
|
51
|
+
page.tsx → GET /blog/:slug
|
|
52
|
+
load.ts → data fetching (server-only, default export)
|
|
53
|
+
route.ts → POST /blog/:slug (API, named exports GET/POST/...)
|
|
54
|
+
blog/layout.tsx → /blog/* layout (UI structure, receives children, hydrated)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### page.tsx — page component
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
export default function Page({ params, query }: {
|
|
61
|
+
params: { slug: string }
|
|
62
|
+
query: Record<string, string>
|
|
63
|
+
}) {
|
|
64
|
+
return <article><h1>{params.slug}</h1></article>
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### load.ts — data fetching (server-only)
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { db } from './db.ts'
|
|
72
|
+
|
|
73
|
+
export default async function load({ params, query }: {
|
|
74
|
+
params: Record<string, string>
|
|
75
|
+
query: Record<string, string>
|
|
76
|
+
}) {
|
|
77
|
+
const data = await db.query(params.slug)
|
|
78
|
+
return { data } // merged into props passed to page.tsx
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`load()` runs only on the server. Its return value is merged with `{ params, query }` and passed to the page component. The merged props are serialized as `window.__WEIFUWU_PROPS` for client hydration.
|
|
83
|
+
|
|
84
|
+
### layout.tsx — root layout vs nested layouts
|
|
85
|
+
|
|
86
|
+
Two types of layouts, distinguished by their position in the directory tree:
|
|
87
|
+
|
|
88
|
+
**Root layout** (`pages/layout.tsx`) — receives `{ children, req, ctx }`:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
export default function RootLayout({ children, req, ctx }: {
|
|
92
|
+
children: React.ReactNode
|
|
93
|
+
req: Request
|
|
94
|
+
ctx: Context
|
|
95
|
+
}) {
|
|
96
|
+
const theme = req.headers.get('Cookie')?.includes('theme=dark') ? 'dark' : 'light'
|
|
97
|
+
return (
|
|
98
|
+
<html class={theme}>
|
|
99
|
+
<head><title>App</title></head>
|
|
100
|
+
<body>
|
|
101
|
+
<div id="__weifuwu_root">{children}</div>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Controls the full HTML shell (`<html>`, `<head>`, `<body>`)
|
|
109
|
+
- Has access to `req`/`ctx` for cookie/header-based customization
|
|
110
|
+
- **Not hydrated** — safe to use `req`/`ctx` (never serialized to client)
|
|
111
|
+
|
|
112
|
+
**Nested layouts** (`pages/blog/layout.tsx`) — receives only `{ children }`:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
export default function BlogLayout({ children }: { children: React.ReactNode }) {
|
|
116
|
+
return <div className="sidebar-layout">{children}</div>
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- Provide UI structure (sidebar, nav, search box)
|
|
121
|
+
- **Hydrated** on the client — can use `useState`, event handlers
|
|
122
|
+
- No access to `req`/`ctx` (not serializable)
|
|
123
|
+
|
|
124
|
+
Layouts auto-nest by directory depth — `pages/blog/layout.tsx` wraps `pages/blog/*` pages inside `pages/layout.tsx`.
|
|
125
|
+
|
|
126
|
+
### `TsxContext` and `useTsx()`
|
|
127
|
+
|
|
128
|
+
Any component in the tree can access routing context without prop drilling:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { useTsx } from 'weifuwu'
|
|
132
|
+
|
|
133
|
+
function Sidebar() {
|
|
134
|
+
const { params, query, user, parsed } = useTsx()
|
|
135
|
+
// params.slug, query.page, user.name — from any depth
|
|
136
|
+
return <aside>User: {user?.name}</aside>
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Available fields:
|
|
141
|
+
|
|
142
|
+
| Field | Source | Description |
|
|
143
|
+
|-------|--------|-------------|
|
|
144
|
+
| `params` | URL path | Route parameters (`:slug`, `:id`) |
|
|
145
|
+
| `query` | URL search | Query string (`?page=1`) |
|
|
146
|
+
| `user` | `auth()` middleware | Set by `verify` callback |
|
|
147
|
+
| `parsed` | `validate()` / `upload()` | Validated body / uploaded files |
|
|
148
|
+
|
|
149
|
+
### route.ts — API (co-located with page)
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
export const POST: Handler = async (req, ctx) => {
|
|
153
|
+
const body = await req.json()
|
|
154
|
+
return Response.json({ ...body, slug: ctx.params.slug })
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Route.ts exports `POST`/`PUT`/`DELETE`/`PATCH` (GET is handled by page.tsx). The same `route.ts` file coexists with `page.tsx` in the same directory for handling form submissions or AJAX requests.
|
|
159
|
+
|
|
160
|
+
### Usage within a full app
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { serve, Router } from 'weifuwu'
|
|
164
|
+
import { tsx } from 'weifuwu/tsx'
|
|
165
|
+
|
|
166
|
+
const r = new Router()
|
|
167
|
+
r.use('/', await tsx({ dir: './pages/' }))
|
|
168
|
+
|
|
169
|
+
// Other features coexist in the same process
|
|
170
|
+
r.ws('/chat', { message(ws, _, data) { ws.send(data) } })
|
|
171
|
+
r.graphql('/graphql', { schema: `...`, resolvers: { ... } })
|
|
172
|
+
|
|
173
|
+
serve(r.handler())
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
node --watch app.ts # development
|
|
178
|
+
node app.ts # production
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
No build step, no configuration file — just Node.js and React.
|
|
182
|
+
|
|
30
183
|
## Router
|
|
31
184
|
|
|
32
185
|
```ts
|
|
@@ -277,6 +430,18 @@ const app = new Router()
|
|
|
277
430
|
|
|
278
431
|
Returns `{ stop, port, hostname, ready }`.
|
|
279
432
|
|
|
433
|
+
### `tsx(options)`
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
import { tsx } from 'weifuwu/tsx'
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
| Option | Default | Description |
|
|
440
|
+
|--------|---------|-------------|
|
|
441
|
+
| `dir` | — | Pages directory path |
|
|
442
|
+
|
|
443
|
+
Returns `Promise<Router>`.
|
|
444
|
+
|
|
280
445
|
### `Router`
|
|
281
446
|
|
|
282
447
|
| Method | Description |
|
|
@@ -310,6 +475,9 @@ Returns `{ stop, port, hostname, ready }`.
|
|
|
310
475
|
| `getCookies(req)` | Parse Cookie header → object |
|
|
311
476
|
| `setCookie(res, name, value, options?)` | Set cookie (returns new Response) |
|
|
312
477
|
| `deleteCookie(res, name)` | Delete cookie (returns new Response) |
|
|
478
|
+
| `useTsx()` | Hook returning `{ params, query, user, parsed }` from `TsxContext` |
|
|
479
|
+
|
|
480
|
+
Import `useTsx` and `TsxContext` from `'weifuwu'`.
|
|
313
481
|
|
|
314
482
|
## License
|
|
315
483
|
|
package/dist/cookie.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CookieOptions {
|
|
2
|
+
domain?: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
maxAge?: number;
|
|
5
|
+
expires?: Date;
|
|
6
|
+
httpOnly?: boolean;
|
|
7
|
+
secure?: boolean;
|
|
8
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
9
|
+
}
|
|
10
|
+
export declare function getCookies(req: Request): Record<string, string>;
|
|
11
|
+
export declare function setCookie(res: Response, name: string, value: string, options?: CookieOptions): Response;
|
|
12
|
+
export declare function deleteCookie(res: Response, name: string, options?: Omit<CookieOptions, 'maxAge'>): Response;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type { Context, Handler, Middleware, ErrorHandler } from './types.ts';
|
|
2
|
+
export { serve } from './serve.ts';
|
|
3
|
+
export type { ServeOptions, Server } from './serve.ts';
|
|
4
|
+
export { Router } from './router.ts';
|
|
5
|
+
export type { WebSocketHandler, GraphQLOptions, AIHandler } from './router.ts';
|
|
6
|
+
export { tsx, TsxContext, useTsx } from './tsx.ts';
|
|
7
|
+
export type { TsxOptions } from './tsx.ts';
|
|
8
|
+
export { auth, cors, logger } from './middleware.ts';
|
|
9
|
+
export type { AuthOptions, CORSOptions, LoggerOptions } from './middleware.ts';
|
|
10
|
+
export { serveStatic } from './static.ts';
|
|
11
|
+
export type { ServeStaticOptions } from './static.ts';
|
|
12
|
+
export { validate } from './validate.ts';
|
|
13
|
+
export type { ValidationSchemas } from './validate.ts';
|
|
14
|
+
export { getCookies, setCookie, deleteCookie } from './cookie.ts';
|
|
15
|
+
export type { CookieOptions } from './cookie.ts';
|
|
16
|
+
export { upload } from './upload.ts';
|
|
17
|
+
export type { UploadOptions, UploadedFile } from './upload.ts';
|
|
18
|
+
export { rateLimit } from './rate-limit.ts';
|
|
19
|
+
export type { RateLimitOptions } from './rate-limit.ts';
|
|
20
|
+
export { compress } from './compress.ts';
|
|
21
|
+
export type { CompressOptions } from './compress.ts';
|