weifuwu 0.61.1 → 0.61.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 CHANGED
@@ -551,7 +551,7 @@ await server.stop(2000) // 超时毫秒
551
551
  | `hostname` | `string` | `'0.0.0.0'` | 监听地址 |
552
552
  | `signal` | `AbortSignal` | — | 通过信号停止 |
553
553
  | `maxBodySize` | `number` | `10MB` | 请求体上限(0=无限) |
554
- | `timeout` | `number` | `30000` | Socket 超时(ms |
554
+ | `timeout` | `number` | `120000` | Socket 超时(ms,2 分钟,适配 LLM 生成等长任务) |
555
555
  | `keepAliveTimeout` | `number` | `5000` | Keep-Alive 超时 |
556
556
  | `headersTimeout` | `number` | `6000` | 请求头超时 |
557
557
  | `shutdown` | `boolean` | `true` | 自动注册 SIGTERM/SIGINT |
@@ -1095,7 +1095,8 @@ app.get('/secure', () => {
1095
1095
  | API | 说明 |
1096
1096
  |-----|------|
1097
1097
  | `new HttpError(msg, status)` | 创建 HTTP 错误,name = 'HttpError' |
1098
- | `DEFAULT_MAX_BODY` | `10 * 1024 * 1024` (10MB) |
1098
+
1099
+ > 请求体上限常量 `DEFAULT_MAX_BODY`(10MB)见上方 serve 选项表 `maxBodySize`。
1099
1100
 
1100
1101
  ---
1101
1102
 
@@ -1991,7 +1992,7 @@ createApp()
1991
1992
  locale: 'zh-CN',
1992
1993
  messages: {
1993
1994
  'title': '仪表盘',
1994
- 'welcome': '欢迎, {name}',
1995
+ 'welcome': '欢迎光临',
1995
1996
  },
1996
1997
  }))
1997
1998
  .mount('#root', App)
@@ -2226,7 +2227,7 @@ import type { RouterOptions } from 'weifuwu/client'
2226
2227
 
2227
2228
  # 组件库 (`weifuwu/components`)
2228
2229
 
2229
- 46 个 HTML 原语组件。每个是 `(_init, ctx) => (props) => VNode`(两阶段组件,与前端框架同一模型),引用 `--wf-*` CSS 变量做主题。另含 `confirm()` / `toast()` 命令式中间件。
2230
+ 48 个 HTML 原语组件。每个是 `(_init, ctx) => (props) => VNode`(两阶段组件,与前端框架同一模型),引用 `--wf-*` CSS 变量做主题。另含 `confirm()` / `toast()` 命令式中间件。
2230
2231
 
