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.
Files changed (46) hide show
  1. package/README.md +431 -152
  2. package/cli.ts +3 -0
  3. package/dist/agent/types.d.ts +2 -1
  4. package/dist/ai/provider.d.ts +10 -1
  5. package/dist/analytics.d.ts +2 -2
  6. package/dist/cache.d.ts +4 -4
  7. package/dist/cli.js +3 -0
  8. package/dist/cors.d.ts +2 -2
  9. package/dist/csrf.d.ts +11 -5
  10. package/dist/deploy/types.d.ts +2 -2
  11. package/dist/env.d.ts +33 -0
  12. package/dist/flash.d.ts +5 -0
  13. package/dist/helmet.d.ts +2 -2
  14. package/dist/hub.d.ts +2 -1
  15. package/dist/i18n.d.ts +27 -2
  16. package/dist/iii/register-worker.d.ts +1 -1
  17. package/dist/iii/types.d.ts +5 -3
  18. package/dist/index.d.ts +8 -10
  19. package/dist/index.js +434 -359
  20. package/dist/kb/types.d.ts +8 -0
  21. package/dist/logdb/types.d.ts +2 -1
  22. package/dist/mailer.d.ts +2 -1
  23. package/dist/messager/types.d.ts +2 -1
  24. package/dist/opencode/types.d.ts +2 -1
  25. package/dist/permissions.d.ts +2 -2
  26. package/dist/postgres/module.d.ts +2 -1
  27. package/dist/postgres/types.d.ts +2 -2
  28. package/dist/queue/types.d.ts +2 -2
  29. package/dist/rate-limit.d.ts +3 -2
  30. package/dist/react.js +6 -6
  31. package/dist/redis/types.d.ts +2 -2
  32. package/dist/request-id.d.ts +16 -7
  33. package/dist/router.d.ts +3 -0
  34. package/dist/seo.d.ts +2 -2
  35. package/dist/serve.d.ts +1 -1
  36. package/dist/session.d.ts +9 -5
  37. package/dist/tailwind.d.ts +9 -0
  38. package/dist/tenant/types.d.ts +3 -3
  39. package/dist/theme.d.ts +25 -2
  40. package/dist/trace.d.ts +44 -0
  41. package/dist/types.d.ts +8 -17
  42. package/dist/upload.d.ts +9 -2
  43. package/dist/user/client.d.ts +11 -3
  44. package/dist/user/types.d.ts +21 -6
  45. package/dist/validate.d.ts +5 -0
  46. 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
- | `csrfToken` | `csrf()` | `string` | CSRF token |
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 & { csrfToken: string }>
180
- .use(requestId()) // → Router<Context & { csrfToken, requestId }>
181
- .use(postgres()) // → Router<Context & { csrfToken, requestId, sql }>
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.csrfToken // ✅ string (IDE autocomplete)
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 **2 patterns** — learn these and you know every module.
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 .stop()
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
- ### agent [β]
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.csrfToken — set on GET/HEAD/OPTIONS
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
- ### graphql [β]
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, or path + query params |
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
- ### oauthClient [β] — Social login (OAuth 2.0 client)
1065
+ ### oauthLogin (via user()) — Social login (OAuth 2.0 client) [Security]
858
1066
 
859
- ```ts
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
- app.use(user({ pg, jwtSecret })) // required — user management
864
- app.use('/auth', oauthClient({ // mounts /auth/google, /auth/google/callback
1071
+ const u = user({
865
1072
  pg,
866
- jwtSecret,
867
- redirectUrl: '/dashboard',
868
- providers: {
869
- google: {
870
- clientId: process.env.GOOGLE_CLIENT_ID,
871
- clientSecret: process.env.GOOGLE_CLIENT_SECRET,
872
- },
873
- github: {
874
- clientId: process.env.GITHUB_CLIENT_ID,
875
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
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
- app.use('/auth', oauthClient({
1097
+ const u = user({
887
1098
  pg,
888
- jwtSecret,
889
- providers: {
890
- discord: {
891
- clientId: process.env.DISCORD_CLIENT_ID,
892
- clientSecret: process.env.DISCORD_CLIENT_SECRET,
893
- authUrl: 'https://discord.com/api/oauth2/authorize',
894
- tokenUrl: 'https://discord.com/api/oauth2/token',
895
- userUrl: 'https://discord.com/api/users/@me',
896
- parseUser: (data) => ({
897
- id: data.id,
898
- email: data.email ?? '',
899
- name: data.global_name ?? data.username,
900
- avatarUrl: data.avatar
901
- ? `https://cdn.discordapp.com/avatars/${data.id}/${data.avatar}.png`
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
- The module auto-creates a `_auth_providers` table (`user_id`, `provider`, `provider_id`, `email`, `name`, `avatar_url`) on first request. Built-in providers (Google, GitHub) have preset URLs — you only need to provide `clientId` and `clientSecret`.
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
- // Client-side switching (interceptor auto-handles /__theme/:value)
1178
- // GET /__theme/dark — 302 + Set-Cookie
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
- // → ctx.i18n = { locale: 'zh', t: (key) => string, set: (locale) => Response }
1201
- // ctx.i18n.t('welcome') '欢迎'
1202
- // ctx.i18n.locale → 'zh'
1203
- // ctx.i18n.set('en', '/settings') — 302 + Set-Cookie
1204
- // GET /__lang/enswitch locale (client-side interceptor)
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
- Request-scoped tracing via `AsyncLocalStorage`. Used internally by `serve()`.
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
- - Both the hydration script and the page component share the same store via `globalThis.__WEIFUWU_CTX_STORE`
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 navigate = useNavigate() // programmatic: navigate('/contact')
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 `__weifuwu_root`, replaces in-place. Middleware runs on server each nav — data is always fresh.
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 (`globalThis.__WEIFUWU_CTX_STORE`). `useLoaderData()` reads from the snapshot via `useSyncExternalStore` — no SSR-specific code needed in your components.
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 → `__WFW_REFRESH(NewComponent)` — `useState` values preserved
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, testApp, TestApp, TestRequest, TestResponse,
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 modules
2286
+ ### Middleware / DevTools
2043
2287
 
2044
2288
  ```ts
2045
- auth, cors, csrf, compress, helmet, logger, rateLimit, requestId, validate, upload,
2046
- theme, i18n, flash, permissions, serveStatic, session, MemoryStore, RedisStore, SessionStore,
2047
- cache, MemoryCache, RedisCache, CacheStore
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 — importable alongside postgres:
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
- ### Client-side (from `'weifuwu/react'`)
2313
+ ### Security / Auth
2066
2314
 
2067
2315
  ```ts
2068
- TsxContext, setCtx, useCtx, addCtxRebuilder, useLoaderData,
2069
- useWebsocket, useAction, useFetch, useQueryState, createStore,
2070
- Link, useNavigate, useNavigating, addInterceptor,
2071
- useLocale, useTheme, applyTheme, useFlashMessage,
2072
- useAgentStream,
2073
- Head
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
- ### AI Provider (framework abstraction)
2325
+ ### UX Middleware
2078
2326
 
2079
2327
  ```ts
2080
- aiProvider, AIProvider, AIProviderOptions
2328
+ theme, ThemeOptions, ThemeInjected,
2329
+ i18n, I18nOptions, I18nInjected,
2330
+ flash, FlashOptions, FlashInjected,
2081
2331
  ```
2082
2332
 
2083
- ### AI SDK (re-exported from `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
- ### Other modules
2347
+ ### API / Routing
2092
2348
 
2093
2349
  ```ts
2094
- theme, i18n, flash, health, analytics, seo, seoMiddleware, seoTags,
2095
- user, mailer, graphql, aiStream, runWorkflow, knowledgeBase, permissions, queue,
2096
- logdb, messager, agent, iii, createWorker, registerWorker,
2097
- opencode, deploy, defineConfig, webhook,
2098
- testApp, TestApp, TestRequest, TestResponse,
2099
- createTestDb, withTestDb,
2100
- getCookies, setCookie, deleteCookie,
2101
- createSSEStream, formatSSE, formatSSEData,
2102
- currentTraceId, currentTrace, runWithTrace, traceElapsed,
2103
- createHub, Hub, HubOptions,
2104
- DEFAULT_MAX_BODY, MIGRATIONS_TABLE,
2105
- fts,
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
  ---