weifuwu 0.27.28 → 0.28.2

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