2231
2232
  ```ts
2232
2233
  import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
@@ -2273,35 +2274,35 @@ import 'weifuwu/components/style.css' // 包含 Token + 67 布局原语 + 组
2273
2274
  <Alert variant="warning" closable>注意:磁盘空间不足</Alert>
2274
2275
 
2275
2276
  // ├─ 标签 / 徽标 / 头像
2276
- <Badge count={5}>消息</Badge>
2277
- <Badge variant="success">通过</Badge>
2278
- <Tag variant="blue" closable onClose={() => {}}>标签</Tag>
2277
+ <Badge variant="primary">消息</Badge>
2278
+ <Badge variant="success" dot>通过</Badge>
2279
+ <Tag variant="primary" closable onClose={() => {}}>标签</Tag>
2279
2280
  <Avatar name="张三" size="lg" />
2280
2281
 
2281
2282
  // ├─ 卡片 / 统计卡片
2282
- <Card title="卡片标题" extra={<a href="#">更多</a>}>卡片内容</Card>
2283
- <StatCard title="总用户" value="1,234" trend={12.5} variant="primary" />
2283
+ <Card variant="outlined" padding="md">卡片内容</Card>
2284
+ <StatCard label="总用户" value="1,234" trend="up" trendLabel="12%" />
2284
2285
 
2285
2286
  // ├─ 标签页 / 下拉菜单
2286
- <Tabs items={[{ key: 'a', label: '标签A' }, { key: 'b', label: '标签B' }]} activeKey="a" onChange={setTab} />
2287
- <Dropdown items={[{ label: '编辑', onClick: () => {} }, { label: '删除', danger: true }]}>操作</Dropdown>
2287
+ <Tabs items={[{ key: 'a', label: '标签A' }, { key: 'b', label: '标签B' }]} active="a" onChange={setTab} />
2288
+ <Dropdown items={[{ label: '编辑', onClick: () => {} }, { label: '删除', variant: 'danger' }]}>操作</Dropdown>
2288
2289
 
2289
2290
  // ├─ 分页 / 步骤条
2290
2291
  <Pagination total={100} page={1} pageSize={10} onChange={setPage} />
2291
- <Steps items={[{ title: '第一步' }, { title: '第二步' }]} current={1} />
2292
+ <Steps items={[{ key: 's1', label: '第一步' }, { key: 's2', label: '第二步' }]} current={1} />
2292
2293
 
2293
2294
  // ├─ 滑块 / 进度条
2294
2295
  <Slider min={0} max={100} value={50} onChange={setValue} />
2295
- <ProgressBar value={75} variant="success" label="75%" />
2296
+ <ProgressBar value={75} label="75%" />
2296
2297
 
2297
2298
  // ├─ 面包屑 / 分割线
2298
2299
  <Breadcrumb items={[{ label: '首页' }, { label: '用户管理' }]} />
2299
2300
  <Divider />
2300
- <Divider orientation="left">分割文字</Divider>
2301
+ <Divider>分割文字</Divider>
2301
2302
 
2302
2303
  // ├─ 加载 / 空状态 / 骨架屏
2303
2304
  <Loading text="加载中..." />
2304
- <EmptyState title="暂无数据" description="请先创建一条记录" action={<Button>新建</Button>} />
2305
+ <EmptyState text="暂无数据" hint="请先创建一条记录"><Button>新建</Button></EmptyState>
2305
2306
  <Skeleton variant="text" lines={3} />
2306
2307
  <Skeleton variant="table" lines={5} cols={4} />
2307
2308
  <Skeleton variant="avatar" />
@@ -2366,42 +2367,42 @@ props 变化 ──────────────────────
2366
2367
  |-----|--------|-----------|------|
2367
2368
  | Button | `Button` | `variant`, `size`, `loading`, `disabled`, `block`, `type` | 按钮 |
2368
2369
  | Input | `Input` | `label`, `name`, `type`, `value`, `placeholder`, `required`, `disabled`, `error`, `hint`, `onInput`, `onChange` | 输入框 |
2369
- | Textarea | `Textarea` | `rows`, `resize`, `maxLength`, `error` | 文本域 |
2370
+ | Textarea | `Textarea` | `rows`, `maxLength`, `showCount`, `error` | 文本域 |
2370
2371
  | Select | `Select` | `options: SelectOption[]`, `placeholder`, `searchable` | 下拉选择 |
2371
2372
 
2372
2373
  ### 表单选择
2373
2374
 
2374
2375
  | 组件 | 导入名 | 关键 Props | 说明 |
2375
2376
  |-----|--------|-----------|------|
2376
- | Checkbox | `Checkbox` | `checked`, `label`, `indeterminate` | 复选框 |
2377
- | Switch | `Switch` | `checked`, `size` | 开关 |
2377
+ | Checkbox | `Checkbox` | `checked`, `label`, `onChange` | 复选框 |
2378
+ | Switch | `Switch` | `checked`, `label`, `onChange` | 开关 |
2378
2379
  | RadioGroup | `RadioGroup` | `options: RadioOption[]`, `value`, `name` | 单选组 |
2379
- | Slider | `Slider` | `min`, `max`, `step`, `value`, `range` | 滑块 |
2380
+ | Slider | `Slider` | `min`, `max`, `step`, `value`, `onChange` | 滑块 |
2380
2381
 
2381
2382
  ### 表单增强
2382
2383
 
2383
2384
  | 组件 | 导入名 | 关键 Props | 说明 |
2384
2385
  |-----|--------|-----------|------|
2385
2386
  | Form | `Form` | `onSubmit`, `validation` | 表单容器 |
2386
- | Field | `Field` | `label`, `error`, `required`, `help` | 字段包装 |
2387
- | FileUpload | `FileUpload` | `accept`, `multiple`, `maxSize`, `onFiles` | 文件上传 |
2388
- | SearchInput | `SearchInput` | `value`, `placeholder`, `onSearch`, `loading` | 搜索框 |
2387
+ | Field | `Field` | `label`, `error`, `required`, `hint` | 字段包装 |
2388
+ | FileUpload | `FileUpload` | `accept`, `multiple`, `maxSize`, `onChange` | 文件上传 |
2389
+ | SearchInput | `SearchInput` | `value`, `placeholder`, `onInput`, `onClear` | 搜索框 |
2389
2390
  | SegmentedControl | `SegmentedControl` | `options: SegmentedOption[]`, `value`, `onChange`, `size` | 分段选择器 |
2390
- | ProgressBar | `ProgressBar` | `value`, `max`, `variant`, `size`, `label` | 进度条 |
2391
+ | ProgressBar | `ProgressBar` | `value`, `max`, `label`, `showValue` | 进度条 |
2391
2392
 
2392
2393
  ### 数据展示
2393
2394
 
2394
2395
  | 组件 | 导入名 | 关键 Props | 说明 |
2395
2396
  |-----|--------|-----------|------|
2396
- | Table | `Table` | `columns: TableColumn[]`, `data`, `loading`, `sortable`, `selectable` | 表格 |
2397
- | Card | `Card` | `title`, `extra`, `shadow`, `padding` | 卡片 |
2398
- | Badge | `Badge` | `variant: BadgeVariant`, `count`, `dot`, `max` | 徽标 |
2399
- | Tag | `Tag` | `variant`, `closable`, `onClose` | 标签 |
2400
- | Avatar | `Avatar` | `src`, `name`, `size`, `shape` | 头像 |
2401
- | Icon | `Icon` | `name: IconName`, `size`, `color`, `strokeWidth` | 图标(内置 100+ 图标,stroke 风格) |
2402
- | StatCard | `StatCard` | `title`, `value`, `trend`, `icon`, `variant` | 统计卡片 |
2403
- | PageHeader | `PageHeader` | `title`, `subtitle`, `actions`, `onBack`, `breadcrumb` | 页面标题 |
2404
- | Img | `Img` | `src`, `alt`, `fallback`, `lazy`, `fit` | 图片(含 fallback) |
2397
+ | Table | `Table` | `columns: TableColumn[]`, `data`, `loading`, `sortKey`, `sortOrder`, `onSort`, `onRowClick` | 表格 |
2398
+ | Card | `Card` | `variant`, `outlined`, `padding`, `clickable`, `hover`, `active`, `onClick` | 卡片 |
2399
+ | Badge | `Badge` | `variant: BadgeVariant`, `dot` | 徽标 |
2400
+ | Tag | `Tag` | `variant: 'default'\|'primary'\|'success'\|'danger'`, `closable`, `onClose` | 标签 |
2401
+ | Avatar | `Avatar` | `src`, `name`, `size`, `color` | 头像 |
2402
+ | Icon | `Icon` | `name: IconName`, `size` | 图标(内置 25 stroke 图标,currentColor 随字号) |
2403
+ | StatCard | `StatCard` | `label`, `value`, `trend: 'up'\|'down'`, `trendLabel`, `icon`, `animate` | 统计卡片 |
2404
+ | PageHeader | `PageHeader` | `title`, `sub`, `display` | 页面标题(actions 放 children) |
2405
+ | Img | `Img` | `src`, `alt`, `fallback`, `loading`, `width`, `height` | 图片(含 fallback) |
2405
2406
  | InView | `InView` | `once`, `threshold`, `rootMargin`, `placeholder`, `onEnter` | 进入视窗后懒加载内容 |
2406
2407
 
2407
2408
  ### 数据反馈
@@ -2410,13 +2411,13 @@ props 变化 ──────────────────────
2410
2411
  |-----|--------|-----------|------|
2411
2412
  | Modal | `Modal` | `open`, `title`, `onClose`, `width`, `footer`, `closable` | 模态框 |
2412
2413
  | Confirm | `Confirm` | `open`, `message`, `confirmText`, `cancelText`, `variant`, `onConfirm`, `onCancel` | 确认对话框(同 `ctx.confirm()` 命令式) |
2413
- | Drawer | `Drawer` | `open`, `title`, `onClose`, `position: DrawerPosition`, `width` | 抽屉 |
2414
+ | Drawer | `Drawer` | `open`, `title`, `position: DrawerPosition`, `onClose`, `footer` | 抽屉 |
2414
2415
  | Tooltip | `Tooltip` | `content`, `position: TooltipPosition`, `disabled` | 工具提示(hover/focus 触发) |
2415
2416
  | Popover | `Popover` | `content`, `position: PopoverPosition`, `trigger`, `open`, `onOpenChange`, `disabled` | 弹出层 |
2416
- | Toast | `Toast` | `items: ToastItem[]`, `position`, `max` | 消息提示 |
2417
- | Alert | `Alert` | `variant: AlertVariant`, `title`, `closable`, `icon` | 警告提示 |
2418
- | Loading | `Loading` | `size`, `text`, `fullscreen` | 加载中 |
2419
- | EmptyState | `EmptyState` | `title`, `description`, `action`, `icon` | 空状态 |
2417
+ | Toast | `Toast` | `toasts: ToastItem[]`, `position`, `max`, `onRemove` | 消息提示 |
2418
+ | Alert | `Alert` | `variant: AlertVariant`, `closable`, `onClose` | 警告提示(内容放 children) |
2419
+ | Loading | `Loading` | `text` | 加载中 |
2420
+ | EmptyState | `EmptyState` | `icon`, `text`, `hint` | 空状态(操作放 children) |
2420
2421
  | Skeleton | `Skeleton` | `variant: SkeletonVariant`, `lines`, `cols`, `width`, `height` | 骨架屏 |
2421
2422
 
2422
2423
  ### 导航组件
@@ -2424,11 +2425,11 @@ props 变化 ──────────────────────
2424
2425
  | 组件 | 导入名 | 关键 Props | 说明 |
2425
2426
  |-----|--------|-----------|------|
2426
2427
  | Breadcrumb | `Breadcrumb` | `items: BreadcrumbItem[]` | 面包屑 |
2427
- | Tabs | `Tabs` | `items: TabItem[]`, `activeKey`, `onChange`, `type` | 标签页 |
2428
- | Dropdown | `Dropdown` | `trigger`, `items: DropdownItem[]`, `open` | 下拉菜单 |
2428
+ | Tabs | `Tabs` | `items: TabItem[]`, `active`, `onChange` | 标签页 |
2429
+ | Dropdown | `Dropdown` | `trigger`, `items: DropdownItem[]`, `open`, `onOpenChange` | 下拉菜单 |
2429
2430
  | Pagination | `Pagination` | `total`, `page`, `pageSize`, `onChange` | 分页 |
2430
- | Steps | `Steps` | `items: StepItem[]`, `current`, `direction`, `size` | 步骤条 |
2431
- | Accordion | `Accordion` | `items: AccordionItem[]`, `multiple`, `defaultActive` | 手风琴 |
2431
+ | Steps | `Steps` | `items: StepItem[]`(`{ key, label }`), `current`, `active` | 步骤条 |
2432
+ | Accordion | `Accordion` | `items: AccordionItem[]`, `multiple` | 手风琴 |
2432
2433
 
2433
2434
  ### 图表
2434
2435
 
@@ -2442,7 +2443,7 @@ props 变化 ──────────────────────
2442
2443
 
2443
2444
  | 组件 | 导入名 | 关键 Props | 说明 |
2444
2445
  |-----|--------|-----------|------|
2445
- | Divider | `Divider` | `orientation`, `plain` | 分割线(水平/垂直/带文字) |
2446
+ | Divider | `Divider` | `vertical` | 分割线(水平带文字放 children,`vertical` 垂直) |
2446
2447
 
2447
2448
  ### AI 交互原语(wf: 协议配套)
2448
2449
 
@@ -2799,7 +2800,7 @@ const LoginPage = (_init, ctx) => {
2799
2800
 
2800
2801
  return (props) =>
2801
2802
  h('div', { class: 'wf-stack', style: { maxWidth: 400, margin: '40px auto' } },
2802
- h(Card, { shadow: 'md' },
2803
+ h(Card, { padding: 'lg' },
2803
2804
  h('div', { class: 'wf-stack', style: { gap: 'var(--wf-space-md)' } },
2804
2805
  h('h2', {}, '登录'),
2805
2806
  h(Form, {
@@ -2847,7 +2848,7 @@ const UserList = (_init, ctx) => {
2847
2848
 
2848
2849
  return h('div', { class: 'wf-stack', style: { gap: 'var(--wf-space-md)' } },
2849
2850
  h('div', { class: 'wf-row', style: { justifyContent: 'space-between', alignItems: 'center' } },
2850
- h(SearchInput, { placeholder: '搜索用户...', value: $.keyword, onSearch: (v: string) => { $.keyword = v } }),
2851
+ h(SearchInput, { placeholder: '搜索用户...', value: $.keyword, onInput: (e: Event) => { $.keyword = (e.target as HTMLInputElement).value } }),
2851
2852
  h(Button, { variant: 'primary' }, '新建用户'),
2852
2853
  ),
2853
2854
  h(Table, {
@@ -2956,8 +2957,8 @@ app.get('/api/search', async (req, ctx) => {
2956
2957
  await ctx.limit('search', { max: 30, windowMs: 60_000 }) // 手动限流,超限抛 429
2957
2958
  })
2958
2959
 
2959
- // 登录防爆破(配合 userSystem):组合键 ip:email
2960
- app.use(rateLimit({ key: (req) => `login:${req.ip}:${req.email}`, max: 5, windowMs: 15 * 60_000 }))
2960
+ // 登录防爆破(配合 userSystem):组合键 ip:email(key 接收标准 Request,取头拿 IP)
2961
+ app.use(rateLimit({ key: (req) => `login:${req.headers.get('x-forwarded-for')}:${req.headers.get('x-user-email')}`, max: 5, windowMs: 15 * 60_000 }))
2961
2962
  ```
