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 +234 -63
- package/cli.ts +3 -0
- package/dist/ai/provider.d.ts +10 -1
- package/dist/cli.js +3 -0
- package/dist/csrf.d.ts +11 -5
- package/dist/env.d.ts +41 -0
- package/dist/flash.d.ts +5 -0
- package/dist/i18n.d.ts +27 -2
- package/dist/iii/types.d.ts +2 -0
- package/dist/index.d.ts +8 -10
- package/dist/index.js +343 -275
- package/dist/rate-limit.d.ts +2 -1
- package/dist/request-id.d.ts +16 -7
- package/dist/router.d.ts +3 -0
- package/dist/theme.d.ts +25 -2
- package/dist/trace.d.ts +44 -0
- package/dist/types.d.ts +0 -17
- package/dist/upload.d.ts +5 -0
- package/dist/user/client.d.ts +11 -3
- package/dist/user/types.d.ts +19 -4
- package/dist/validate.d.ts +5 -0
- package/package.json +2 -2
- package/dist/kb.d.ts +0 -70
- package/dist/oauth-client.d.ts +0 -41
- package/dist/preferences.d.ts +0 -18
- package/dist/server.d.ts +0 -1
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 **
|
|
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 .
|
|
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
|
-
|
|
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
|
-
//
|
|
1178
|
-
//
|
|
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
|
-
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
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
|
}
|
package/dist/ai/provider.d.ts
CHANGED
|
@@ -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;
|