weifuwu 0.23.4 → 0.24.1
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 +431 -152
- package/cli.ts +3 -0
- package/dist/agent/types.d.ts +2 -1
- package/dist/ai/provider.d.ts +10 -1
- package/dist/analytics.d.ts +2 -2
- package/dist/cache.d.ts +4 -4
- package/dist/cli.js +3 -0
- package/dist/cors.d.ts +2 -2
- package/dist/csrf.d.ts +11 -5
- package/dist/deploy/types.d.ts +2 -2
- package/dist/env.d.ts +33 -0
- package/dist/flash.d.ts +5 -0
- package/dist/helmet.d.ts +2 -2
- package/dist/hub.d.ts +2 -1
- package/dist/i18n.d.ts +27 -2
- package/dist/iii/register-worker.d.ts +1 -1
- package/dist/iii/types.d.ts +5 -3
- package/dist/index.d.ts +8 -10
- package/dist/index.js +434 -359
- package/dist/kb/types.d.ts +8 -0
- package/dist/logdb/types.d.ts +2 -1
- package/dist/mailer.d.ts +2 -1
- package/dist/messager/types.d.ts +2 -1
- package/dist/opencode/types.d.ts +2 -1
- package/dist/permissions.d.ts +2 -2
- package/dist/postgres/module.d.ts +2 -1
- package/dist/postgres/types.d.ts +2 -2
- package/dist/queue/types.d.ts +2 -2
- package/dist/rate-limit.d.ts +3 -2
- package/dist/react.js +6 -6
- package/dist/redis/types.d.ts +2 -2
- package/dist/request-id.d.ts +16 -7
- package/dist/router.d.ts +3 -0
- package/dist/seo.d.ts +2 -2
- package/dist/serve.d.ts +1 -1
- package/dist/session.d.ts +9 -5
- package/dist/tailwind.d.ts +9 -0
- package/dist/tenant/types.d.ts +3 -3
- package/dist/theme.d.ts +25 -2
- package/dist/trace.d.ts +44 -0
- package/dist/types.d.ts +8 -17
- package/dist/upload.d.ts +9 -2
- package/dist/user/client.d.ts +11 -3
- package/dist/user/types.d.ts +21 -6
- package/dist/validate.d.ts +5 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -27,6 +27,53 @@ npx weifuwu init my-app && cd my-app && npm run dev
|
|
|
27
27
|
|
|
28
28
|
## CLI
|
|
29
29
|
|
|
30
|
+
### Typical Full App
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { serve, Router, postgres, session, user, aiProvider, ssr, flash, i18n, theme, logger, rateLimit } from 'weifuwu'
|
|
34
|
+
|
|
35
|
+
const app = new Router()
|
|
36
|
+
|
|
37
|
+
// 1. Observability (order matters — run early)
|
|
38
|
+
app.use(logger())
|
|
39
|
+
|
|
40
|
+
// 2. UX middleware — single-line auto-registers middleware + routes
|
|
41
|
+
app.use(theme())
|
|
42
|
+
app.use(i18n({ default: 'zh', dir: './locales' }))
|
|
43
|
+
app.use(flash())
|
|
44
|
+
|
|
45
|
+
// 3. Database
|
|
46
|
+
const pg = postgres()
|
|
47
|
+
app.use(pg)
|
|
48
|
+
|
|
49
|
+
// 4. Session & Auth
|
|
50
|
+
app.use(session({ store: 'redis', redis: myRedis }))
|
|
51
|
+
const auth = user({ pg, jwtSecret: process.env.JWT_SECRET })
|
|
52
|
+
await auth.migrate()
|
|
53
|
+
app.use(auth) // auto-registers middleware + /register, /login
|
|
54
|
+
app.use('/auth', auth) // explicit path mounts for more control
|
|
55
|
+
|
|
56
|
+
// 5. API protection
|
|
57
|
+
app.use('/api', rateLimit({ max: 60, window: 60_000 }))
|
|
58
|
+
|
|
59
|
+
// 6. AI
|
|
60
|
+
app.use(aiProvider()) // ctx.ai
|
|
61
|
+
|
|
62
|
+
// 7. SSR
|
|
63
|
+
app.use('/', ssr({ dir: './ui' }))
|
|
64
|
+
|
|
65
|
+
// 8. REST API
|
|
66
|
+
app.get('/api/ping', () => Response.json({ ok: true }))
|
|
67
|
+
app.post('/api/chat', async (req, ctx) => {
|
|
68
|
+
const { prompt } = await req.json()
|
|
69
|
+
const result = await ctx.ai.generateText({ prompt })
|
|
70
|
+
return Response.json(result)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// 9. Start
|
|
74
|
+
const server = serve(app.handler(), { port: 3000 })
|
|
75
|
+
```
|
|
76
|
+
|
|
30
77
|
```bash
|
|
31
78
|
npx weifuwu init my-app # Full project (SSR + i18n + theme + WS demo)
|
|
32
79
|
npx weifuwu init my-api --minimal # Minimal HTTP project (2 files)
|
|
@@ -149,14 +196,13 @@ The `ctx` object accumulates properties as it passes through the middleware chai
|
|
|
149
196
|
| `query` | Router | `Record<string, string>` | URL query parameters |
|
|
150
197
|
| `mountPath` | Router | `string` | Current sub-router mount prefix |
|
|
151
198
|
| `env` | `loadEnv()` | `Record<string, string>` | Public env vars (`WEIFUWU_PUBLIC_*`) |
|
|
152
|
-
| `
|
|
199
|
+
| `csrf.token` | `csrf()` | `string` | CSRF token (namespace) |
|
|
153
200
|
| `requestId` | `requestId()` | `string` | Request ID |
|
|
154
201
|
| `session` | `session()` | `Session` | Session data object |
|
|
155
202
|
| `sql` | `postgres()` | `Sql<{}>` | PostgreSQL tagged-template client |
|
|
156
203
|
| `redis` | `redis()` | `Redis` | Redis client |
|
|
157
204
|
| `ai` | `aiProvider()` | `AIProvider` | AI model & embedding |
|
|
158
205
|
| `queue` | `queue()` | `Queue` | Job queue |
|
|
159
|
-
| `session` | `session()` | `Session` | Session data object |
|
|
160
206
|
| `user` | `auth()` / `user().middleware()` | `{ id?: string }` | Authenticated user |
|
|
161
207
|
| `permissions` | `permissions()` | `{ roles, permissions }` | RBAC roles & permissions sets |
|
|
162
208
|
| `theme` | `theme()` | `{ value, set }` | Current theme + switcher |
|
|
@@ -176,12 +222,12 @@ Middleware-injected properties are **automatically typed** through chained `use(
|
|
|
176
222
|
|
|
177
223
|
```ts
|
|
178
224
|
const app = new Router()
|
|
179
|
-
.use(csrf()) // → Router<Context & {
|
|
180
|
-
.use(requestId()) // → Router<Context & {
|
|
181
|
-
.use(postgres()) // → Router<Context & {
|
|
225
|
+
.use(csrf()) // → Router<Context & { csrf: { token: string } }>
|
|
226
|
+
.use(requestId()) // → Router<Context & { csrf: ..., requestId }>
|
|
227
|
+
.use(postgres()) // → Router<Context & { csrf: ..., requestId, sql }>
|
|
182
228
|
|
|
183
229
|
app.get('/me', (_req, ctx) => {
|
|
184
|
-
ctx.
|
|
230
|
+
ctx.csrf.token // ✅ string (IDE autocomplete)
|
|
185
231
|
ctx.requestId // ✅ string
|
|
186
232
|
ctx.sql`SELECT 1` // ✅ Sql<{}>
|
|
187
233
|
})
|
|
@@ -193,12 +239,14 @@ Each module exports an `XxxInjected` type (e.g. `PostgresInjected`, `UserInjecte
|
|
|
193
239
|
|
|
194
240
|
## Module Patterns
|
|
195
241
|
|
|
196
|
-
All modules follow one of **
|
|
242
|
+
All modules follow one of **4 patterns** — learn these and you know every module.
|
|
197
243
|
|
|
198
244
|
| Pattern | How to mount | Example |
|
|
199
245
|
|---------|-------------|---------|
|
|
200
246
|
| `[α]` | `app.use(mod())` | `compress()`, `theme()`, `postgres()` |
|
|
201
247
|
| `[β]` | `app.use('/path', mod())` | `health()`, `ssr({dir})`, `graphql(handler)`, `user()` |
|
|
248
|
+
| `[γ]` | Import and call directly | `mailer()`, `fts`, `cron-utils` |
|
|
249
|
+
| `[δ]` | `import { useXxx } from 'weifuwu/react'` | `useTheme()`, `useLocale()`, `useWebsocket()` |
|
|
202
250
|
|
|
203
251
|
### Pattern α — Middleware
|
|
204
252
|
|
|
@@ -206,7 +254,7 @@ All modules follow one of **2 patterns** — learn these and you know every modu
|
|
|
206
254
|
app.use(compress()) // basic
|
|
207
255
|
const pg = postgres() // with extras: .sql, .table, .migrate(), .close()
|
|
208
256
|
app.use(pg)
|
|
209
|
-
app.use(rateLimit({ max: 100 })) // with .
|
|
257
|
+
app.use(rateLimit({ max: 100 })) // with .close()
|
|
210
258
|
```
|
|
211
259
|
|
|
212
260
|
### Pattern β — Router
|
|
@@ -219,15 +267,136 @@ app.use('/auth', user({ pg, jwtSecret })) // with .middlew
|
|
|
219
267
|
app.ws('/ws', messager({ pg }).wsHandler())
|
|
220
268
|
```
|
|
221
269
|
|
|
222
|
-
β modules that need **separate middleware** use `.middleware()
|
|
270
|
+
β modules that need **separate middleware** use `.middleware()`. Most can auto-register both middleware and routes in one call:
|
|
223
271
|
```ts
|
|
272
|
+
app.use(theme()) // auto: middleware + /__theme/:value
|
|
273
|
+
app.use(i18n({ dir: './locales' })) // auto: middleware + /__lang/:locale
|
|
274
|
+
app.use(analytics({ pg })) // auto: middleware + /__analytics
|
|
275
|
+
app.use(auth) // auto: middleware + /register, /login (user())
|
|
276
|
+
|
|
277
|
+
// Explicit form when more control is needed:
|
|
224
278
|
const a = analytics()
|
|
225
|
-
app.use(a.middleware()) // tracking
|
|
226
|
-
app.use('/', a) // dashboard
|
|
279
|
+
app.use(a.middleware()) // tracking only
|
|
280
|
+
app.use('/', a) // dashboard at custom path
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Pattern γ — Standalone
|
|
284
|
+
|
|
285
|
+
Modules that don't intercept requests or serve routes. Import and use directly.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { mailer, cronNext, fts } from 'weifuwu'
|
|
289
|
+
|
|
290
|
+
const email = mailer({ transport: 'smtp://...', from: 'noreply@example.com' })
|
|
291
|
+
await email.send({ to: 'user@test.com', subject: 'Hello', text: 'Body' })
|
|
292
|
+
|
|
293
|
+
const next = cronNext('0 9 * * 1-5') // next weekday at 09:00
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Pattern δ — Client-side
|
|
297
|
+
|
|
298
|
+
React hooks that self-register via `addInterceptor()`. Import to enable.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { useTheme, useLocale, useWebsocket } from 'weifuwu/react'
|
|
302
|
+
|
|
303
|
+
function ThemeToggle() {
|
|
304
|
+
const { theme, setTheme } = useTheme()
|
|
305
|
+
return <button onClick={() => setTheme('dark')}>Dark</button>
|
|
306
|
+
}
|
|
227
307
|
```
|
|
228
308
|
|
|
229
309
|
---
|
|
230
310
|
|
|
311
|
+
## Module Dependency Map
|
|
312
|
+
|
|
313
|
+
```mermaid
|
|
314
|
+
graph TD
|
|
315
|
+
serve --> Router
|
|
316
|
+
Router --> postgres
|
|
317
|
+
Router --> redis
|
|
318
|
+
Router --> aiProvider
|
|
319
|
+
|
|
320
|
+
subgraph "DB-Dependent Modules"
|
|
321
|
+
user --> postgres
|
|
322
|
+
session --> postgres
|
|
323
|
+
session -.-> redis
|
|
324
|
+
queue --> postgres
|
|
325
|
+
queue -.-> redis
|
|
326
|
+
permissions --> postgres
|
|
327
|
+
analytics --> postgres
|
|
328
|
+
logdb --> postgres
|
|
329
|
+
tenant --> postgres
|
|
330
|
+
messager --> postgres
|
|
331
|
+
messager -.-> redis
|
|
332
|
+
agent --> postgres
|
|
333
|
+
kb --> postgres
|
|
334
|
+
iii --> postgres
|
|
335
|
+
iii -.-> redis
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
subgraph "AI-Dependent Modules"
|
|
339
|
+
agent --> aiProvider
|
|
340
|
+
kb --> aiProvider
|
|
341
|
+
aiStream --> aiProvider
|
|
342
|
+
opencode --> aiProvider
|
|
343
|
+
runWorkflow --> aiProvider
|
|
344
|
+
end
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Quick Module Selection
|
|
348
|
+
|
|
349
|
+
| What do you want to do? | Module | Pattern |
|
|
350
|
+
|------------------------|--------|---------|
|
|
351
|
+
| **User registration / login** | `user()` | β |
|
|
352
|
+
| **Simple token/header auth** | `auth()` | α |
|
|
353
|
+
| **JWT verification** | `user().middleware()` | α |
|
|
354
|
+
| **Role-based access control** | `permissions()` | α |
|
|
355
|
+
| **AI chat / generate / stream** | `ctx.ai.generateText()` / `ctx.ai.streamText()` | α (via `aiProvider()`) |
|
|
356
|
+
| **AI agent with knowledge** | `agent()` + `knowledgeBase()` | β |
|
|
357
|
+
| **Send email** | `mailer()` | γ |
|
|
358
|
+
| **File upload** | `upload()` | α |
|
|
359
|
+
| **Object storage (S3/MinIO)** | `s3()` | α |
|
|
360
|
+
| **Rate limiting** | `rateLimit()` | α |
|
|
361
|
+
| **Response caching** | `cache()` | α |
|
|
362
|
+
| **Periodic / delayed jobs** | `queue()` | α |
|
|
363
|
+
| **Page view analytics** | `analytics()` | β |
|
|
364
|
+
| **Structured logging** | `logdb()` | β |
|
|
365
|
+
| **Real-time chat / messager** | `messager()` | β |
|
|
366
|
+
| **Full-text search** | `fts` | γ |
|
|
367
|
+
| **Theme switching** | `theme()` | α |
|
|
368
|
+
| **i18n / localization** | `i18n()` | α |
|
|
369
|
+
| **Flash messages** | `flash()` | α |
|
|
370
|
+
| **Server-Sent Events** | `createSSEStream()` | γ |
|
|
371
|
+
| **GraphQL endpoint** | `graphql()` | β |
|
|
372
|
+
| **Webhook receiver** | `webhook()` | β |
|
|
373
|
+
| **SSR with React** | `ssr()` | β |
|
|
374
|
+
| **Health check** | `health()` | β |
|
|
375
|
+
| **SEO (robots.txt, sitemap)** | `seo()` | β |
|
|
376
|
+
| **Multi-process deploy** | `deploy()` | γ |
|
|
377
|
+
| **Distributed functions (iii)** | `iii()` | β |
|
|
378
|
+
| **Multi-tenant BaaS** | `tenant()` | β |
|
|
379
|
+
| **Client-side routing** | `useNavigate()`, `<Link>` | δ |
|
|
380
|
+
| **WebSocket in React** | `useWebsocket()` | δ |
|
|
381
|
+
| **Compression (brotli/gzip)** | `compress()` | α |
|
|
382
|
+
| **Security headers (CSP, HSTS)** | `helmet()` | α |
|
|
383
|
+
| **CORS** | `cors()` | α |
|
|
384
|
+
| **CSRF protection** | `csrf()` | α |
|
|
385
|
+
| **Request ID tracing** | `requestId()` | α |
|
|
386
|
+
| **Environment variables** | `env()` / `loadEnv()` | α |
|
|
387
|
+
| **Static file serving** | `serveStatic()` | α |
|
|
388
|
+
| **Object storage (S3/MinIO)** | `s3()` | α |
|
|
389
|
+
| **Send email** | `mailer()` | γ |
|
|
390
|
+
| **Scheduled / cron tasks** | `cron-utils` (`cronNext()`) | γ |
|
|
391
|
+
| **Server-Sent Events** | `createSSEStream()` | γ |
|
|
392
|
+
| **Multi-process deploy** | `deploy()` | γ |
|
|
393
|
+
| **Distributed functions (iii)** | `iii()` | β |
|
|
394
|
+
| **Webhook receiver** | `webhook()` | β |
|
|
395
|
+
| **Social login (OAuth)** | `user({ oauthLogin })` | β |
|
|
396
|
+
| **Database migrations** | `pg.migrate()` | — |
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
231
400
|
## Request Tracing & Logging
|
|
232
401
|
|
|
233
402
|
Every request gets a **trace ID** via `AsyncLocalStorage`, injected into responses as `X-Trace-Id`. W3C `traceparent` headers are forwarded.
|
|
@@ -347,7 +516,13 @@ Uses `TEST_DATABASE_URL` or `DATABASE_URL`. Automatically skipped in CI if unset
|
|
|
347
516
|
|
|
348
517
|
## Module Reference
|
|
349
518
|
|
|
350
|
-
|
|
519
|
+
Modules are organized alphabetically. Each module shows its pattern badge (`[α]` Middleware, `[β]` Router, `[γ]` Standalone, `[δ]` Client-side) and category.
|
|
520
|
+
|
|
521
|
+
**Category key:** AI, API, Clientδ, Database, DevTools, Networking, Security, SSR, UX
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
### agent [β] [AI]
|
|
351
526
|
|
|
352
527
|
```ts
|
|
353
528
|
const provider = aiProvider()
|
|
@@ -374,7 +549,7 @@ a.run(agentId, { input: 'summarize the data', stream: true })
|
|
|
374
549
|
| `.migrate()` | DB setup |
|
|
375
550
|
| `.close()` | Cleanup |
|
|
376
551
|
|
|
377
|
-
### aiStream [β]
|
|
552
|
+
### aiStream [β] [AI]
|
|
378
553
|
|
|
379
554
|
Creates an AI streaming chat endpoint using the Vercel AI SDK.
|
|
380
555
|
|
|
@@ -389,7 +564,7 @@ app.use('/chat', chat)
|
|
|
389
564
|
| `handler` | `(req, ctx) => AIStreamOptions \| Promise<AIStreamOptions>` | Returns AI SDK options (model, messages, schema, etc.) |
|
|
390
565
|
| `provider` | `AIProvider` | Optional. If provided and handler omits `model`, `provider.model()` is used as default |
|
|
391
566
|
|
|
392
|
-
### analytics [β]
|
|
567
|
+
### analytics [β] [API]
|
|
393
568
|
|
|
394
569
|
In-memory or PostgreSQL page view tracking with built-in dashboard.
|
|
395
570
|
|
|
@@ -412,7 +587,7 @@ app.use(a.middleware())
|
|
|
412
587
|
app.use('/', a) // dashboard routes
|
|
413
588
|
```
|
|
414
589
|
|
|
415
|
-
### auth [α]
|
|
590
|
+
### auth [α] [Security]
|
|
416
591
|
|
|
417
592
|
```ts
|
|
418
593
|
app.use(auth({ token: 'sk-123' })) // static token
|
|
@@ -445,7 +620,7 @@ Authorization header. This lets logged-in users authenticate via their
|
|
|
445
620
|
session cookie without sending a token. Falls back to header/token auth
|
|
446
621
|
if no session userId is present.
|
|
447
622
|
|
|
448
|
-
### compress [α]
|
|
623
|
+
### compress [α] [DevTools]
|
|
449
624
|
|
|
450
625
|
```ts
|
|
451
626
|
app.use(compress()) // brotli > gzip > deflate (min 1KB)
|
|
@@ -457,7 +632,7 @@ app.use(compress({ threshold: 2048, level: 4 })) // custom threshold and le
|
|
|
457
632
|
| `threshold` | `number` | `1024` | Minimum byte size to compress |
|
|
458
633
|
| `level` | `number` | `6` | Compression level (zlib) |
|
|
459
634
|
|
|
460
|
-
### cors [α]
|
|
635
|
+
### cors [α] [DevTools]
|
|
461
636
|
|
|
462
637
|
```ts
|
|
463
638
|
app.use(cors()) // allow all
|
|
@@ -475,7 +650,7 @@ app.use(cors({ credentials: true, maxAge: 3600 }))
|
|
|
475
650
|
| `credentials` | `boolean` | `false` | Allow cookies/credentials |
|
|
476
651
|
| `maxAge` | `number` | — | Preflight cache duration (seconds) |
|
|
477
652
|
|
|
478
|
-
### flash [α]
|
|
653
|
+
### flash [α] [UX]
|
|
479
654
|
|
|
480
655
|
Cookie-based flash message. Read from request, write via redirect.
|
|
481
656
|
|
|
@@ -495,7 +670,7 @@ app.post('/save', (req, ctx) => {
|
|
|
495
670
|
|--------|------|---------|-------------|
|
|
496
671
|
| `name` | `string` | `'flash'` | Cookie name |
|
|
497
672
|
|
|
498
|
-
### cache [α]
|
|
673
|
+
### cache [α] [DevTools]
|
|
499
674
|
|
|
500
675
|
Response caching middleware with memory and Redis stores. Caches GET/HEAD responses, with tag-based invalidation.
|
|
501
676
|
|
|
@@ -535,11 +710,11 @@ await mem.set('key', { status: 200, statusText: 'OK', headers: {}, body: '...',
|
|
|
535
710
|
mem.close()
|
|
536
711
|
```
|
|
537
712
|
|
|
538
|
-
### csrf [α]
|
|
713
|
+
### csrf [α] [Security]
|
|
539
714
|
|
|
540
715
|
```ts
|
|
541
716
|
app.use(csrf())
|
|
542
|
-
// ctx.
|
|
717
|
+
// ctx.csrf.token — set on GET/HEAD/OPTIONS
|
|
543
718
|
// Auto-validates x-csrf-token or x-xsrf-token header on POST/PUT/DELETE/PATCH
|
|
544
719
|
// Falls back to body field matching the key name
|
|
545
720
|
```
|
|
@@ -551,7 +726,7 @@ app.use(csrf())
|
|
|
551
726
|
| `key` | `'_csrf'` | Body field fallback |
|
|
552
727
|
| `excludeMethods` | `['GET','HEAD','OPTIONS']` | Skip validation |
|
|
553
728
|
|
|
554
|
-
### deploy [β]
|
|
729
|
+
### deploy [β] [Networking]
|
|
555
730
|
|
|
556
731
|
Multi-process manager with reverse proxy, health checks, auto-restart, and zero-downtime updates. Works identically locally and in production.
|
|
557
732
|
|
|
@@ -657,9 +832,42 @@ Restart=always
|
|
|
657
832
|
| `buildCommand` | — | Build command |
|
|
658
833
|
| `ports` | — | `[port, port+1]` for blue-green |
|
|
659
834
|
|
|
835
|
+
### env [α] [DevTools]
|
|
660
836
|
|
|
837
|
+
Environment variable middleware. Injects `ctx.env` with all `WEIFUWU_PUBLIC_*` variables (prefix stripped).
|
|
838
|
+
Safe to expose to the client.
|
|
661
839
|
|
|
662
|
-
|
|
840
|
+
```ts
|
|
841
|
+
import { env, loadEnv } from 'weifuwu'
|
|
842
|
+
loadEnv() // Load .env into process.env
|
|
843
|
+
app.use(env()) // → ctx.env
|
|
844
|
+
|
|
845
|
+
app.get('/config', (req, ctx) => {
|
|
846
|
+
return Response.json({ apiUrl: ctx.env.API_URL })
|
|
847
|
+
})
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
Helper utilities:
|
|
851
|
+
|
|
852
|
+
```ts
|
|
853
|
+
import { isDev, isProd, isBundled, getPublicEnv } from 'weifuwu'
|
|
854
|
+
|
|
855
|
+
isDev() // NODE_ENV === 'development'
|
|
856
|
+
isProd() // NODE_ENV === 'production'
|
|
857
|
+
isBundled() // Running from compiled dist/index.js?
|
|
858
|
+
getPublicEnv() // { API_URL: '...' } — no middleware needed
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
| Function | Description |
|
|
862
|
+
|----------|-------------|
|
|
863
|
+
| `loadEnv(path?)` | Load `.env` file into `process.env` (does not override existing) |
|
|
864
|
+
| `env()` | Middleware — injects `ctx.env` with public vars |
|
|
865
|
+
| `getPublicEnv()` | Returns `WEIFUWU_PUBLIC_*` vars with prefix stripped |
|
|
866
|
+
| `isDev()` | `true` when `NODE_ENV === 'development'` |
|
|
867
|
+
| `isProd()` | `true` when `NODE_ENV === 'production'` |
|
|
868
|
+
| `isBundled()` | `true` when running from compiled bundle |
|
|
869
|
+
|
|
870
|
+
### graphql [β] [API]
|
|
663
871
|
|
|
664
872
|
```ts
|
|
665
873
|
const handler: GraphQLHandler = () => ({
|
|
@@ -682,7 +890,7 @@ app.use('/graphql', graphql(handler))
|
|
|
682
890
|
| `maxDepth` | `number` | `10` | Max query nesting depth |
|
|
683
891
|
| `timeout` | `number` | `30_000` | Execution timeout (ms) |
|
|
684
892
|
|
|
685
|
-
### health [β]
|
|
893
|
+
### health [β] [API]
|
|
686
894
|
|
|
687
895
|
```ts
|
|
688
896
|
app.use('/health', health())
|
|
@@ -694,7 +902,7 @@ app.use('/health', health())
|
|
|
694
902
|
| `path` | `string` | `'/health'` | Health check endpoint |
|
|
695
903
|
| `check` | `() => Promise<void>` | — | Async function; throws → 503 |
|
|
696
904
|
|
|
697
|
-
### helmet [α]
|
|
905
|
+
### helmet [α] [Security]
|
|
698
906
|
|
|
699
907
|
15 security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, etc.
|
|
700
908
|
|
|
@@ -715,7 +923,7 @@ app.use(helmet({ contentSecurityPolicy: "default-src 'self'", xFrameOptions: 'DE
|
|
|
715
923
|
| `crossOriginOpenerPolicy` | — | COOP header |
|
|
716
924
|
| `crossOriginResourcePolicy` | — | CORP header |
|
|
717
925
|
|
|
718
|
-
### iii [β] — Worker / Function / Trigger
|
|
926
|
+
### iii [β] — Worker / Function / Trigger [API]
|
|
719
927
|
|
|
720
928
|
Distributed function execution with WebSocket workers, triggers, and Redis streams.
|
|
721
929
|
|
|
@@ -751,7 +959,7 @@ await engine.trigger({ function_id: 'orders::create', payload: { items: ['apple'
|
|
|
751
959
|
|
|
752
960
|
|
|
753
961
|
|
|
754
|
-
### knowledgeBase [β] — RAG with pgvector
|
|
962
|
+
### knowledgeBase [β] — RAG with pgvector [AI]
|
|
755
963
|
|
|
756
964
|
```ts
|
|
757
965
|
import { knowledgeBase, aiProvider } from 'weifuwu'
|
|
@@ -805,7 +1013,7 @@ replaces old chunks. Provider's `embed()` is used automatically.
|
|
|
805
1013
|
The HNSW index enables fast approximate nearest-neighbor search (cosine distance).
|
|
806
1014
|
|
|
807
1015
|
|
|
808
|
-
### logdb [β]
|
|
1016
|
+
### logdb [β] [API]
|
|
809
1017
|
|
|
810
1018
|
PostgreSQL structured event logging with monthly partitioning.
|
|
811
1019
|
|
|
@@ -828,7 +1036,7 @@ await logger.log({ level: 'info', source: 'app', message: 'hello', metadata: { u
|
|
|
828
1036
|
| GET | `/` | Query (`?level=`, `?source=`, `?after=`, `?before=`, `?meta.*=`) |
|
|
829
1037
|
| GET | `/:id` | Get single entry |
|
|
830
1038
|
|
|
831
|
-
### logger [α]
|
|
1039
|
+
### logger [α] [DevTools]
|
|
832
1040
|
|
|
833
1041
|
```ts
|
|
834
1042
|
app.use(logger()) // GET /hello 200 5ms
|
|
@@ -837,9 +1045,9 @@ app.use(logger({ format: 'combined' })) // with query params
|
|
|
837
1045
|
|
|
838
1046
|
| Option | Type | Default | Description |
|
|
839
1047
|
|--------|------|---------|-------------|
|
|
840
|
-
| `format` | `'short' \| 'combined'` | `'short'` | Log format: path only,
|
|
1048
|
+
| `format` | `'short' \| 'combined' \| 'json'` | `'short'` | Log format: path only, path + query params, or JSON to stderr |
|
|
841
1049
|
|
|
842
|
-
### mailer
|
|
1050
|
+
### mailer [γ] [Networking]
|
|
843
1051
|
|
|
844
1052
|
```ts
|
|
845
1053
|
const mail = mailer({ from: 'noreply@example.com', transport: 'smtp://user:pass@smtp.example.com:587' })
|
|
@@ -854,28 +1062,31 @@ await mail.send({ to: 'user@test.com', subject: 'Hello', text: 'Body', html: '<p
|
|
|
854
1062
|
|
|
855
1063
|
|
|
856
1064
|
|
|
857
|
-
###
|
|
1065
|
+
### oauthLogin (via user()) — Social login (OAuth 2.0 client) [Security]
|
|
858
1066
|
|
|
859
|
-
|
|
860
|
-
import { oauthClient } from 'weifuwu'
|
|
1067
|
+
Social login is built into the [`user()`](#user-β) module via the `oauthLogin` option — no separate import needed.
|
|
861
1068
|
|
|
1069
|
+
```ts
|
|
862
1070
|
app.use(session()) // required — stores OAuth state
|
|
863
|
-
|
|
864
|
-
app.use('/auth', oauthClient({ // mounts /auth/google, /auth/google/callback
|
|
1071
|
+
const u = user({
|
|
865
1072
|
pg,
|
|
866
|
-
jwtSecret
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1073
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
1074
|
+
oauthLogin: {
|
|
1075
|
+
redirectUrl: '/dashboard',
|
|
1076
|
+
providers: {
|
|
1077
|
+
google: {
|
|
1078
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
1079
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
1080
|
+
},
|
|
1081
|
+
github: {
|
|
1082
|
+
clientId: process.env.GITHUB_CLIENT_ID,
|
|
1083
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
1084
|
+
},
|
|
876
1085
|
},
|
|
877
1086
|
},
|
|
878
|
-
})
|
|
1087
|
+
})
|
|
1088
|
+
await u.migrate()
|
|
1089
|
+
app.use(u) // POST /register, POST /login, GET /auth/:provider, GET /auth/:provider/callback
|
|
879
1090
|
```
|
|
880
1091
|
|
|
881
1092
|
**Flow:** User clicks "Login with Google" → redirected to Google → back to app → user created/linked in database → JWT signed → session created → redirected to `redirectUrl` with `?token=` (or JSON response for API clients).
|
|
@@ -883,42 +1094,40 @@ app.use('/auth', oauthClient({ // mounts /auth/google, /auth/google/ca
|
|
|
883
1094
|
Supports custom providers via `authUrl`, `tokenUrl`, `userUrl`, and `parseUser`:
|
|
884
1095
|
|
|
885
1096
|
```ts
|
|
886
|
-
|
|
1097
|
+
const u = user({
|
|
887
1098
|
pg,
|
|
888
|
-
jwtSecret
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1099
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
1100
|
+
oauthLogin: {
|
|
1101
|
+
providers: {
|
|
1102
|
+
discord: {
|
|
1103
|
+
clientId: process.env.DISCORD_CLIENT_ID,
|
|
1104
|
+
clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
|
1105
|
+
authUrl: 'https://discord.com/api/oauth2/authorize',
|
|
1106
|
+
tokenUrl: 'https://discord.com/api/oauth2/token',
|
|
1107
|
+
userUrl: 'https://discord.com/api/users/@me',
|
|
1108
|
+
parseUser: (data) => ({
|
|
1109
|
+
id: data.id,
|
|
1110
|
+
email: data.email ?? '',
|
|
1111
|
+
name: data.global_name ?? data.username,
|
|
1112
|
+
avatarUrl: data.avatar
|
|
1113
|
+
? `https://cdn.discordapp.com/avatars/${data.id}/${data.avatar}.png`
|
|
1114
|
+
: '',
|
|
1115
|
+
}),
|
|
1116
|
+
},
|
|
904
1117
|
},
|
|
905
1118
|
},
|
|
906
|
-
})
|
|
1119
|
+
})
|
|
907
1120
|
```
|
|
908
1121
|
|
|
909
|
-
| Option | Type | Default | Description |
|
|
1122
|
+
| Option (oauthLogin) | Type | Default | Description |
|
|
910
1123
|
|--------|------|---------|-------------|
|
|
911
|
-
| `pg` | `PostgresClient` | — | **Required.** Database connection |
|
|
912
|
-
| `jwtSecret` | `string` | — | **Required.** Must match `user()` module's secret |
|
|
913
1124
|
| `providers` | `Record<string, OAuthProviderConfig>` | — | **Required.** Provider configs (Google/GitHub built-in, any custom) |
|
|
914
1125
|
| `redirectUrl` | `string` | `'/'` | Post-login redirect destination |
|
|
915
|
-
| `expiresIn` | `string \| number` | `'24h'` | JWT expiry |
|
|
916
|
-
| `table` | `string` | `'_auth_providers'` | Provider-user link table name |
|
|
917
1126
|
|
|
918
|
-
|
|
1127
|
+
Built-in providers (Google, GitHub) have preset URLs — you only need to provide `clientId` and `clientSecret`. The module auto-creates a `_auth_providers` table on first request.
|
|
919
1128
|
|
|
920
1129
|
|
|
921
|
-
### messager [β]
|
|
1130
|
+
### messager [β] [Networking]
|
|
922
1131
|
|
|
923
1132
|
Real-time chat with channels, WebSocket, agent routing.
|
|
924
1133
|
|
|
@@ -945,7 +1154,7 @@ await msg.send(channelId, 'System message', { sender_type: 'system', sender_id:
|
|
|
945
1154
|
|
|
946
1155
|
|
|
947
1156
|
|
|
948
|
-
### opencode [β]
|
|
1157
|
+
### opencode [β] [AI]
|
|
949
1158
|
|
|
950
1159
|
AI programming assistant.
|
|
951
1160
|
|
|
@@ -972,7 +1181,7 @@ app.ws('/opencode', oc.wsHandler())
|
|
|
972
1181
|
| `skills` | `object[]` | — | Custom skill definitions |
|
|
973
1182
|
| `permissions` | `object` | — | Tool permission rules |
|
|
974
1183
|
|
|
975
|
-
### postgres [α]
|
|
1184
|
+
### postgres [α] [Database]
|
|
976
1185
|
|
|
977
1186
|
Type-safe PostgreSQL client with schema builder, CRUD, migrations, soft delete, and JSONB/vector support.
|
|
978
1187
|
|
|
@@ -1112,7 +1321,7 @@ class MyModule extends PgModule {
|
|
|
1112
1321
|
|
|
1113
1322
|
Where helpers + `and`/`or`/`not` can be imported from `'weifuwu'` alongside `postgres`. Full column builders and table helpers are in the same barrel.
|
|
1114
1323
|
|
|
1115
|
-
### cron-utils
|
|
1324
|
+
### cron-utils [γ] [DevTools]
|
|
1116
1325
|
|
|
1117
1326
|
Shared cron expression parsing utilities. All functions operate in **local timezone**.
|
|
1118
1327
|
|
|
@@ -1130,7 +1339,7 @@ console.log(new Date(next))
|
|
|
1130
1339
|
| `matches(fields, date)` | Check if a date matches a parsed pattern |
|
|
1131
1340
|
| `cronNext(expr, from?)` | Calculate next matching timestamp (`from` defaults to now) |
|
|
1132
1341
|
|
|
1133
|
-
### fts — Full-Text Search (PostgreSQL)
|
|
1342
|
+
### fts — Full-Text Search (PostgreSQL) [Database]
|
|
1134
1343
|
|
|
1135
1344
|
Utilities for PostgreSQL full-text search: create GIN indexes, search with ranking, and generate highlighted snippets.
|
|
1136
1345
|
|
|
@@ -1166,16 +1375,20 @@ await fts.dropIndex(pg.sql, articles)
|
|
|
1166
1375
|
|
|
1167
1376
|
Search options: `fields`, `limit` (20), `offset` (0), `headline` (false), `language` ('english'), `minRank`.
|
|
1168
1377
|
|
|
1169
|
-
### theme [α]
|
|
1378
|
+
### theme [α] [UX]
|
|
1170
1379
|
|
|
1171
1380
|
```ts
|
|
1381
|
+
// Single line — auto-registers middleware + /__theme/:value route
|
|
1172
1382
|
app.use(theme({ default: 'dark' }))
|
|
1173
|
-
// → ctx.theme = { value: 'dark', set: fn }
|
|
1174
|
-
// → ctx.theme.value — 'dark'
|
|
1175
|
-
// → ctx.theme.set('light', '/settings') — 302 + Set-Cookie
|
|
1176
1383
|
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1384
|
+
// ctx.theme = { value: 'dark', set: fn }
|
|
1385
|
+
// ctx.theme.value — 'dark'
|
|
1386
|
+
// ctx.theme.set('light', '/settings') — 302 + Set-Cookie
|
|
1387
|
+
|
|
1388
|
+
// Explicit form for more control:
|
|
1389
|
+
// const t = theme()
|
|
1390
|
+
// app.use(t.middleware())
|
|
1391
|
+
// app.use('/', t)
|
|
1179
1392
|
```
|
|
1180
1393
|
|
|
1181
1394
|
| Option | Type | Default | Description |
|
|
@@ -1193,15 +1406,21 @@ app.post('/settings', async (req, ctx) => {
|
|
|
1193
1406
|
|
|
1194
1407
|
See [`useTheme()`](#usetheme) for client-side usage.
|
|
1195
1408
|
|
|
1196
|
-
### i18n [α]
|
|
1409
|
+
### i18n [α] [UX]
|
|
1197
1410
|
|
|
1198
1411
|
```ts
|
|
1412
|
+
// Single line — auto-registers middleware + /__lang/:locale route
|
|
1199
1413
|
app.use(i18n({ default: 'zh', dir: './locales' }))
|
|
1200
|
-
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1414
|
+
|
|
1415
|
+
// ctx.i18n = { locale: 'zh', t, set }
|
|
1416
|
+
// ctx.i18n.t('welcome') → '欢迎'
|
|
1417
|
+
// ctx.i18n.locale → 'zh'
|
|
1418
|
+
// ctx.i18n.set('en', '/settings') — 302 + Set-Cookie
|
|
1419
|
+
|
|
1420
|
+
// Explicit form for more control:
|
|
1421
|
+
// const l = i18n()
|
|
1422
|
+
// app.use(l.middleware())
|
|
1423
|
+
// app.use('/', l)
|
|
1205
1424
|
```
|
|
1206
1425
|
|
|
1207
1426
|
| Option | Type | Default | Description |
|
|
@@ -1222,7 +1441,7 @@ app.get('/greet', async (req, ctx) => {
|
|
|
1222
1441
|
|
|
1223
1442
|
**Client-side:** import `useLocale` from `weifuwu/react`, `useTheme` from `weifuwu/react`.
|
|
1224
1443
|
|
|
1225
|
-
### queue [α]
|
|
1444
|
+
### queue [α] [Database]
|
|
1226
1445
|
|
|
1227
1446
|
Async job queue. Supports immediate, delayed, and recurring (cron) tasks with three backends:
|
|
1228
1447
|
|
|
@@ -1300,7 +1519,7 @@ Supported cron syntax: `*` (any), `*/n` (every n), `n-m` (range), `n,m,o` (list)
|
|
|
1300
1519
|
| POST | `/:type/retry` | Retry all failed jobs of a type |
|
|
1301
1520
|
| POST | `/retry/:id` | Retry a specific failed job by ID |
|
|
1302
1521
|
|
|
1303
|
-
### rateLimit [α]
|
|
1522
|
+
### rateLimit [α] [Security]
|
|
1304
1523
|
|
|
1305
1524
|
```ts
|
|
1306
1525
|
app.use(rateLimit({ max: 100, window: 60_000 })) // 100 req/min, in-memory
|
|
@@ -1326,7 +1545,7 @@ app.use(rateLimit({ max: 100, store: 'redis', redis: ctx.redis }))
|
|
|
1326
1545
|
|
|
1327
1546
|
Redis mode uses `INCR` + `EXPIRE` for atomic counting, enabling accurate rate limiting across multiple server processes. Memory mode is ideal for single-process deployments.
|
|
1328
1547
|
|
|
1329
|
-
### redis [α]
|
|
1548
|
+
### redis [α] [Database]
|
|
1330
1549
|
|
|
1331
1550
|
```ts
|
|
1332
1551
|
const r = redis() // reads REDIS_URL
|
|
@@ -1340,7 +1559,7 @@ await ctx.redis.set('key', 'value')
|
|
|
1340
1559
|
| `url` | `string` | `REDIS_URL` env | Redis connection string |
|
|
1341
1560
|
| (all ioredis options) | — | — | Passed directly to ioredis |
|
|
1342
1561
|
|
|
1343
|
-
### requestId [α]
|
|
1562
|
+
### requestId [α] [DevTools]
|
|
1344
1563
|
|
|
1345
1564
|
```ts
|
|
1346
1565
|
app.use(requestId())
|
|
@@ -1353,16 +1572,35 @@ app.use(requestId({ header: 'X-Request-Id', generator: () => crypto.randomUUID()
|
|
|
1353
1572
|
| `header` | `string` | `'X-Request-ID'` | Header name to read/write |
|
|
1354
1573
|
| `generator` | `() => string` | `crypto.randomUUID()` | ID generator |
|
|
1355
1574
|
|
|
1356
|
-
### trace
|
|
1575
|
+
### trace [α] [DevTools]
|
|
1576
|
+
|
|
1577
|
+
Request-scoped tracing via `AsyncLocalStorage`. Use as middleware to inject `ctx.trace`:
|
|
1578
|
+
|
|
1579
|
+
```ts
|
|
1580
|
+
import { trace } from 'weifuwu'
|
|
1581
|
+
app.use(trace()) // → ctx.trace
|
|
1582
|
+
app.use(trace({ header: 'X-Trace-Id' })) // custom header
|
|
1583
|
+
|
|
1584
|
+
app.get('/', (req, ctx) => {
|
|
1585
|
+
console.log(ctx.trace.requestId) // 550e8400-e29b-...
|
|
1586
|
+
console.log(ctx.trace.traceId) // trace UUID
|
|
1587
|
+
console.log(ctx.trace.elapsed()) // ms since request start
|
|
1588
|
+
})
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
| Option | Type | Default | Description |
|
|
1592
|
+
|--------|------|---------|-------------|
|
|
1593
|
+
| `header` | `string` | `'X-Request-ID'` | Request ID header name |
|
|
1594
|
+
| `generator` | `() => string` | `crypto.randomUUID()` | Custom ID generator |
|
|
1357
1595
|
|
|
1358
|
-
|
|
1596
|
+
Utility functions (also available standalone):
|
|
1359
1597
|
|
|
1360
1598
|
```ts
|
|
1361
|
-
import { currentTraceId, runWithTrace, traceElapsed } from 'weifuwu'
|
|
1599
|
+
import { currentTraceId, runWithTrace, traceElapsed, currentTrace } from 'weifuwu'
|
|
1362
1600
|
|
|
1363
|
-
// Inside a middleware or handler
|
|
1364
1601
|
const traceId = currentTraceId() // UUID or incoming X-Trace-Id
|
|
1365
1602
|
const elapsed = traceElapsed() // ms since request started
|
|
1603
|
+
runWithTrace(incomingId, () => { ... }) // manual scope
|
|
1366
1604
|
```
|
|
1367
1605
|
|
|
1368
1606
|
| Function | Description |
|
|
@@ -1372,7 +1610,7 @@ const elapsed = traceElapsed() // ms since request started
|
|
|
1372
1610
|
| `traceElapsed()` | Milliseconds elapsed since the trace started |
|
|
1373
1611
|
| `runWithTrace(traceId, fn)` | Execute `fn` inside a trace scope |
|
|
1374
1612
|
|
|
1375
|
-
### s3 [α] — S3-compatible object storage
|
|
1613
|
+
### s3 [α] — S3-compatible object storage [Networking]
|
|
1376
1614
|
|
|
1377
1615
|
```ts
|
|
1378
1616
|
import { s3 } from 'weifuwu'
|
|
@@ -1451,7 +1689,7 @@ minio:
|
|
|
1451
1689
|
```
|
|
1452
1690
|
|
|
1453
1691
|
|
|
1454
|
-
### seo [β] + seoMiddleware [α]
|
|
1692
|
+
### seo [β] + seoMiddleware [α] [API]
|
|
1455
1693
|
|
|
1456
1694
|
```ts
|
|
1457
1695
|
app.use('/', seo({ baseUrl: 'https://example.com', robots: [{ userAgent: '*', allow: '/' }], sitemap: { urls: [{ loc: '/' }] } }))
|
|
@@ -1469,7 +1707,7 @@ Also exports `seoTags(config)` for generating meta/og/twitter tags as an HTML st
|
|
|
1469
1707
|
| `sitemap` | `SitemapConfig` | — | Sitemap configuration (urls, resolve, cacheTTL) |
|
|
1470
1708
|
| `headers` | `SeoHeadersConfig` | — | Response headers (e.g. `X-Robots-Tag`) |
|
|
1471
1709
|
|
|
1472
|
-
### session [α]
|
|
1710
|
+
### session [α] [Security]
|
|
1473
1711
|
|
|
1474
1712
|
Cookie-based server-side session management with memory and Redis stores.
|
|
1475
1713
|
|
|
@@ -1531,7 +1769,7 @@ const redis = new RedisStore(redisClient, 'myapp:session:')
|
|
|
1531
1769
|
await redis.destroy('sid')
|
|
1532
1770
|
```
|
|
1533
1771
|
|
|
1534
|
-
### ssr({ dir }) [β]
|
|
1772
|
+
### ssr({ dir }) [β] [SSR]
|
|
1535
1773
|
|
|
1536
1774
|
One-stop Server-Side Rendering. Accepts a directory and returns a Router that handles all SSR routes, tailwind CSS, hydration, and livereload — using Next.js-style file conventions.
|
|
1537
1775
|
|
|
@@ -1580,7 +1818,7 @@ app.use('/', ssr({ dir: './ui' }))
|
|
|
1580
1818
|
- The vendor bundle (react + react-dom + weifuwu client libs) is compiled once and cached
|
|
1581
1819
|
- Page components are pre-compiled to `/__ssr/{hash}.js` — no runtime esbuild after first request
|
|
1582
1820
|
- **Dev:** `createRoot` + render; **Production:** `hydrateRoot` (reuses SSR DOM)
|
|
1583
|
-
-
|
|
1821
|
+
- The hydration script and page component share the same SSR context store — data flows seamlessly from server to client
|
|
1584
1822
|
- Tailwind CSS served at `/__wfw/style/{hash}.css` (cached, content-hashed)
|
|
1585
1823
|
- Dev mode extras: HMR WebSocket, file watcher, hot component replacement
|
|
1586
1824
|
|
|
@@ -1595,7 +1833,7 @@ app.get('/api/ping', () => Response.json({ pong: true }))
|
|
|
1595
1833
|
|
|
1596
1834
|
Layout components receive `{ children }` and wrap from outer to inner:
|
|
1597
1835
|
|
|
1598
|
-
### tenant [β]
|
|
1836
|
+
### tenant [β] [Networking]
|
|
1599
1837
|
|
|
1600
1838
|
Multi-tenant BaaS with dynamic table API and GraphQL.
|
|
1601
1839
|
|
|
@@ -1612,7 +1850,7 @@ app.use('/graphql', t.graphql()) // dynamic GraphQL
|
|
|
1612
1850
|
| `pg` | `object` | — | PostgreSQL client |
|
|
1613
1851
|
| `usersTable` | `string` | — | Users table name for tenant membership lookup |
|
|
1614
1852
|
|
|
1615
|
-
### upload [α]
|
|
1853
|
+
### upload [α] [DevTools]
|
|
1616
1854
|
|
|
1617
1855
|
```ts
|
|
1618
1856
|
app.post('/upload', upload({ dir: './uploads', maxFileSize: 10_485_760, allowedTypes: ['image/jpeg', 'image/png'] }), (req, ctx) => {
|
|
@@ -1628,7 +1866,7 @@ app.post('/upload', upload({ dir: './uploads', maxFileSize: 10_485_760, allowedT
|
|
|
1628
1866
|
| `maxFileSize` | `number` | — | Max bytes per file |
|
|
1629
1867
|
| `allowedTypes` | `string[]` | — | Allowed MIME types |
|
|
1630
1868
|
|
|
1631
|
-
### user [β]
|
|
1869
|
+
### user [β] [Security]
|
|
1632
1870
|
|
|
1633
1871
|
Authentication: register, login, JWT, OAuth2 服务端, 社会化登录.
|
|
1634
1872
|
|
|
@@ -1665,7 +1903,7 @@ app.use(u.middleware()) // ctx.user
|
|
|
1665
1903
|
| `.verify(token)` | Verify JWT token |
|
|
1666
1904
|
| `.middleware()` | JWT verify middleware — sets `ctx.user` |
|
|
1667
1905
|
|
|
1668
|
-
### permissions [α] — RBAC
|
|
1906
|
+
### permissions [α] — RBAC [Security]
|
|
1669
1907
|
|
|
1670
1908
|
Role-based access control.
|
|
1671
1909
|
|
|
@@ -1713,7 +1951,7 @@ app.get('/posts/:id', async (req, ctx) => {
|
|
|
1713
1951
|
| `.requirePermission(...perms)` | Middleware — rejects if user lacks any permission |
|
|
1714
1952
|
| `.migrate()` | Create tables |
|
|
1715
1953
|
|
|
1716
|
-
### validate [α]
|
|
1954
|
+
### validate [α] [DevTools]
|
|
1717
1955
|
|
|
1718
1956
|
```ts
|
|
1719
1957
|
import { z } from 'zod'
|
|
@@ -1747,7 +1985,7 @@ app.post('/contact', validate({ body: z.object({ email: z.string().email() }) })
|
|
|
1747
1985
|
| `params` | `ZodSchema` | — | URL params validation schema |
|
|
1748
1986
|
| `headers` | `ZodSchema` | — | Header validation schema |
|
|
1749
1987
|
|
|
1750
|
-
### webhook [β]
|
|
1988
|
+
### webhook [β] [API]
|
|
1751
1989
|
|
|
1752
1990
|
Webhook receiver with built-in signature verification for Stripe, GitHub, and Slack. Event-based dispatch with replay protection.
|
|
1753
1991
|
|
|
@@ -1786,21 +2024,22 @@ wh.on('*', (event) => {
|
|
|
1786
2024
|
|
|
1787
2025
|
Built-in verifiers handle HMAC-SHA256, timestamp validation (Slack's 5-min window), and Stripe's `t=` / `v1=` signature format. Slack URL verification challenges are auto-responded.
|
|
1788
2026
|
|
|
1789
|
-
### Client-side navigation
|
|
2027
|
+
### Client-side navigation [δ] [Client]
|
|
1790
2028
|
|
|
1791
2029
|
```tsx
|
|
1792
|
-
import { Link, useNavigate, useNavigating } from 'weifuwu/react'
|
|
2030
|
+
import { Link, navigate, useNavigate, useNavigating } from 'weifuwu/react'
|
|
1793
2031
|
|
|
1794
2032
|
<Link href="/about" prefetch>About</Link> // client-side nav + prefetch on hover/visible
|
|
1795
|
-
const
|
|
2033
|
+
const n = useNavigate() // hook: n('/contact')
|
|
2034
|
+
navigate('/contact') // bare function (no hook needed)
|
|
1796
2035
|
const loading = useNavigating() // reactive loading state
|
|
1797
2036
|
```
|
|
1798
2037
|
|
|
1799
|
-
`navigate()` fetches SSR, extracts
|
|
2038
|
+
`navigate()` fetches the SSR page, extracts the root container content, and replaces it in-place. Middleware runs on server each nav — data is always fresh.
|
|
1800
2039
|
|
|
1801
2040
|
**Preference URLs** (`/__lang/`, `/__theme/`) are intercepted by modular interceptors registered via `addInterceptor()` — no page reload needed. Importing `useLocale` or `useTheme` registers the interceptor automatically.
|
|
1802
2041
|
|
|
1803
|
-
### Client-side hooks
|
|
2042
|
+
### Client-side hooks [δ] [Client]
|
|
1804
2043
|
|
|
1805
2044
|
```tsx
|
|
1806
2045
|
import { useWebsocket, useAction, useFetch, useQueryState, createStore, Head } from 'weifuwu/react'
|
|
@@ -1840,7 +2079,7 @@ const count = useStore(s => s.count)
|
|
|
1840
2079
|
|
|
1841
2080
|
**`TsxContext`** — React context holding page data (`params`, `query`, `user`, `parsed`, `theme`, `i18n`, `flash`, `loaderData`, `env`). Used internally by hooks; rarely needed directly.
|
|
1842
2081
|
|
|
1843
|
-
### Locale & Theme
|
|
2082
|
+
### Locale & Theme [δ] [Client]
|
|
1844
2083
|
|
|
1845
2084
|
```tsx
|
|
1846
2085
|
import { useLocale } from 'weifuwu/react'
|
|
@@ -1891,7 +2130,7 @@ function Page() {
|
|
|
1891
2130
|
}
|
|
1892
2131
|
```
|
|
1893
2132
|
|
|
1894
|
-
On the server, data flows from middleware → `ctx` → `setCtx(ctxValue)` (serialized via JSON). On the client, the hydration script calls `setCtx(ctxData)` which populates the shared store
|
|
2133
|
+
On the server, data flows from middleware → `ctx` → `setCtx(ctxValue)` (serialized via JSON). On the client, the hydration script calls `setCtx(ctxData)` which populates the shared context store. `useLoaderData()` reads from the snapshot via `useSyncExternalStore` — no SSR-specific code needed in your components.
|
|
1895
2134
|
|
|
1896
2135
|
**`addInterceptor(fn)`** — Register a URL interceptor. Interceptors run before SPA navigation; if one returns `true`, `navigate()` skips the fetch-and-swap.
|
|
1897
2136
|
|
|
@@ -1904,7 +2143,7 @@ addInterceptor(async (url) => {
|
|
|
1904
2143
|
})
|
|
1905
2144
|
```
|
|
1906
2145
|
|
|
1907
|
-
### Flash messages
|
|
2146
|
+
### Flash messages [δ] [Client]
|
|
1908
2147
|
|
|
1909
2148
|
```ts
|
|
1910
2149
|
import { flash } from 'weifuwu'
|
|
@@ -1939,13 +2178,13 @@ function Toast() {
|
|
|
1939
2178
|
|--------|------|---------|-------------|
|
|
1940
2179
|
| `name` | `string` | `'flash'` | Cookie name |
|
|
1941
2180
|
|
|
1942
|
-
### Dev mode
|
|
2181
|
+
### Dev mode [δ] [Client]
|
|
1943
2182
|
|
|
1944
2183
|
Auto-detected when `NODE_ENV === 'development'`. `ssr({dir})` automatically registers importmap, vendor bundle, HMR WebSocket, and file watcher. No explicit setup needed.
|
|
1945
2184
|
|
|
1946
2185
|
- Inline hydration script uses `createRoot` + render (replaces SSR DOM)
|
|
1947
2186
|
- Vendor bundle served at `/__wfw/v/bundle?h=<hash>` — compiled from source, unminified
|
|
1948
|
-
- Hot component replacement: file changes → WebSocket message → browser imports hot bundle →
|
|
2187
|
+
- Hot component replacement: file changes → WebSocket message → browser imports hot bundle → component refreshed in place — `useState` values preserved
|
|
1949
2188
|
- Tailwind CSS hot-reloads without page refresh
|
|
1950
2189
|
- Layout changes trigger a full page reload
|
|
1951
2190
|
|
|
@@ -1962,7 +2201,7 @@ const provider = aiProvider()
|
|
|
1962
2201
|
|
|
1963
2202
|
For AI streaming endpoints see [`aiStream`](#aistream-β). For AI agent APIs see [`agent`](#agent-β).
|
|
1964
2203
|
|
|
1965
|
-
### aiProvider [α] — AI model & embedding configuration
|
|
2204
|
+
### aiProvider [α] — AI model & embedding configuration [AI]
|
|
1966
2205
|
|
|
1967
2206
|
```ts
|
|
1968
2207
|
const provider = aiProvider() // auto from env
|
|
@@ -1995,7 +2234,7 @@ app.post('/ask', async (req, ctx) => {
|
|
|
1995
2234
|
| `.streamText(params)` | Stream text (model auto-injected) |
|
|
1996
2235
|
| `.dimension` | Configured embedding dimension |
|
|
1997
2236
|
|
|
1998
|
-
### DAG Workflow
|
|
2237
|
+
### DAG Workflow [AI]
|
|
1999
2238
|
|
|
2000
2239
|
```ts
|
|
2001
2240
|
const tools = { queryUser: tool({ ... }) }
|
|
@@ -2035,16 +2274,24 @@ Every public symbol can be imported from `'weifuwu'`:
|
|
|
2035
2274
|
```ts
|
|
2036
2275
|
serve, createTestServer, Router, ssr,
|
|
2037
2276
|
Context, Handler, Middleware, ErrorHandler, ServeOptions, Server,
|
|
2038
|
-
loadEnv,
|
|
2039
|
-
currentTraceId, currentTrace, runWithTrace, traceElapsed, TraceContext,
|
|
2277
|
+
loadEnv, env, isDev, isProd, isBundled, getPublicEnv,
|
|
2278
|
+
currentTraceId, currentTrace, runWithTrace, traceElapsed, trace, TraceContext,
|
|
2279
|
+
testApp, TestApp, TestRequest, TestResponse,
|
|
2280
|
+
createTestDb, withTestDb,
|
|
2281
|
+
getCookies, setCookie, deleteCookie,
|
|
2282
|
+
createSSEStream, formatSSE, formatSSEData, SSEEvent,
|
|
2283
|
+
DEFAULT_MAX_BODY, MIGRATIONS_TABLE,
|
|
2040
2284
|
```
|
|
2041
2285
|
|
|
2042
|
-
### Middleware
|
|
2286
|
+
### Middleware / DevTools
|
|
2043
2287
|
|
|
2044
2288
|
```ts
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2289
|
+
logger, cors, compress, helmet,
|
|
2290
|
+
rateLimit, requestId, validate, upload,
|
|
2291
|
+
csrf, session, MemoryStore, RedisStore, SessionStore,
|
|
2292
|
+
cache, MemoryCache, RedisCache, CacheStore,
|
|
2293
|
+
flash, permissions,
|
|
2294
|
+
serveStatic, s3,
|
|
2048
2295
|
```
|
|
2049
2296
|
|
|
2050
2297
|
### Database
|
|
@@ -2054,55 +2301,87 @@ postgres, PostgresOptions, PostgresClient,
|
|
|
2054
2301
|
redis, RedisOptions, RedisClient,
|
|
2055
2302
|
queue, QueueOptions, QueueJob, Queue,
|
|
2056
2303
|
PostgresInjected, RedisInjected, QueueInjected,
|
|
2057
|
-
// Schema helpers
|
|
2304
|
+
// Schema helpers:
|
|
2058
2305
|
pgTable, SQL, sql,
|
|
2059
2306
|
ColumnBuilder, serial, uuid, text, integer, boolean, boolean_, timestamptz, jsonb, textArray, vector,
|
|
2060
2307
|
partitionBy, timestamps, toDDL, PartitionByDef,
|
|
2061
2308
|
Table, BoundTable, IndexOptions, FindOptions, CreateOptions,
|
|
2062
|
-
eq, ne, gt, gte, lt, lte, isNull, isNotNull, like, contains, in_, and, or, not
|
|
2309
|
+
eq, ne, gt, gte, lt, lte, isNull, isNotNull, like, contains, in_, and, or, not,
|
|
2310
|
+
fts,
|
|
2063
2311
|
```
|
|
2064
2312
|
|
|
2065
|
-
###
|
|
2313
|
+
### Security / Auth
|
|
2066
2314
|
|
|
2067
2315
|
```ts
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2316
|
+
auth,
|
|
2317
|
+
user, UserModule, UserData, UserOptions, UserInjected, OAuthProviderConfig, OAuth2Client,
|
|
2318
|
+
permissions, PermissionsModule, PermissionsOptions,
|
|
2319
|
+
csrf, CsrfOptions, CsrfInjected,
|
|
2320
|
+
helmet, HelmetOptions,
|
|
2321
|
+
session, SessionStore, SessionOptions, SessionData, SessionInjected,
|
|
2322
|
+
rateLimit, RateLimitOptions,
|
|
2074
2323
|
```
|
|
2075
|
-
export type { UseAgentStreamOptions, UseAgentStreamReturn, AgentStreamState } from 'weifuwu/react'
|
|
2076
2324
|
|
|
2077
|
-
###
|
|
2325
|
+
### UX Middleware
|
|
2078
2326
|
|
|
2079
2327
|
```ts
|
|
2080
|
-
|
|
2328
|
+
theme, ThemeOptions, ThemeInjected,
|
|
2329
|
+
i18n, I18nOptions, I18nInjected,
|
|
2330
|
+
flash, FlashOptions, FlashInjected,
|
|
2081
2331
|
```
|
|
2082
2332
|
|
|
2083
|
-
### AI
|
|
2333
|
+
### AI
|
|
2084
2334
|
|
|
2085
2335
|
```ts
|
|
2336
|
+
aiProvider, AIProvider, AIProviderOptions, AIProviderInjected,
|
|
2086
2337
|
streamText, generateText, streamObject, generateObject,
|
|
2087
2338
|
tool, embed, embedMany, smoothStream,
|
|
2088
|
-
openai, createOpenAI
|
|
2339
|
+
openai, createOpenAI,
|
|
2340
|
+
aiStream, AIHandler,
|
|
2341
|
+
runWorkflow,
|
|
2342
|
+
agent, AgentModule, AgentOptions,
|
|
2343
|
+
knowledgeBase, KBModule, KBOptions,
|
|
2344
|
+
opencode, OpencodeModule, OpencodeOptions,
|
|
2089
2345
|
```
|
|
2090
2346
|
|
|
2091
|
-
###
|
|
2347
|
+
### API / Routing
|
|
2092
2348
|
|
|
2093
2349
|
```ts
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2350
|
+
analytics, AnalyticsModule, AnalyticsOptions,
|
|
2351
|
+
health, HealthOptions,
|
|
2352
|
+
graphql, GraphQLOptions, GraphQLHandler,
|
|
2353
|
+
logdb, LogdbModule, LogdbOptions,
|
|
2354
|
+
seo, seoMiddleware, seoTags, SeoOptions,
|
|
2355
|
+
webhook, WebhookModule, WebhookOptions,
|
|
2356
|
+
iii, createWorker, registerWorker, IIIModule, IIIOptions,
|
|
2357
|
+
```
|
|
2358
|
+
|
|
2359
|
+
### Networking / Storage
|
|
2360
|
+
|
|
2361
|
+
```ts
|
|
2362
|
+
s3, S3Options, S3Module, S3Body,
|
|
2363
|
+
mailer, MailerOptions, Mailer,
|
|
2364
|
+
messager, MessagerModule, MessagerOptions,
|
|
2365
|
+
hub, createHub, Hub, HubOptions,
|
|
2366
|
+
deploy, defineConfig, DeployConfig, AppConfig,
|
|
2367
|
+
tenant, TenantModule, TenantOptions, TenantContext,
|
|
2368
|
+
```
|
|
2369
|
+
|
|
2370
|
+
### Client-side (from `'weifuwu/react'`)
|
|
2371
|
+
|
|
2372
|
+
```ts
|
|
2373
|
+
TsxContext, setCtx, useCtx, addCtxRebuilder, useLoaderData,
|
|
2374
|
+
useWebsocket, useAction, useFetch, useQueryState, createStore,
|
|
2375
|
+
Link, useNavigate, useNavigating, addInterceptor,
|
|
2376
|
+
useLocale, useTheme, applyTheme, useFlashMessage,
|
|
2377
|
+
useAgentStream,
|
|
2378
|
+
Head,
|
|
2379
|
+
|
|
2380
|
+
// Types:
|
|
2381
|
+
StoreApi,
|
|
2382
|
+
UseActionOptions, UseActionReturn,
|
|
2383
|
+
UseWebsocketOptions, UseWebsocketReturn,
|
|
2384
|
+
UseAgentStreamOptions, UseAgentStreamReturn, AgentStreamState,
|
|
2106
2385
|
```
|
|
2107
2386
|
|
|
2108
2387
|
---
|