2962
2963
 
2963
2964
  | 选项 | 默认 | 说明 |
@@ -3023,7 +3024,7 @@ await msg.migrate() // 幂等建表(conversations + members + messa
3023
3024
  app.use(db)
3024
3025
  app.use(msg) // 注入 ctx.msg
3025
3026
  msg.routes(app) // /api/messages/*(会话/历史/发消息/已读)
3026
- app.ws('/ws', ctx.msg.handler()) // 标准 WS 协议内置
3027
+ app.ws('/ws', msg.handler()) // 标准 WS 协议内置
3027
3028
 
3028
3029
  // 业务代码:持久化 + 鉴权 + 广播 + 未读 + 历史,一次调用
3029
3030
  const conv = await ctx.msg.createConversation(ctx.user.id, { type: 'group', memberIds: ['u2'] })
@@ -3153,7 +3154,7 @@ return () => <AiChat chat={$} />
3153
3154
 
3154
3155
  ```ts
3155
3156
  app.use(redis())
3156
- app.use(rateLimit({ key: (req) => `login:${req.ip}`, max: 5, windowMs: 60_000 })) // 防爆破
3157
+ app.use(rateLimit({ key: (req) => `login:${req.headers.get('x-forwarded-for')}`, max: 5, windowMs: 60_000 })) // 防爆破
3157
3158
  app.use(email({ from: 'no-reply@x.com', adapter: 'resend', resend: { apiKey } }))
3158
3159
  app.use(db)
3159
3160
  app.use(users)
@@ -1,5 +1,5 @@
1
1
  import { type Context, type Handler, type Middleware, type ErrorHandler, type Closeable } from '../types.ts';
2
- import { type WebSocketHandler, type WsUpgradeHandler } from './ws.ts';
2
+ import { type WebSocketHandler, type WsUpgradeHandler, type Hub } from './ws.ts';
3
3
  import type { GraphQLHandler } from '../graphql.ts';
4
4
  /**
5
5
  * WebSocket room hub — manages pub/sub groups for real-time messaging.
@@ -10,12 +10,7 @@ import type { GraphQLHandler } from '../graphql.ts';
10
10
  * The default implementation is in-memory (single process).
11
11
  * Pass a custom Hub with Redis backend for multi-instance deployments.
12
12
  */
13
- export interface Hub {
14
- join(key: string, ws: import('ws').WebSocket): void;
15
- leave(ws: import('ws').WebSocket): void;
16
- send(key: string, message: string): void;
17
- close(): Promise<void>;
18
- }
13
+ export type { Hub } from './ws.ts';
19
14
  export declare class Router<T extends object = Context> {
20
15
  private root;
21
16
  private wsRoot;
package/dist/core/ws.d.ts CHANGED
@@ -8,6 +8,21 @@ import { WebSocketServer } from 'ws';
8
8
  import { Duplex } from 'node:stream';
9
9
  import type { IncomingMessage } from 'node:http';
10
10
  import type { Context } from '../types.ts';
11
+ /**
12
+ * WebSocket room hub — manages pub/sub groups for real-time messaging.
13
+ *
14
+ * Rooms are identified by string keys. Multiple WebSocket connections
15
+ * can join/leave rooms, and messages are broadcast to all members.
16
+ *
17
+ * The default implementation is in-memory (single process).
18
+ * Pass a custom Hub with Redis backend for multi-instance deployments.
19
+ */
20
+ export interface Hub {
21
+ join(key: string, ws: import('ws').WebSocket): void;
22
+ leave(ws: import('ws').WebSocket): void;
23
+ send(key: string, message: string): void;
24
+ close(): Promise<void>;
25
+ }
11
26
  /** WebSocket lifecycle handler. */
12
27
  export type WebSocketHandler = {
13
28
  open?: (ws: import('ws').WebSocket, ctx: Context) => void | Promise<void>;
@@ -20,6 +35,9 @@ type WsMatch = {
20
35
  params: Record<string, string>;
21
36
  };
22
37
  export type WsUpgradeHandler = (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
23
- /** Minimal context shape for WS handler execution. */
24
- export declare function createWsUpgradeHandler(wss: WebSocketServer, matchWs: (segments: string[]) => WsMatch | null): WsUpgradeHandler;
38
+ /**
39
+ * Minimal context shape for WS handler execution.
40
+ * ctx.hub = 路由级 Hub(默认内存,可用 app.wsHub() 替换为 Redis 后端)。
41
+ */
42
+ export declare function createWsUpgradeHandler(wss: WebSocketServer, matchWs: (segments: string[]) => WsMatch | null, hub: Hub): WsUpgradeHandler;
25
43
  export {};
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export type { User } from './types.ts';
4
4
  export { serve, DEFAULT_MAX_BODY } from './core/serve.ts';
5
5
  export type { ServeOptions, Server } from './core/serve.ts';
6
6
  export { Router } from './core/router.ts';
7
- export type { Hub } from './core/router.ts';
7
+ export type { Hub } from './core/ws.ts';
8
8
  export type { WebSocketHandler } from './core/ws.ts';
9
9
  export type { WebSocket } from './types.ts';
10
10
  export { cors } from './middleware/cors.ts';
package/dist/index.js CHANGED
@@ -213,7 +213,7 @@ function serve(router, options) {
213
213
  import { WebSocketServer } from "ws";
214
214
 
215
215
  // src/core/ws.ts
216
- function createWsUpgradeHandler(wss, matchWs) {
216
+ function createWsUpgradeHandler(wss, matchWs, hub) {
217
217
  return (req, socket, head) => {
218
218
  const segments = req.url?.split("/").filter(Boolean) ?? [];
219
219
  const match = matchWs(segments);
@@ -223,7 +223,7 @@ function createWsUpgradeHandler(wss, matchWs) {
223
223
  }
224
224
  wss.handleUpgrade(req, socket, head, (ws) => {
225
225
  const url = new URL(req.url ?? "/", "http://localhost");
226
- const ctx = { params: match.params, query: Object.fromEntries(url.searchParams) };
226
+ const ctx = { params: match.params, query: Object.fromEntries(url.searchParams), hub };
227
227
  if (match.handler.open) match.handler.open(ws, ctx);
228
228
  ws.on("message", (data) => {
229
229
  if (match.handler.message) match.handler.message(ws, ctx, data);
@@ -608,7 +608,8 @@ var Router = class _Router {
608
608
  websocketHandler() {
609
609
  return createWsUpgradeHandler(
610
610
  this.wss,
611
- (segments) => this.matchWsTrie(this.wsRoot, segments)
611
+ (segments) => this.matchWsTrie(this.wsRoot, segments),
612
+ this.hub
612
613
  );
613
614
  }
614
615
  // ── Debug ──────────────────────────────────────────────────
@@ -6,7 +6,7 @@
6
6
  * app.use(db)
7
7
  * app.use(userSystem({ sql })) // 可选依赖:sender_id 来自 ctx.user
8
8
  * app.use(messager({ sql, redis })) // redis 可选:多进程广播
9
- * app.ws('/ws', ctx.msg.handler()) // 实时协议(P2)
9
+ * app.ws('/ws', msg.handler()) // 实时协议(P2)
10
10
  *
11
11
  * 数据模型:_weifuwu_conversations / _weifuwu_conversation_members / _weifuwu_messages
12
12
  * sender_type + sender_id(不 FK users)——user/agent/system 消息天然可存。
package/dist/types.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface User {
24
24
  tenant?: string;
25
25
  [key: string]: unknown;
26
26
  }
27
+ import type { Hub } from './core/ws.ts';
27
28
  export interface Context {
28
29
  params: Record<string, string>;
29
30
  query: Record<string, string>;
@@ -33,6 +34,8 @@ export interface Context {
33
34
  loaderData?: Record<string, unknown>;
34
35
  /** Public environment variables. */
35
36
  env?: Record<string, string>;
37
+ /** WebSocket 房间 Hub(仅 WS 连接 ctx 注入;HTTP 请求 ctx 为 undefined) */
38
+ hub?: Hub;
36
39
  [key: string]: unknown;
37
40
  }
38
41
  export type Handler<T extends object = Context> = (req: Request, ctx: T) => Response | Promise<Response>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.61.1",
4
+ "version": "0.61.2",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {