weifuwu 0.23.3 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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)
@@ -193,12 +240,14 @@ Each module exports an `XxxInjected` type (e.g. `PostgresInjected`, `UserInjecte
193
240
 
194
241
  ## Module Patterns
195
242
 
196
- All modules follow one of **2 patterns** — learn these and you know every module.
243
+ All modules follow one of **4 patterns** — learn these and you know every module.
197
244
 
198
245
  | Pattern | How to mount | Example |
199
246
  |---------|-------------|---------|
200
247
  | `[α]` | `app.use(mod())` | `compress()`, `theme()`, `postgres()` |
201
248
  | `[β]` | `app.use('/path', mod())` | `health()`, `ssr({dir})`, `graphql(handler)`, `user()` |
249
+ | `[γ]` | Import and call directly | `mailer()`, `fts`, `cron-utils` |
250
+ | `[δ]` | `import { useXxx } from 'weifuwu/react'` | `useTheme()`, `useLocale()`, `useWebsocket()` |
202
251
 
203
252
  ### Pattern α — Middleware
204
253
 
@@ -206,7 +255,7 @@ All modules follow one of **2 patterns** — learn these and you know every modu
206
255
  app.use(compress()) // basic
207
256
  const pg = postgres() // with extras: .sql, .table, .migrate(), .close()
208
257
  app.use(pg)
209
- app.use(rateLimit({ max: 100 })) // with .stop()
258
+ app.use(rateLimit({ max: 100 })) // with .close()
210
259
  ```
211
260
 
212
261
  ### Pattern β — Router
@@ -219,15 +268,120 @@ app.use('/auth', user({ pg, jwtSecret })) // with .middlew
219
268
  app.ws('/ws', messager({ pg }).wsHandler())
220
269
  ```
221
270
 
222
- β modules that need **separate middleware** use `.middleware()`:
271
+ β modules that need **separate middleware** use `.middleware()`. Most can auto-register both middleware and routes in one call:
223
272
  ```ts
273
+ app.use(theme()) // auto: middleware + /__theme/:value
274
+ app.use(i18n({ dir: './locales' })) // auto: middleware + /__lang/:locale
275
+ app.use(analytics({ pg })) // auto: middleware + /__analytics
276
+ app.use(auth) // auto: middleware + /register, /login (user())
277
+
278
+ // Explicit form when more control is needed:
224
279
  const a = analytics()
225
- app.use(a.middleware()) // tracking
226
- app.use('/', a) // dashboard
280
+ app.use(a.middleware()) // tracking only
281
+ app.use('/', a) // dashboard at custom path
282
+ ```
283
+
284
+ ### Pattern γ — Standalone
285
+
286
+ Modules that don't intercept requests or serve routes. Import and use directly.
287
+
288
+ ```ts
289
+ import { mailer, cronNext, fts } from 'weifuwu'
290
+
291
+ const email = mailer({ transport: 'smtp://...', from: 'noreply@example.com' })
292
+ await email.send({ to: 'user@test.com', subject: 'Hello', text: 'Body' })
293
+
294
+ const next = cronNext('0 9 * * 1-5') // next weekday at 09:00
295
+ ```
296
+
297
+ ### Pattern δ — Client-side
298
+
299
+ React hooks that self-register via `addInterceptor()`. Import to enable.
300
+
301
+ ```tsx
302
+ import { useTheme, useLocale, useWebsocket } from 'weifuwu/react'
303
+
304
+ function ThemeToggle() {
305
+ const { theme, setTheme } = useTheme()
306
+ return <button onClick={() => setTheme('dark')}>Dark</button>
307
+ }
227
308
  ```
228
309
 
229
310
  ---
230
311
 
312
+ ## Module Dependency Map
313
+
314
+ ```mermaid
315
+ graph TD
316
+ serve --> Router
317
+ Router --> postgres
318
+ Router --> redis
319
+ Router --> aiProvider
320
+
321
+ subgraph "DB-Dependent Modules"
322
+ user --> postgres
323
+ session --> postgres
324
+ session -.-> redis
325
+ queue --> postgres
326
+ queue -.-> redis
327
+ permissions --> postgres
328
+ analytics --> postgres
329
+ logdb --> postgres
330
+ tenant --> postgres
331
+ messager --> postgres
332
+ messager -.-> redis
333
+ agent --> postgres
334
+ kb --> postgres
335
+ iii --> postgres
336
+ iii -.-> redis
337
+ end
338
+
339
+ subgraph "AI-Dependent Modules"
340
+ agent --> aiProvider
341
+ kb --> aiProvider
342
+ aiStream --> aiProvider
343
+ opencode --> aiProvider
344
+ runWorkflow --> aiProvider
345
+ end
346
+ ```
347
+
348
+ ## Quick Module Selection
349
+
350
+ | What do you want to do? | Module | Pattern |
351
+ |------------------------|--------|---------|
352
+ | **User registration / login** | `user()` | β |
353
+ | **Simple token/header auth** | `auth()` | α |
354
+ | **JWT verification** | `user().middleware()` | α |
355
+ | **Role-based access control** | `permissions()` | α |
356
+ | **AI chat / generate / stream** | `ctx.ai.generateText()` / `ctx.ai.streamText()` | α (via `aiProvider()`) |
357
+ | **AI agent with knowledge** | `agent()` + `knowledgeBase()` | β |
358
+ | **Send email** | `mailer()` | γ |
359
+ | **File upload** | `upload()` | α |
360
+ | **Object storage (S3/MinIO)** | `s3()` | α |
361
+ | **Rate limiting** | `rateLimit()` | α |
362
+ | **Response caching** | `cache()` | α |
363
+ | **Periodic / delayed jobs** | `queue()` | α |
364
+ | **Page view analytics** | `analytics()` | β |
365
+ | **Structured logging** | `logdb()` | β |
366
+ | **Real-time chat / messager** | `messager()` | β |
367
+ | **Full-text search** | `fts` | γ |
368
+ | **Theme switching** | `theme()` | α |
369
+ | **i18n / localization** | `i18n()` | α |
370
+ | **Flash messages** | `flash()` | α |
371
+ | **Server-Sent Events** | `createSSEStream()` | γ |
372
+ | **GraphQL endpoint** | `graphql()` | β |
373
+ | **Webhook receiver** | `webhook()` | β |
374
+ | **SSR with React** | `ssr()` | β |
375
+ | **Health check** | `health()` | β |
376
+ | **SEO (robots.txt, sitemap)** | `seo()` | β |
377
+ | **Multi-process deploy** | `deploy()` | γ |
378
+ | **Distributed functions (iii)** | `iii()` | β |
379
+ | **Multi-tenant BaaS** | `tenant()` | β |
380
+ | **Client-side routing** | `useNavigate()`, `<Link>` | δ |
381
+ | **WebSocket in React** | `useWebsocket()` | δ |
382
+
383
+ ---
384
+
231
385
  ## Request Tracing & Logging
232
386
 
233
387
  Every request gets a **trace ID** via `AsyncLocalStorage`, injected into responses as `X-Trace-Id`. W3C `traceparent` headers are forwarded.
@@ -347,7 +501,13 @@ Uses `TEST_DATABASE_URL` or `DATABASE_URL`. Automatically skipped in CI if unset
347
501
 
348
502
  ## Module Reference
349
503
 
350
- ### agent [β]
504
+ Modules are organized alphabetically. Each module shows its pattern badge (`[α]` Middleware, `[β]` Router, `[γ]` Standalone, `[δ]` Client-side) and category.
505
+
506
+ **Category key:** AI, API, Clientδ, Database, DevTools, Networking, Security, SSR, UX
507
+
508
+ ---
509
+
510
+ ### agent [β] [AI]
351
511
 
352
512
  ```ts
353
513
  const provider = aiProvider()
@@ -374,7 +534,7 @@ a.run(agentId, { input: 'summarize the data', stream: true })
374
534
  | `.migrate()` | DB setup |
375
535
  | `.close()` | Cleanup |
376
536
 
377
- ### aiStream [β]
537
+ ### aiStream [β] [AI]
378
538
 
379
539
  Creates an AI streaming chat endpoint using the Vercel AI SDK.
380
540
 
@@ -389,7 +549,7 @@ app.use('/chat', chat)
389
549
  | `handler` | `(req, ctx) => AIStreamOptions \| Promise<AIStreamOptions>` | Returns AI SDK options (model, messages, schema, etc.) |
390
550
  | `provider` | `AIProvider` | Optional. If provided and handler omits `model`, `provider.model()` is used as default |
391
551
 
392
- ### analytics [β]
552
+ ### analytics [β] [API]
393
553
 
394
554
  In-memory or PostgreSQL page view tracking with built-in dashboard.
395
555
 
@@ -412,7 +572,7 @@ app.use(a.middleware())
412
572
  app.use('/', a) // dashboard routes
413
573
  ```
414
574
 
415
- ### auth [α]
575
+ ### auth [α] [Security]
416
576
 
417
577
  ```ts
418
578
  app.use(auth({ token: 'sk-123' })) // static token
@@ -445,7 +605,7 @@ Authorization header. This lets logged-in users authenticate via their
445
605
  session cookie without sending a token. Falls back to header/token auth
446
606
  if no session userId is present.
447
607
 
448
- ### compress [α]
608
+ ### compress [α] [DevTools]
449
609
 
450
610
  ```ts
451
611
  app.use(compress()) // brotli > gzip > deflate (min 1KB)
@@ -457,7 +617,7 @@ app.use(compress({ threshold: 2048, level: 4 })) // custom threshold and le
457
617
  | `threshold` | `number` | `1024` | Minimum byte size to compress |
458
618
  | `level` | `number` | `6` | Compression level (zlib) |
459
619
 
460
- ### cors [α]
620
+ ### cors [α] [DevTools]
461
621
 
462
622
  ```ts
463
623
  app.use(cors()) // allow all
@@ -475,7 +635,7 @@ app.use(cors({ credentials: true, maxAge: 3600 }))
475
635
  | `credentials` | `boolean` | `false` | Allow cookies/credentials |
476
636
  | `maxAge` | `number` | — | Preflight cache duration (seconds) |
477
637
 
478
- ### flash [α]
638
+ ### flash [α] [UX]
479
639
 
480
640
  Cookie-based flash message. Read from request, write via redirect.
481
641
 
@@ -495,7 +655,7 @@ app.post('/save', (req, ctx) => {
495
655
  |--------|------|---------|-------------|
496
656
  | `name` | `string` | `'flash'` | Cookie name |
497
657
 
498
- ### cache [α]
658
+ ### cache [α] [DevTools]
499
659
 
500
660
  Response caching middleware with memory and Redis stores. Caches GET/HEAD responses, with tag-based invalidation.
501
661
 
@@ -535,7 +695,7 @@ await mem.set('key', { status: 200, statusText: 'OK', headers: {}, body: '...',
535
695
  mem.close()
536
696
  ```
537
697
 
538
- ### csrf [α]
698
+ ### csrf [α] [Security]
539
699
 
540
700
  ```ts
541
701
  app.use(csrf())
@@ -551,7 +711,7 @@ app.use(csrf())
551
711
  | `key` | `'_csrf'` | Body field fallback |
552
712
  | `excludeMethods` | `['GET','HEAD','OPTIONS']` | Skip validation |
553
713
 
554
- ### deploy [β]
714
+ ### deploy [β] [Networking]
555
715
 
556
716
  Multi-process manager with reverse proxy, health checks, auto-restart, and zero-downtime updates. Works identically locally and in production.
557
717
 
@@ -659,7 +819,7 @@ Restart=always
659
819
 
660
820
 
661
821
 
662
- ### graphql [β]
822
+ ### graphql [β] [API]
663
823
 
664
824
  ```ts
665
825
  const handler: GraphQLHandler = () => ({
@@ -682,7 +842,7 @@ app.use('/graphql', graphql(handler))
682
842
  | `maxDepth` | `number` | `10` | Max query nesting depth |
683
843
  | `timeout` | `number` | `30_000` | Execution timeout (ms) |
684
844
 
685
- ### health [β]
845
+ ### health [β] [API]
686
846
 
687
847
  ```ts
688
848
  app.use('/health', health())
@@ -694,7 +854,7 @@ app.use('/health', health())
694
854
  | `path` | `string` | `'/health'` | Health check endpoint |
695
855
  | `check` | `() => Promise<void>` | — | Async function; throws → 503 |
696
856
 
697
- ### helmet [α]
857
+ ### helmet [α] [Security]
698
858
 
699
859
  15 security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, etc.
700
860
 
@@ -715,7 +875,7 @@ app.use(helmet({ contentSecurityPolicy: "default-src 'self'", xFrameOptions: 'DE
715
875
  | `crossOriginOpenerPolicy` | — | COOP header |
716
876
  | `crossOriginResourcePolicy` | — | CORP header |
717
877
 
718
- ### iii [β] — Worker / Function / Trigger
878
+ ### iii [β] — Worker / Function / Trigger [API]
719
879
 
720
880
  Distributed function execution with WebSocket workers, triggers, and Redis streams.
721
881
 
@@ -751,7 +911,7 @@ await engine.trigger({ function_id: 'orders::create', payload: { items: ['apple'
751
911
 
752
912
 
753
913
 
754
- ### knowledgeBase [β] — RAG with pgvector
914
+ ### knowledgeBase [β] — RAG with pgvector [AI]
755
915
 
756
916
  ```ts
757
917
  import { knowledgeBase, aiProvider } from 'weifuwu'
@@ -805,7 +965,7 @@ replaces old chunks. Provider's `embed()` is used automatically.
805
965
  The HNSW index enables fast approximate nearest-neighbor search (cosine distance).
806
966
 
807
967
 
808
- ### logdb [β]
968
+ ### logdb [β] [API]
809
969
 
810
970
  PostgreSQL structured event logging with monthly partitioning.
811
971
 
@@ -828,7 +988,7 @@ await logger.log({ level: 'info', source: 'app', message: 'hello', metadata: { u
828
988
  | GET | `/` | Query (`?level=`, `?source=`, `?after=`, `?before=`, `?meta.*=`) |
829
989
  | GET | `/:id` | Get single entry |
830
990
 
831
- ### logger [α]
991
+ ### logger [α] [DevTools]
832
992
 
833
993
  ```ts
834
994
  app.use(logger()) // GET /hello 200 5ms
@@ -839,7 +999,7 @@ app.use(logger({ format: 'combined' })) // with query params
839
999
  |--------|------|---------|-------------|
840
1000
  | `format` | `'short' \| 'combined'` | `'short'` | Log format: path only, or path + query params |
841
1001
 
842
- ### mailer
1002
+ ### mailer [γ] [Networking]
843
1003
 
844
1004
  ```ts
845
1005
  const mail = mailer({ from: 'noreply@example.com', transport: 'smtp://user:pass@smtp.example.com:587' })
@@ -854,7 +1014,7 @@ await mail.send({ to: 'user@test.com', subject: 'Hello', text: 'Body', html: '<p
854
1014
 
855
1015
 
856
1016
 
857
- ### oauthClient [β] — Social login (OAuth 2.0 client)
1017
+ ### oauthClient [β] — Social login (OAuth 2.0 client) [Security]
858
1018
 
859
1019
  ```ts
860
1020
  import { oauthClient } from 'weifuwu'
@@ -918,7 +1078,7 @@ app.use('/auth', oauthClient({
918
1078
  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`.
919
1079
 
920
1080
 
921
- ### messager [β]
1081
+ ### messager [β] [Networking]
922
1082
 
923
1083
  Real-time chat with channels, WebSocket, agent routing.
924
1084
 
@@ -945,7 +1105,7 @@ await msg.send(channelId, 'System message', { sender_type: 'system', sender_id:
945
1105
 
946
1106
 
947
1107
 
948
- ### opencode [β]
1108
+ ### opencode [β] [AI]
949
1109
 
950
1110
  AI programming assistant.
951
1111
 
@@ -972,7 +1132,7 @@ app.ws('/opencode', oc.wsHandler())
972
1132
  | `skills` | `object[]` | — | Custom skill definitions |
973
1133
  | `permissions` | `object` | — | Tool permission rules |
974
1134
 
975
- ### postgres [α]
1135
+ ### postgres [α] [Database]
976
1136
 
977
1137
  Type-safe PostgreSQL client with schema builder, CRUD, migrations, soft delete, and JSONB/vector support.
978
1138
 
@@ -1112,7 +1272,7 @@ class MyModule extends PgModule {
1112
1272
 
1113
1273
  Where helpers + `and`/`or`/`not` can be imported from `'weifuwu'` alongside `postgres`. Full column builders and table helpers are in the same barrel.
1114
1274
 
1115
- ### cron-utils
1275
+ ### cron-utils [γ] [DevTools]
1116
1276
 
1117
1277
  Shared cron expression parsing utilities. All functions operate in **local timezone**.
1118
1278
 
@@ -1130,7 +1290,7 @@ console.log(new Date(next))
1130
1290
  | `matches(fields, date)` | Check if a date matches a parsed pattern |
1131
1291
  | `cronNext(expr, from?)` | Calculate next matching timestamp (`from` defaults to now) |
1132
1292
 
1133
- ### fts — Full-Text Search (PostgreSQL)
1293
+ ### fts — Full-Text Search (PostgreSQL) [Database]
1134
1294
 
1135
1295
  Utilities for PostgreSQL full-text search: create GIN indexes, search with ranking, and generate highlighted snippets.
1136
1296
 
@@ -1166,16 +1326,20 @@ await fts.dropIndex(pg.sql, articles)
1166
1326
 
1167
1327
  Search options: `fields`, `limit` (20), `offset` (0), `headline` (false), `language` ('english'), `minRank`.
1168
1328
 
1169
- ### theme [α]
1329
+ ### theme [α] [UX]
1170
1330
 
1171
1331
  ```ts
1332
+ // Single line — auto-registers middleware + /__theme/:value route
1172
1333
  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
1334
 
1177
- // Client-side switching (interceptor auto-handles /__theme/:value)
1178
- // GET /__theme/dark — 302 + Set-Cookie
1335
+ // ctx.theme = { value: 'dark', set: fn }
1336
+ // ctx.theme.value 'dark'
1337
+ // ctx.theme.set('light', '/settings') — 302 + Set-Cookie
1338
+
1339
+ // Explicit form for more control:
1340
+ // const t = theme()
1341
+ // app.use(t.middleware())
1342
+ // app.use('/', t)
1179
1343
  ```
1180
1344
 
1181
1345
  | Option | Type | Default | Description |
@@ -1193,15 +1357,22 @@ app.post('/settings', async (req, ctx) => {
1193
1357
 
1194
1358
  See [`useTheme()`](#usetheme) for client-side usage.
1195
1359
 
1196
- ### i18n [α]
1360
+ ### i18n [α] [UX]
1197
1361
 
1198
1362
  ```ts
1363
+ // Single line — auto-registers middleware + /__lang/:locale route
1199
1364
  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)
1365
+
1366
+ // ctx.i18n = { locale: 'zh', t, set }
1367
+ // ctx.i18n.t('welcome') → '欢迎'
1368
+ // ctx.i18n.locale 'zh'
1369
+ // ctx.i18n.set('en', '/settings')302 + Set-Cookie
1370
+
1371
+ // Explicit form for more control:
1372
+ // const l = i18n()
1373
+ // app.use(l.middleware())
1374
+ // app.use('/', l)
1375
+ ```
1205
1376
  ```
1206
1377
 
1207
1378
  | Option | Type | Default | Description |
@@ -1222,7 +1393,7 @@ app.get('/greet', async (req, ctx) => {
1222
1393
 
1223
1394
  **Client-side:** import `useLocale` from `weifuwu/react`, `useTheme` from `weifuwu/react`.
1224
1395
 
1225
- ### queue [α]
1396
+ ### queue [α] [Database]
1226
1397
 
1227
1398
  Async job queue. Supports immediate, delayed, and recurring (cron) tasks with three backends:
1228
1399
 
@@ -1300,7 +1471,7 @@ Supported cron syntax: `*` (any), `*/n` (every n), `n-m` (range), `n,m,o` (list)
1300
1471
  | POST | `/:type/retry` | Retry all failed jobs of a type |
1301
1472
  | POST | `/retry/:id` | Retry a specific failed job by ID |
1302
1473
 
1303
- ### rateLimit [α]
1474
+ ### rateLimit [α] [Security]
1304
1475
 
1305
1476
  ```ts
1306
1477
  app.use(rateLimit({ max: 100, window: 60_000 })) // 100 req/min, in-memory
@@ -1326,7 +1497,7 @@ app.use(rateLimit({ max: 100, store: 'redis', redis: ctx.redis }))
1326
1497
 
1327
1498
  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
1499
 
1329
- ### redis [α]
1500
+ ### redis [α] [Database]
1330
1501
 
1331
1502
  ```ts
1332
1503
  const r = redis() // reads REDIS_URL
@@ -1340,7 +1511,7 @@ await ctx.redis.set('key', 'value')
1340
1511
  | `url` | `string` | `REDIS_URL` env | Redis connection string |
1341
1512
  | (all ioredis options) | — | — | Passed directly to ioredis |
1342
1513
 
1343
- ### requestId [α]
1514
+ ### requestId [α] [DevTools]
1344
1515
 
1345
1516
  ```ts
1346
1517
  app.use(requestId())
@@ -1353,7 +1524,7 @@ app.use(requestId({ header: 'X-Request-Id', generator: () => crypto.randomUUID()
1353
1524
  | `header` | `string` | `'X-Request-ID'` | Header name to read/write |
1354
1525
  | `generator` | `() => string` | `crypto.randomUUID()` | ID generator |
1355
1526
 
1356
- ### trace
1527
+ ### trace [γ] [DevTools]
1357
1528
 
1358
1529
  Request-scoped tracing via `AsyncLocalStorage`. Used internally by `serve()`.
1359
1530
 
@@ -1372,7 +1543,7 @@ const elapsed = traceElapsed() // ms since request started
1372
1543
  | `traceElapsed()` | Milliseconds elapsed since the trace started |
1373
1544
  | `runWithTrace(traceId, fn)` | Execute `fn` inside a trace scope |
1374
1545
 
1375
- ### s3 [α] — S3-compatible object storage
1546
+ ### s3 [α] — S3-compatible object storage [Networking]
1376
1547
 
1377
1548
  ```ts
1378
1549
  import { s3 } from 'weifuwu'
@@ -1451,7 +1622,7 @@ minio:
1451
1622
  ```
1452
1623
 
1453
1624
 
1454
- ### seo [β] + seoMiddleware [α]
1625
+ ### seo [β] + seoMiddleware [α] [API]
1455
1626
 
1456
1627
  ```ts
1457
1628
  app.use('/', seo({ baseUrl: 'https://example.com', robots: [{ userAgent: '*', allow: '/' }], sitemap: { urls: [{ loc: '/' }] } }))
@@ -1469,7 +1640,7 @@ Also exports `seoTags(config)` for generating meta/og/twitter tags as an HTML st
1469
1640
  | `sitemap` | `SitemapConfig` | — | Sitemap configuration (urls, resolve, cacheTTL) |
1470
1641
  | `headers` | `SeoHeadersConfig` | — | Response headers (e.g. `X-Robots-Tag`) |
1471
1642
 
1472
- ### session [α]
1643
+ ### session [α] [Security]
1473
1644
 
1474
1645
  Cookie-based server-side session management with memory and Redis stores.
1475
1646
 
@@ -1531,7 +1702,7 @@ const redis = new RedisStore(redisClient, 'myapp:session:')
1531
1702
  await redis.destroy('sid')
1532
1703
  ```
1533
1704
 
1534
- ### ssr({ dir }) [β]
1705
+ ### ssr({ dir }) [β] [SSR]
1535
1706
 
1536
1707
  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
1708
 
@@ -1595,7 +1766,7 @@ app.get('/api/ping', () => Response.json({ pong: true }))
1595
1766
 
1596
1767
  Layout components receive `{ children }` and wrap from outer to inner:
1597
1768
 
1598
- ### tenant [β]
1769
+ ### tenant [β] [Networking]
1599
1770
 
1600
1771
  Multi-tenant BaaS with dynamic table API and GraphQL.
1601
1772
 
@@ -1612,7 +1783,7 @@ app.use('/graphql', t.graphql()) // dynamic GraphQL
1612
1783
  | `pg` | `object` | — | PostgreSQL client |
1613
1784
  | `usersTable` | `string` | — | Users table name for tenant membership lookup |
1614
1785
 
1615
- ### upload [α]
1786
+ ### upload [α] [DevTools]
1616
1787
 
1617
1788
  ```ts
1618
1789
  app.post('/upload', upload({ dir: './uploads', maxFileSize: 10_485_760, allowedTypes: ['image/jpeg', 'image/png'] }), (req, ctx) => {
@@ -1628,7 +1799,7 @@ app.post('/upload', upload({ dir: './uploads', maxFileSize: 10_485_760, allowedT
1628
1799
  | `maxFileSize` | `number` | — | Max bytes per file |
1629
1800
  | `allowedTypes` | `string[]` | — | Allowed MIME types |
1630
1801
 
1631
- ### user [β]
1802
+ ### user [β] [Security]
1632
1803
 
1633
1804
  Authentication: register, login, JWT, OAuth2 服务端, 社会化登录.
1634
1805
 
@@ -1665,7 +1836,7 @@ app.use(u.middleware()) // ctx.user
1665
1836
  | `.verify(token)` | Verify JWT token |
1666
1837
  | `.middleware()` | JWT verify middleware — sets `ctx.user` |
1667
1838
 
1668
- ### permissions [α] — RBAC
1839
+ ### permissions [α] — RBAC [Security]
1669
1840
 
1670
1841
  Role-based access control.
1671
1842
 
@@ -1713,7 +1884,7 @@ app.get('/posts/:id', async (req, ctx) => {
1713
1884
  | `.requirePermission(...perms)` | Middleware — rejects if user lacks any permission |
1714
1885
  | `.migrate()` | Create tables |
1715
1886
 
1716
- ### validate [α]
1887
+ ### validate [α] [DevTools]
1717
1888
 
1718
1889
  ```ts
1719
1890
  import { z } from 'zod'
@@ -1747,7 +1918,7 @@ app.post('/contact', validate({ body: z.object({ email: z.string().email() }) })
1747
1918
  | `params` | `ZodSchema` | — | URL params validation schema |
1748
1919
  | `headers` | `ZodSchema` | — | Header validation schema |
1749
1920
 
1750
- ### webhook [β]
1921
+ ### webhook [β] [API]
1751
1922
 
1752
1923
  Webhook receiver with built-in signature verification for Stripe, GitHub, and Slack. Event-based dispatch with replay protection.
1753
1924
 
@@ -1786,7 +1957,7 @@ wh.on('*', (event) => {
1786
1957
 
1787
1958
  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
1959
 
1789
- ### Client-side navigation
1960
+ ### Client-side navigation [δ] [Client]
1790
1961
 
1791
1962
  ```tsx
1792
1963
  import { Link, useNavigate, useNavigating } from 'weifuwu/react'
@@ -1800,7 +1971,7 @@ const loading = useNavigating() // reactive loading state
1800
1971
 
1801
1972
  **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
1973
 
1803
- ### Client-side hooks
1974
+ ### Client-side hooks [δ] [Client]
1804
1975
 
1805
1976
  ```tsx
1806
1977
  import { useWebsocket, useAction, useFetch, useQueryState, createStore, Head } from 'weifuwu/react'
@@ -1840,7 +2011,7 @@ const count = useStore(s => s.count)
1840
2011
 
1841
2012
  **`TsxContext`** — React context holding page data (`params`, `query`, `user`, `parsed`, `theme`, `i18n`, `flash`, `loaderData`, `env`). Used internally by hooks; rarely needed directly.
1842
2013
 
1843
- ### Locale & Theme
2014
+ ### Locale & Theme [δ] [Client]
1844
2015
 
1845
2016
  ```tsx
1846
2017
  import { useLocale } from 'weifuwu/react'
@@ -1904,7 +2075,7 @@ addInterceptor(async (url) => {
1904
2075
  })
1905
2076
  ```
1906
2077
 
1907
- ### Flash messages
2078
+ ### Flash messages [δ] [Client]
1908
2079
 
1909
2080
  ```ts
1910
2081
  import { flash } from 'weifuwu'
@@ -1939,7 +2110,7 @@ function Toast() {
1939
2110
  |--------|------|---------|-------------|
1940
2111
  | `name` | `string` | `'flash'` | Cookie name |
1941
2112
 
1942
- ### Dev mode
2113
+ ### Dev mode [δ] [Client]
1943
2114
 
1944
2115
  Auto-detected when `NODE_ENV === 'development'`. `ssr({dir})` automatically registers importmap, vendor bundle, HMR WebSocket, and file watcher. No explicit setup needed.
1945
2116
 
@@ -1962,7 +2133,7 @@ const provider = aiProvider()
1962
2133
 
1963
2134
  For AI streaming endpoints see [`aiStream`](#aistream-β). For AI agent APIs see [`agent`](#agent-β).
1964
2135
 
1965
- ### aiProvider [α] — AI model & embedding configuration
2136
+ ### aiProvider [α] — AI model & embedding configuration [AI]
1966
2137
 
1967
2138
  ```ts
1968
2139
  const provider = aiProvider() // auto from env
@@ -1995,7 +2166,7 @@ app.post('/ask', async (req, ctx) => {
1995
2166
  | `.streamText(params)` | Stream text (model auto-injected) |
1996
2167
  | `.dimension` | Configured embedding dimension |
1997
2168
 
1998
- ### DAG Workflow
2169
+ ### DAG Workflow [AI]
1999
2170
 
2000
2171
  ```ts
2001
2172
  const tools = { queryUser: tool({ ... }) }
package/cli.ts CHANGED
@@ -92,6 +92,9 @@ async function cmdInit(name: string, opts: { minimal?: boolean; skipInstall?: bo
92
92
  const deps: Record<string, string> = { weifuwu: `^${v}` }
93
93
  const devDeps: Record<string, string> = {}
94
94
  if (!opts.minimal) {
95
+ deps['react'] = '^19'
96
+ deps['react-dom'] = '^19'
97
+ deps['tailwindcss'] = '^4'
95
98
  devDeps['@types/react'] = depVer('@types/react')
96
99
  devDeps['@types/react-dom'] = depVer('@types/react-dom')
97
100
  }
@@ -1,4 +1,13 @@
1
+ import type { Context, Middleware } from '../types.ts';
1
2
  import { generateText as aiGenerateText, streamText as aiStreamText, type LanguageModel, type EmbeddingModel } from 'ai';
3
+ declare module '../types.ts' {
4
+ interface Context {
5
+ ai: AIProvider;
6
+ }
7
+ }
8
+ export interface AIProviderInjected {
9
+ ai: AIProvider;
10
+ }
2
11
  export interface AIProviderOptions {
3
12
  /** API base URL (default: OPENAI_BASE_URL env or http://localhost:11434/v1). */
4
13
  baseURL?: string;
@@ -33,4 +42,4 @@ export interface AIProvider {
33
42
  */
34
43
  streamText(params: Omit<Parameters<typeof aiStreamText>[0], 'model'>): ReturnType<typeof aiStreamText>;
35
44
  }
36
- export declare function aiProvider(options?: AIProviderOptions): AIProvider;
45
+ export declare function aiProvider(options?: AIProviderOptions): Middleware<Context, Context & AIProviderInjected> & AIProvider;