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 +49 -48
- package/dist/core/router.d.ts +2 -7
- package/dist/core/ws.d.ts +20 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -3
- package/dist/messager/index.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/package.json +1 -1
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` | `
|
|
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
|
-
|
|
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': '
|
|
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
|
-
|
|
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
|
|
2277
|
-
<Badge variant="success">通过</Badge>
|
|
2278
|
-
<Tag variant="
|
|
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
|
|
2283
|
-
<StatCard
|
|
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' }]}
|
|
2287
|
-
<Dropdown items={[{ label: '编辑', onClick: () => {} }, { label: '删除',
|
|
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={[{
|
|
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}
|
|
2296
|
+
<ProgressBar value={75} label="75%" />
|
|
2296
2297
|
|
|
2297
2298
|
// ├─ 面包屑 / 分割线
|
|
2298
2299
|
<Breadcrumb items={[{ label: '首页' }, { label: '用户管理' }]} />
|
|
2299
2300
|
<Divider />
|
|
2300
|
-
<Divider
|
|
2301
|
+
<Divider>分割文字</Divider>
|
|
2301
2302
|
|
|
2302
2303
|
// ├─ 加载 / 空状态 / 骨架屏
|
|
2303
2304
|
<Loading text="加载中..." />
|
|
2304
|
-
<EmptyState
|
|
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`, `
|
|
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`, `
|
|
2377
|
-
| Switch | `Switch` | `checked`, `
|
|
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`, `
|
|
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`, `
|
|
2387
|
-
| FileUpload | `FileUpload` | `accept`, `multiple`, `maxSize`, `
|
|
2388
|
-
| SearchInput | `SearchInput` | `value`, `placeholder`, `
|
|
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`, `
|
|
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`, `
|
|
2397
|
-
| Card | `Card` | `
|
|
2398
|
-
| Badge | `Badge` | `variant: BadgeVariant`, `
|
|
2399
|
-
| Tag | `Tag` | `variant`, `closable`, `onClose` | 标签 |
|
|
2400
|
-
| Avatar | `Avatar` | `src`, `name`, `size`, `
|
|
2401
|
-
| Icon | `Icon` | `name: IconName`, `size
|
|
2402
|
-
| StatCard | `StatCard` | `
|
|
2403
|
-
| PageHeader | `PageHeader` | `title`, `
|
|
2404
|
-
| Img | `Img` | `src`, `alt`, `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`, `
|
|
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` | `
|
|
2417
|
-
| Alert | `Alert` | `variant: AlertVariant`, `
|
|
2418
|
-
| Loading | `Loading` | `
|
|
2419
|
-
| EmptyState | `EmptyState` | `
|
|
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[]`, `
|
|
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[]
|
|
2431
|
-
| Accordion | `Accordion` | `items: AccordionItem[]`, `multiple
|
|
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` | `
|
|
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, {
|
|
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,
|
|
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.
|
|
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',
|
|
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.
|
|
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)
|
package/dist/core/router.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
24
|
-
|
|
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/
|
|
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 ──────────────────────────────────────────────────
|
package/dist/messager/index.d.ts
CHANGED
|
@@ -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',
|
|
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>;
|