weifuwu 0.27.28 → 0.28.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/index.d.ts +8 -0
- package/index.js +26 -0
- package/package.json +9 -52
- package/README.md +0 -711
- package/dist/ai/provider.d.ts +0 -45
- package/dist/ai/stream.d.ts +0 -13
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -126
- package/dist/core/cookie.d.ts +0 -36
- package/dist/core/env.d.ts +0 -69
- package/dist/core/logger.d.ts +0 -16
- package/dist/core/router.d.ts +0 -88
- package/dist/core/serve.d.ts +0 -38
- package/dist/core/sse.d.ts +0 -47
- package/dist/core/trace.d.ts +0 -95
- package/dist/graphql.d.ts +0 -16
- package/dist/hub.d.ts +0 -36
- package/dist/index.d.ts +0 -51
- package/dist/index.js +0 -3249
- package/dist/middleware/compress.d.ts +0 -20
- package/dist/middleware/cors.d.ts +0 -25
- package/dist/middleware/health.d.ts +0 -24
- package/dist/middleware/helmet.d.ts +0 -33
- package/dist/middleware/rate-limit.d.ts +0 -44
- package/dist/middleware/request-id.d.ts +0 -40
- package/dist/middleware/static.d.ts +0 -23
- package/dist/middleware/upload.d.ts +0 -55
- package/dist/middleware/validate.d.ts +0 -32
- package/dist/postgres/client.d.ts +0 -4
- package/dist/postgres/index.d.ts +0 -3
- package/dist/postgres/module.d.ts +0 -12
- package/dist/postgres/types.d.ts +0 -42
- package/dist/queue/cron.d.ts +0 -9
- package/dist/queue/index.d.ts +0 -2
- package/dist/queue/types.d.ts +0 -61
- package/dist/redis/client.d.ts +0 -2
- package/dist/redis/index.d.ts +0 -2
- package/dist/redis/types.d.ts +0 -17
- package/dist/test/test-utils.d.ts +0 -193
- package/dist/types.d.ts +0 -76
package/README.md
DELETED
|
@@ -1,711 +0,0 @@
|
|
|
1
|
-
# weifuwu
|
|
2
|
-
|
|
3
|
-
**Web-standard HTTP microframework for Node.js** — `(req, ctx) => Response`
|
|
4
|
-
|
|
5
|
-
Pure Node.js, no build step. Native TypeScript via Node.js 24+.
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
npm install weifuwu
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Quick start
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { serve, Router } from 'weifuwu'
|
|
17
|
-
|
|
18
|
-
const app = new Router()
|
|
19
|
-
app.get('/', () => new Response('Hello world!'))
|
|
20
|
-
app.get('/api/ping', () => Response.json({ pong: true }))
|
|
21
|
-
|
|
22
|
-
serve(app.handler(), { port: 3000 })
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Core concepts
|
|
26
|
-
|
|
27
|
-
### Handler
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
type Handler = (req: Request, ctx: Context) => Response | Promise<Response>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Standard `Request` in, standard `Response` out. No framework-specific request/response objects.
|
|
34
|
-
|
|
35
|
-
### Router
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
const app = new Router()
|
|
39
|
-
app.get('/users', handler)
|
|
40
|
-
app.post('/users', handler)
|
|
41
|
-
app.get('/users/:id', handler)
|
|
42
|
-
app.ws('/chat', { message(ws, ctx, data) { ... } })
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Returns a `handler()` function compatible with `serve()`.
|
|
46
|
-
|
|
47
|
-
### Middleware
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
type Middleware = (req: Request, ctx: Context, next: Handler) => Response | Promise<Response>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Middleware enriches `ctx` with additional properties:
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
app.use(postgres()) // → ctx.sql
|
|
57
|
-
app.use(redis()) // → ctx.redis
|
|
58
|
-
app.use(aiProvider()) // → ctx.ai
|
|
59
|
-
app.use(queue()) // → ctx.queue
|
|
60
|
-
app.use(cors())
|
|
61
|
-
app.use(rateLimit({ window: 60 }))
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Full-stack SSR
|
|
67
|
-
|
|
68
|
-
Server-rendered HTML with zero frontend build tools. Uses `html()` tagged templates
|
|
69
|
-
for safe HTML rendering, and **HTMX + Alpine.js + weifuwu-ui** for client-side interactions.
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { Router, serve, html, raw, layout, view, wfuwAssets, wfuwVersion, theme, i18n, flash } from 'weifuwu'
|
|
73
|
-
|
|
74
|
-
const app = new Router()
|
|
75
|
-
|
|
76
|
-
// Middleware
|
|
77
|
-
app.use(theme())
|
|
78
|
-
app.use(i18n({ dir: './locales' }))
|
|
79
|
-
app.use(flash())
|
|
80
|
-
|
|
81
|
-
// HTMX + Alpine.js + weifuwu-ui frontend assets
|
|
82
|
-
app.use('/', wfuwAssets())
|
|
83
|
-
|
|
84
|
-
// Layout (wraps all pages)
|
|
85
|
-
app.use(layout('./ui/app/layout.ts'))
|
|
86
|
-
|
|
87
|
-
// Page
|
|
88
|
-
app.get('/', view('./ui/app/page.ts'))
|
|
89
|
-
|
|
90
|
-
// API
|
|
91
|
-
app.get('/api/ping', () => Response.json({ pong: true }))
|
|
92
|
-
|
|
93
|
-
serve(app.handler(), { port: 3000 })
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### html() — Tagged template HTML
|
|
97
|
-
|
|
98
|
-
Safe HTML rendering with automatic escaping. Zero dependencies — uses JavaScript
|
|
99
|
-
tagged template literals.
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import { html, raw } from 'weifuwu'
|
|
103
|
-
|
|
104
|
-
// Auto-escaped
|
|
105
|
-
html`<h1>${userInput}</h1>`
|
|
106
|
-
// `<h1><script>...</script></h1>`
|
|
107
|
-
|
|
108
|
-
// raw() bypasses escaping (for trusted HTML)
|
|
109
|
-
html`<div>${raw(body)}</div>`
|
|
110
|
-
|
|
111
|
-
// Arrays (from map)
|
|
112
|
-
html`<ul>
|
|
113
|
-
${items.map((i) => html`<li>${i}</li>`)}
|
|
114
|
-
</ul>`
|
|
115
|
-
|
|
116
|
-
// Conditionals
|
|
117
|
-
html`${isAdmin && html`<button>Admin</button>`}`
|
|
118
|
-
|
|
119
|
-
// Nested html() is safe from double-escaping
|
|
120
|
-
html`<div>${html`<span>nested</span>`}</div>`
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### layout() — Layout middleware
|
|
124
|
-
|
|
125
|
-
Wraps page HTML in a layout template. Multiple layouts nest naturally.
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
import { html, raw, wfuwVersion } from 'weifuwu'
|
|
129
|
-
|
|
130
|
-
// ui/app/layout.ts
|
|
131
|
-
export default function (body: string, ctx: any) {
|
|
132
|
-
return html`<!DOCTYPE html>
|
|
133
|
-
<html data-theme="${ctx.theme?.value || 'light'}">
|
|
134
|
-
<head>
|
|
135
|
-
<meta charset="utf-8" />
|
|
136
|
-
<link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css?v=${wfuwVersion}" />
|
|
137
|
-
<script src="/__wfw/js/htmx.min.js?v=${wfuwVersion}"></script>
|
|
138
|
-
<script defer src="/__wfw/js/alpine.min.js?v=${wfuwVersion}"></script>
|
|
139
|
-
<script src="/__wfw/js/weifuwu-ui.js?v=${wfuwVersion}"></script>
|
|
140
|
-
<script id="__wf-i18n" type="application/json">
|
|
141
|
-
${raw(JSON.stringify(ctx.i18n?.messages || {}))}
|
|
142
|
-
</script>
|
|
143
|
-
</head>
|
|
144
|
-
<body data-locale="${ctx.i18n?.locale || 'en'}">
|
|
145
|
-
${raw(body)}
|
|
146
|
-
</body>
|
|
147
|
-
</html>`
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### view() — Page handler factory
|
|
152
|
-
|
|
153
|
-
Loads a `.ts` file and calls its default export to produce an HTML Response.
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
// app.ts
|
|
157
|
-
app.get('/', view('./ui/app/page.ts'))
|
|
158
|
-
|
|
159
|
-
// ui/app/page.ts
|
|
160
|
-
export default function (ctx: any) {
|
|
161
|
-
return html`<h1 class="text-3xl font-bold">${ctx.i18n?.t('title')}</h1>`
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### UI frontend runtime
|
|
166
|
-
|
|
167
|
-
weifuwu ships with **HTMX** (AJAX, SSE, WebSocket, forms) + **Alpine.js** (state, DOM
|
|
168
|
-
binding, UI components) + **weifuwu-ui** (Alpine stores for theme/i18n/flash).
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
import { wfuwAssets } from 'weifuwu'
|
|
172
|
-
app.use(wfuwAssets()) // serve htmx.min.js + alpine.min.js + weifuwu-ui.js
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
In your layout (with cache-busting via `wfuwVersion`):
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
import { wfuwVersion } from 'weifuwu'
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
```html
|
|
182
|
-
<script src="/__wfw/js/htmx.min.js?v=${wfuwVersion}"></script>
|
|
183
|
-
<script defer src="/__wfw/js/alpine.min.js?v=${wfuwVersion}"></script>
|
|
184
|
-
<script src="/__wfw/js/weifuwu-ui.js?v=${wfuwVersion}"></script>
|
|
185
|
-
<link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css?v=${wfuwVersion}" />
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## Public API
|
|
191
|
-
|
|
192
|
-
### serve
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
import { serve } from 'weifuwu'
|
|
196
|
-
|
|
197
|
-
const server = serve(handler, {
|
|
198
|
-
port: 3000, // default: 3000
|
|
199
|
-
websocket: wsHandler, // optional WebSocket handler from Router
|
|
200
|
-
shutdown: true, // graceful shutdown on SIGTERM/SIGINT (default: true)
|
|
201
|
-
maxBody: 1024 * 1024, // max request body size (default: 1MB)
|
|
202
|
-
})
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Returns `{ close(): Promise<void> }`.
|
|
206
|
-
|
|
207
|
-
### Router
|
|
208
|
-
|
|
209
|
-
```ts
|
|
210
|
-
import { Router } from 'weifuwu'
|
|
211
|
-
|
|
212
|
-
const r = new Router()
|
|
213
|
-
|
|
214
|
-
// HTTP methods
|
|
215
|
-
r.get(path, handler)
|
|
216
|
-
r.post(path, handler)
|
|
217
|
-
r.put(path, handler)
|
|
218
|
-
r.patch(path, handler)
|
|
219
|
-
r.delete(path, handler)
|
|
220
|
-
r.head(path, handler)
|
|
221
|
-
r.options(path, handler)
|
|
222
|
-
|
|
223
|
-
// Middleware (applied to all routes)
|
|
224
|
-
r.use(middleware) // global middleware
|
|
225
|
-
r.use('/prefix', middleware) // scoped to prefix
|
|
226
|
-
|
|
227
|
-
// WebSocket
|
|
228
|
-
r.ws(path, {
|
|
229
|
-
open(ws, ctx) { ... },
|
|
230
|
-
message(ws, ctx, data) { ... },
|
|
231
|
-
close(ws, ctx) { ... },
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// Compose
|
|
235
|
-
r.handler() // → (req, ctx) => Response (for serve())
|
|
236
|
-
r.websocketHandler() // → WebSocket upgrade handler
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Context
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
interface Context {
|
|
243
|
-
params: Record<string, string> // URL parameters
|
|
244
|
-
query: Record<string, string> // query string
|
|
245
|
-
mountPath?: string // prefix path if mounted under Router.use()
|
|
246
|
-
[key: string]: unknown // middleware-injected fields
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Middleware modules
|
|
251
|
-
|
|
252
|
-
#### postgres()
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
import { postgres } from 'weifuwu'
|
|
256
|
-
|
|
257
|
-
app.use(postgres({ connection: process.env.DATABASE_URL }))
|
|
258
|
-
// ctx.sql → SqlClient
|
|
259
|
-
|
|
260
|
-
const rows = await ctx.sql`SELECT * FROM users WHERE id = ${id}`
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Includes table builder and migrations:
|
|
264
|
-
|
|
265
|
-
```ts
|
|
266
|
-
const { sql, migrate } = postgres({ connection: '...' })
|
|
267
|
-
await migrate() // run all pending migrations
|
|
268
|
-
await sql.close()
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
Options: `connection`, `max`, `ssl`, `idle_timeout`, `connect_timeout`, `statementTimeout`, `onQuery`, `signal`, `closeTimeout`
|
|
272
|
-
|
|
273
|
-
Types:
|
|
274
|
-
|
|
275
|
-
- `PostgresOptions`, `PostgresClient`, `PostgresInjected`, `SqlClient`, `Sql`
|
|
276
|
-
|
|
277
|
-
#### redis()
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
import { redis } from 'weifuwu'
|
|
281
|
-
|
|
282
|
-
app.use(redis({ url: process.env.REDIS_URL }))
|
|
283
|
-
// ctx.redis → Redis client
|
|
284
|
-
|
|
285
|
-
await ctx.redis.set('key', 'value')
|
|
286
|
-
const val = await ctx.redis.get('key')
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
Options: `url`, `host`, `port`, `password`, `db`, `keyPrefix`, `maxRetriesPerRequest`, `enableReadyCheck`, `lazyConnect`, `retryStrategy`
|
|
290
|
-
|
|
291
|
-
Types: `RedisOptions`, `RedisClient`, `RedisInjected`, `Redis`
|
|
292
|
-
|
|
293
|
-
#### aiProvider()
|
|
294
|
-
|
|
295
|
-
```ts
|
|
296
|
-
import { aiProvider } from 'weifuwu'
|
|
297
|
-
|
|
298
|
-
app.use(aiProvider())
|
|
299
|
-
// ctx.ai → AIProvider
|
|
300
|
-
|
|
301
|
-
app.get('/ask', async (req, ctx) => {
|
|
302
|
-
const result = await ctx.ai.generateText({
|
|
303
|
-
prompt: 'Explain quantum computing',
|
|
304
|
-
})
|
|
305
|
-
return Response.json(result)
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
// Streaming
|
|
309
|
-
app.get('/stream', async (req, ctx) => {
|
|
310
|
-
const result = ctx.ai.streamText({ prompt: 'Tell me a story' })
|
|
311
|
-
return result.toTextStreamResponse()
|
|
312
|
-
})
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Configured via environment variables:
|
|
316
|
-
|
|
317
|
-
- `OPENAI_API_KEY` — API key (default: `ollama`)
|
|
318
|
-
- `OPENAI_BASE_URL` — API base URL (default: `http://localhost:11434/v1`)
|
|
319
|
-
- `OPENAI_MODEL` — model name (default: `gpt-4o`)
|
|
320
|
-
|
|
321
|
-
Types: `AIProviderOptions`, `AIProvider`, `AIProviderInjected`
|
|
322
|
-
|
|
323
|
-
Also exports the raw SDK functions:
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
import { streamText, generateText, embed, embedMany, tool, openai } from 'weifuwu'
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
#### queue()
|
|
330
|
-
|
|
331
|
-
```ts
|
|
332
|
-
import { queue } from 'weifuwu'
|
|
333
|
-
|
|
334
|
-
app.use(queue({ store: 'memory' }))
|
|
335
|
-
// ctx.queue → Queue
|
|
336
|
-
|
|
337
|
-
// In-memory queue (default)
|
|
338
|
-
const q = queue()
|
|
339
|
-
|
|
340
|
-
// Redis-backed queue
|
|
341
|
-
const q = queue({ store: 'redis', redis: ctx.redis })
|
|
342
|
-
|
|
343
|
-
// PostgreSQL-backed queue
|
|
344
|
-
const q = queue({ store: 'pg', pg: { sql: ctx.sql } })
|
|
345
|
-
|
|
346
|
-
q.process('email', async (job) => {
|
|
347
|
-
await sendEmail(job.payload)
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
await q.add('email', { to: 'user@example.com', subject: 'Hello' })
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
Methods: `add(type, payload, opts?)`, `process(type, handler)`, `cron(pattern, handler)`, `run()`, `stats()`, `jobs(limit?)`, `failedJobs(limit?)`, `retryFailed(jobId)`, `retryAllFailed(type?)`, `close()`, `dashboard()`, `migrate()`
|
|
354
|
-
|
|
355
|
-
Types: `QueueOptions`, `Queue`, `QueueJob`, `QueueInjected`
|
|
356
|
-
|
|
357
|
-
#### graphql()
|
|
358
|
-
|
|
359
|
-
```ts
|
|
360
|
-
import { graphql } from 'weifuwu'
|
|
361
|
-
|
|
362
|
-
app.use(
|
|
363
|
-
'/graphql',
|
|
364
|
-
graphql({
|
|
365
|
-
schema: `
|
|
366
|
-
type Query { hello: String }
|
|
367
|
-
`,
|
|
368
|
-
resolvers: { Query: { hello: () => 'world' } },
|
|
369
|
-
}),
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
// With GraphiQL IDE:
|
|
373
|
-
app.use(
|
|
374
|
-
'/graphql',
|
|
375
|
-
graphql({
|
|
376
|
-
schema: `type Query { hello: String }`,
|
|
377
|
-
graphiql: true,
|
|
378
|
-
}),
|
|
379
|
-
)
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
Options: `schema`, `rootValue`, `resolvers`, `context`, `graphiql`, `maxDepth`, `timeout`
|
|
383
|
-
|
|
384
|
-
Types: `GraphQLOptions`, `GraphQLHandler`
|
|
385
|
-
|
|
386
|
-
#### cors()
|
|
387
|
-
|
|
388
|
-
```ts
|
|
389
|
-
import { cors } from 'weifuwu'
|
|
390
|
-
app.use(cors({ origin: 'https://myapp.com' }))
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
Options: `origin`, `methods`, `allowedHeaders`, `exposedHeaders`, `credentials`, `maxAge`
|
|
394
|
-
|
|
395
|
-
#### compress()
|
|
396
|
-
|
|
397
|
-
```ts
|
|
398
|
-
import { compress } from 'weifuwu'
|
|
399
|
-
app.use(compress({ threshold: 1024, brotli: true }))
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
Options: `threshold`, `brotli`, `level`
|
|
403
|
-
|
|
404
|
-
#### helmet()
|
|
405
|
-
|
|
406
|
-
```ts
|
|
407
|
-
import { helmet } from 'weifuwu'
|
|
408
|
-
app.use(helmet())
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
Sets security headers (CSP, HSTS, X-Frame-Options, etc.).
|
|
412
|
-
|
|
413
|
-
#### rateLimit()
|
|
414
|
-
|
|
415
|
-
```ts
|
|
416
|
-
import { rateLimit } from 'weifuwu'
|
|
417
|
-
|
|
418
|
-
// In-memory (default)
|
|
419
|
-
app.use(rateLimit({ window: 60, max: 100 }))
|
|
420
|
-
|
|
421
|
-
// Redis-backed
|
|
422
|
-
app.use(rateLimit({ window: 60, max: 100, redis: ctx.redis }))
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
Options: `window`, `max`, `redis`, `key`, `statusCode`, `message`
|
|
426
|
-
|
|
427
|
-
#### validate()
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
import { validate } from 'weifuwu'
|
|
431
|
-
import { z } from 'zod'
|
|
432
|
-
|
|
433
|
-
app.post(
|
|
434
|
-
'/users',
|
|
435
|
-
validate({
|
|
436
|
-
body: z.object({ name: z.string() }),
|
|
437
|
-
query: z.object({ ref: z.string().optional() }),
|
|
438
|
-
params: z.object({}),
|
|
439
|
-
headers: z.object({ authorization: z.string() }),
|
|
440
|
-
}),
|
|
441
|
-
handler,
|
|
442
|
-
)
|
|
443
|
-
// ctx.parsed → { body, query, params, headers }
|
|
444
|
-
|
|
445
|
-
function handler(req, ctx) {
|
|
446
|
-
const { name } = ctx.parsed.body
|
|
447
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
#### upload()
|
|
451
|
-
|
|
452
|
-
```ts
|
|
453
|
-
import { upload } from 'weifuwu'
|
|
454
|
-
|
|
455
|
-
app.post('/files', upload({ maxFiles: 5, maxSize: 10 * 1024 * 1024 }), handler)
|
|
456
|
-
// ctx.parsed → { files: UploadedFile[], fields: Record<string, string> }
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
Options: `maxFiles`, `maxSize`, `allowedTypes`, `keepExtensions`
|
|
460
|
-
|
|
461
|
-
#### static()
|
|
462
|
-
|
|
463
|
-
```ts
|
|
464
|
-
import { serveStatic } from 'weifuwu'
|
|
465
|
-
app.use('/assets', serveStatic({ root: './public', index: 'index.html' }))
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
Options: `root`, `index`, `maxAge`, `immutable`, `brotli`, `headers`
|
|
469
|
-
|
|
470
|
-
#### csrf()
|
|
471
|
-
|
|
472
|
-
```ts
|
|
473
|
-
import { csrf } from 'weifuwu'
|
|
474
|
-
app.use(csrf())
|
|
475
|
-
// ctx.csrf.token → string (for forms)
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
Protects POST/PUT/DELETE endpoints by requiring a valid CSRF token in `X-CSRF-Token` header.
|
|
479
|
-
|
|
480
|
-
Options: `secret`, `cookie`, `header`
|
|
481
|
-
|
|
482
|
-
#### flash()
|
|
483
|
-
|
|
484
|
-
```ts
|
|
485
|
-
import { flash } from 'weifuwu'
|
|
486
|
-
app.use(flash())
|
|
487
|
-
// ctx.flash.value → string | undefined (read-once)
|
|
488
|
-
// ctx.flash.set('success', 'Saved!')
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
Options: `cookie`, `maxAge`
|
|
492
|
-
|
|
493
|
-
#### requestId()
|
|
494
|
-
|
|
495
|
-
```ts
|
|
496
|
-
import { requestId } from 'weifuwu'
|
|
497
|
-
app.use(requestId())
|
|
498
|
-
// ctx.requestId → string (UUID)
|
|
499
|
-
// Response gets X-Request-Id header
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
Options: `header`, `generator`
|
|
503
|
-
|
|
504
|
-
#### health()
|
|
505
|
-
|
|
506
|
-
```ts
|
|
507
|
-
import { health } from 'weifuwu'
|
|
508
|
-
app.use('/health', health())
|
|
509
|
-
// GET /health → { status: 'ok', uptime: 12345 }
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
#### theme()
|
|
513
|
-
|
|
514
|
-
```ts
|
|
515
|
-
import { theme } from 'weifuwu'
|
|
516
|
-
app.use(theme({ cookie: 'theme' }))
|
|
517
|
-
// ctx.theme → { value: 'light' | 'dark', set(newValue) }
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
Options: `cookie`, `default`, `param`
|
|
521
|
-
|
|
522
|
-
#### i18n()
|
|
523
|
-
|
|
524
|
-
```ts
|
|
525
|
-
import { i18n } from 'weifuwu'
|
|
526
|
-
app.use(i18n({ dir: './locales', defaultLocale: 'en' }))
|
|
527
|
-
// ctx.i18n → { locale: 'en', t(key), set(locale) }
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
Options: `dir`, `defaultLocale`, `cookie`, `param`, `header`
|
|
531
|
-
|
|
532
|
-
### SSR utilities
|
|
533
|
-
|
|
534
|
-
#### html()
|
|
535
|
-
|
|
536
|
-
Tagged template literal for safe HTML. See [Full-stack SSR](#full-stack-ssr).
|
|
537
|
-
|
|
538
|
-
```ts
|
|
539
|
-
import { html, raw } from 'weifuwu'
|
|
540
|
-
|
|
541
|
-
html`<h1>${title}</h1>` // auto-escaped
|
|
542
|
-
html`<div>${raw(html)}</div>` // unescaped
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
#### layout()
|
|
546
|
-
|
|
547
|
-
Middleware that wraps page content in a layout template.
|
|
548
|
-
|
|
549
|
-
```ts
|
|
550
|
-
import { layout } from 'weifuwu'
|
|
551
|
-
app.use(layout('./ui/app/layout.ts'))
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
#### view()
|
|
555
|
-
|
|
556
|
-
Handler factory that loads a `.ts` file as a page.
|
|
557
|
-
|
|
558
|
-
```ts
|
|
559
|
-
import { view } from 'weifuwu'
|
|
560
|
-
app.get('/', view('./ui/app/page.ts'))
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
#### wfuwAssets()
|
|
564
|
-
|
|
565
|
-
Serve HTMX, Alpine.js, and weifuwu-ui (Alpine stores for theme/i18n/flash).
|
|
566
|
-
|
|
567
|
-
```ts
|
|
568
|
-
import { wfuwAssets } from 'weifuwu'
|
|
569
|
-
app.use(wfuwAssets())
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
```ts
|
|
573
|
-
import { wfuwVersion } from 'weifuwu'
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
```html
|
|
577
|
-
<script src="/__wfw/js/htmx.min.js?v=${wfuwVersion}"></script>
|
|
578
|
-
<script defer src="/__wfw/js/alpine.min.js?v=${wfuwVersion}"></script>
|
|
579
|
-
<script src="/__wfw/js/weifuwu-ui.js?v=${wfuwVersion}"></script>
|
|
580
|
-
<link rel="stylesheet" href="/__wfw/css/weifuwu-ui.css?v=${wfuwVersion}" />
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
### Standalone utilities
|
|
584
|
-
|
|
585
|
-
#### SSE
|
|
586
|
-
|
|
587
|
-
```ts
|
|
588
|
-
import { createSSEStream, formatSSE, formatSSEData } from 'weifuwu'
|
|
589
|
-
|
|
590
|
-
const stream = createSSEStream()
|
|
591
|
-
stream.write(formatSSE('eventType', { data: 'hello' }))
|
|
592
|
-
stream.end()
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
#### Hub (pub/sub)
|
|
596
|
-
|
|
597
|
-
```ts
|
|
598
|
-
import { createHub } from 'weifuwu'
|
|
599
|
-
|
|
600
|
-
const hub = createHub({ redis: optionalRedisClient })
|
|
601
|
-
hub.join('room:1', ws)
|
|
602
|
-
hub.sendRoom('room:1', { type: 'message', text: 'hello' })
|
|
603
|
-
hub.leave(ws)
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
#### Cookie helpers
|
|
607
|
-
|
|
608
|
-
```ts
|
|
609
|
-
import { getCookies, setCookie, deleteCookie } from 'weifuwu'
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
### Test utilities
|
|
613
|
-
|
|
614
|
-
```ts
|
|
615
|
-
import { testApp, TestApp, createTestDb, withTestDb } from 'weifuwu'
|
|
616
|
-
|
|
617
|
-
const app = new Router().handler()
|
|
618
|
-
const res = await testApp(app, new Request('http://localhost/'))
|
|
619
|
-
// res.status, res.headers, await res.json()
|
|
620
|
-
|
|
621
|
-
// With database
|
|
622
|
-
const db = await createTestDb()
|
|
623
|
-
// db.sql, db.close()
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
### Environment
|
|
627
|
-
|
|
628
|
-
```ts
|
|
629
|
-
import { loadEnv, isDev, isProd, env } from 'weifuwu'
|
|
630
|
-
|
|
631
|
-
loadEnv() // loads .env file
|
|
632
|
-
isDev() // NODE_ENV === 'development'
|
|
633
|
-
isProd() // NODE_ENV === 'production'
|
|
634
|
-
env('MY_VAR', 'default')
|
|
635
|
-
getPublicEnv() // env vars starting with PUBLIC_
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Tracing
|
|
639
|
-
|
|
640
|
-
```ts
|
|
641
|
-
import { trace, currentTraceId } from 'weifuwu'
|
|
642
|
-
|
|
643
|
-
trace('fetch-user', async () => {
|
|
644
|
-
// auto-tracked with trace ID
|
|
645
|
-
})
|
|
646
|
-
currentTraceId() // get current trace ID
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
### Error handling
|
|
650
|
-
|
|
651
|
-
```ts
|
|
652
|
-
import { HttpError } from 'weifuwu'
|
|
653
|
-
|
|
654
|
-
throw new HttpError('Not found', 404) // caught by serve(), returns 404
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
## CLI
|
|
660
|
-
|
|
661
|
-
```bash
|
|
662
|
-
npx weifuwu init my-app # Full-stack project (SSR + weifuwu-ui)
|
|
663
|
-
npx weifuwu init my-app --minimal # Minimal API-only project
|
|
664
|
-
npx weifuwu version # Print version
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
### Full-stack template (`init`)
|
|
668
|
-
|
|
669
|
-
Generates a complete project with SSR via `html()` tagged templates, weifuwu-ui frontend
|
|
670
|
-
runtime (zero external deps, ~5KB), theme switching, i18n, and flash messages.
|
|
671
|
-
|
|
672
|
-
```
|
|
673
|
-
my-app/
|
|
674
|
-
index.ts — server entry
|
|
675
|
-
app.ts — Router setup
|
|
676
|
-
ui/
|
|
677
|
-
app/
|
|
678
|
-
globals.css — custom styles
|
|
679
|
-
layout.ts — root layout (weifuwu-ui + theme/i18n/flash)
|
|
680
|
-
page.ts — home page (wu-data/wu-theme/wu-lang demo)
|
|
681
|
-
locales/
|
|
682
|
-
en.json
|
|
683
|
-
zh-CN.json
|
|
684
|
-
package.json
|
|
685
|
-
tsconfig.json — with @/ path alias
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
### Minimal template (`init --minimal`)
|
|
689
|
-
|
|
690
|
-
Creates a minimal API project with `app.ts`, `index.ts`, and TypeScript config.
|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
|
-
## Dependencies
|
|
695
|
-
|
|
696
|
-
### Backend
|
|
697
|
-
|
|
698
|
-
- `postgres` — PostgreSQL client
|
|
699
|
-
- `ioredis` — Redis client
|
|
700
|
-
- `ai`, `@ai-sdk/openai` — AI SDK
|
|
701
|
-
- `graphql`, `@graphql-tools/schema` — GraphQL
|
|
702
|
-
- `ws` — WebSocket
|
|
703
|
-
- `zod` — Schema validation
|
|
704
|
-
|
|
705
|
-
### Frontend
|
|
706
|
-
|
|
707
|
-
- **HTMX** (~14KB) — AJAX loading, SSE, WebSocket, form submission
|
|
708
|
-
- **Alpine.js** (~15KB) — state management, DOM binding, UI components
|
|
709
|
-
- **weifuwu-ui** (~2KB) — Alpine stores for theme/i18n/flash/toast
|
|
710
|
-
|
|
711
|
-
Zero build tools. Zero frontend framework compilation.
|