weifuwu 0.74.0 → 0.76.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.
Files changed (86) hide show
  1. package/README.md +20 -20
  2. package/dist/ai/types.d.ts +3 -1
  3. package/dist/components/AiChat/AiChat.d.ts +5 -1
  4. package/dist/components/ApprovalCard/ApprovalCard.d.ts +5 -2
  5. package/dist/components/Button/Button.d.ts +2 -0
  6. package/dist/components/CitationCard/CitationCard.d.ts +38 -0
  7. package/dist/components/Command/Command.d.ts +1 -1
  8. package/dist/components/JsonSchemaForm/JsonSchemaForm.d.ts +48 -0
  9. package/dist/components/ReasoningBlock/ReasoningBlock.d.ts +25 -0
  10. package/dist/components/Select/Select.d.ts +11 -1
  11. package/dist/components/SessionList/SessionList.d.ts +42 -0
  12. package/dist/components/TreeSelect/TreeSelect.d.ts +3 -2
  13. package/dist/components/index.d.ts +10 -2
  14. package/dist/components/index.js +14 -14
  15. package/dist/components/style.css +608 -20
  16. package/dist/index.d.ts +0 -2
  17. package/dist/index.js +141 -433
  18. package/dist/layout/weifuwu-layout.css +9 -0
  19. package/dist/test/client/setup.d.ts +12 -0
  20. package/dist/ui/index.d.ts +3 -3
  21. package/dist/ui-dom/Confirm.d.ts +5 -41
  22. package/dist/ui-dom/Notification.d.ts +8 -70
  23. package/dist/ui-dom/Toast.d.ts +8 -43
  24. package/dist/ui-dom/ai.d.ts +1 -1
  25. package/dist/ui-dom/browser.d.ts +1 -1
  26. package/dist/ui-dom/focus-trap.d.ts +1 -1
  27. package/dist/ui-dom/hooks/chat.d.ts +11 -0
  28. package/dist/ui-dom/hooks/events.d.ts +30 -0
  29. package/dist/ui-dom/hooks/external.d.ts +20 -0
  30. package/dist/ui-dom/hooks/index.d.ts +31 -0
  31. package/dist/ui-dom/hooks/input.d.ts +33 -0
  32. package/dist/ui-dom/hooks/media.d.ts +17 -0
  33. package/dist/ui-dom/hooks/popup.d.ts +37 -0
  34. package/dist/ui-dom/hooks/stable.d.ts +39 -0
  35. package/dist/ui-dom/hooks/test/hooks.test.d.ts +1 -0
  36. package/dist/ui-dom/hooks/types.d.ts +78 -0
  37. package/dist/ui-dom/index.d.ts +19 -17
  38. package/dist/ui-dom/index.js +12 -10
  39. package/dist/ui-dom/jsx-runtime.js +1 -1
  40. package/dist/ui-dom/middleware/api.d.ts +3 -3
  41. package/dist/ui-dom/middleware/auth.d.ts +1 -1
  42. package/dist/ui-dom/motion.d.ts +1 -1
  43. package/dist/ui-dom/popup.d.ts +1 -1
  44. package/dist/ui-dom/scroll-lock.d.ts +1 -1
  45. package/dist/ui-dom/store.d.ts +24 -0
  46. package/dist/ui-dom/testing.d.ts +74 -0
  47. package/dist/ui-dom/testing.js +1 -0
  48. package/dist/ui-dom/testing.test.d.ts +1 -0
  49. package/dist/ui-dom/types.d.ts +34 -17
  50. package/dist/ui-dom/use-chat.d.ts +19 -14
  51. package/dist/ui-dom/vdom/build.d.ts +34 -0
  52. package/dist/ui-dom/vdom/diff.d.ts +34 -0
  53. package/dist/ui-dom/vdom/hydration.d.ts +17 -0
  54. package/dist/ui-dom/vdom/index.d.ts +23 -0
  55. package/dist/ui-dom/vdom/mount.d.ts +49 -0
  56. package/dist/ui-dom/vdom/registry.d.ts +27 -0
  57. package/dist/ui-dom/vdom/render.d.ts +13 -0
  58. package/dist/ui-dom/vdom/scheduler.d.ts +13 -0
  59. package/dist/ui-dom/vdom/serve.d.ts +30 -0
  60. package/dist/ui-dom/vdom/ssr.d.ts +44 -0
  61. package/dist/ui-dom/vnode.d.ts +11 -44
  62. package/dist/user/token.d.ts +1 -1
  63. package/docs/compat-three-library.md +1 -1
  64. package/docs/components-map.md +19 -2
  65. package/docs/components.md +9 -4
  66. package/docs/custom-components.md +19 -14
  67. package/docs/data.md +16 -33
  68. package/docs/environment.md +1 -1
  69. package/docs/frontend-middleware.md +65 -79
  70. package/docs/frontend-ui-dom.md +4 -2
  71. package/docs/frontend.md +35 -38
  72. package/docs/layout.md +2 -2
  73. package/docs/realtime.md +23 -28
  74. package/docs/saas.md +8 -6
  75. package/package.json +5 -1
  76. package/dist/ui/ssr-page.d.ts +0 -41
  77. package/dist/ui/ssr.d.ts +0 -35
  78. package/dist/ui-dom/diff.d.ts +0 -28
  79. package/dist/ui-dom/hydration.d.ts +0 -13
  80. package/dist/ui-dom/reactive.d.ts +0 -6
  81. package/dist/ui-dom/registry.d.ts +0 -39
  82. package/dist/ui-dom/render.d.ts +0 -46
  83. package/dist/ui-dom/route-match.d.ts +0 -22
  84. package/dist/ui-dom/serve.d.ts +0 -28
  85. package/dist/ui-dom/ssr.d.ts +0 -56
  86. package/dist/ui-dom/ui.d.ts +0 -78
package/docs/frontend.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 前端 API 核心(weifuwu/ui-dom)
2
2
 
3
- > ⚠️ **`weifuwu/client` 已删除**——本页为历史文档。前端运行时唯一入口是 **`weifuwu/ui-dom`**(UIRouter 纯路由 + uiServe 渲染运行时 + SSR/hydration),权威参考见 **[docs/frontend-ui-dom.md](frontend-ui-dom.md)**。本页的 createApp/router/RouteView 用法已失效,对应新 API 为 `uiServe` / `UIRouter`。
3
+ > ⚠️ **`weifuwu/client` 已并入 `weifuwu/ui-dom`**(`src/client/` 已删除)——本页 import 均用 `weifuwu/ui-dom`。前端运行时唯一入口是 **`weifuwu/ui-dom`**(UIRouter 纯路由 + uiServe 渲染运行时 + SSR/hydration),权威参考见 **[docs/frontend-ui-dom.md](frontend-ui-dom.md)**。
4
4
 
5
5
  > 以下为完整 API 参考,按需查阅。新手建议先阅读 README 的「核心概念」和「快速开始」。
6
6
 
@@ -13,40 +13,37 @@
13
13
  ```js
14
14
  esbuild.build({
15
15
  jsx: 'automatic',
16
- jsxImportSource: 'weifuwu/client',
16
+ jsxImportSource: 'weifuwu/ui-dom',
17
17
  bundle: true,
18
18
  })
19
19
  ```
20
20
 
21
21
  ---
22
22
 
23
- ## createApp — 应用引导
23
+ ## 应用引导(UIRouter + uiServe)
24
24
 
25
25
  ```tsx
26
- import { createApp } from 'weifuwu/client'
26
+ import { UIRouter, uiServe } from 'weifuwu/ui-dom'
27
27
 
28
- const app = createApp()
28
+ const app = new UIRouter()
29
29
 
30
- // 注册中间件
30
+ // 注册中间件(ctx 注入)
31
31
  app.use(middleware1)
32
32
  app.use(middleware2)
33
33
 
34
- // 挂载到 DOM
35
- app.mount('#root', RootComponent)
34
+ // 页面路由(handler = 异步组件)
35
+ app.get('/', () => h(Home, {}))
36
36
 
37
- // 获取当前 ctx
38
- console.log(app.ctx)
39
-
40
- // 销毁
41
- app.destroy()
37
+ // 落地:客户端渲染(hydrate: true 收养 SSR HTML)
38
+ const handle = uiServe(app, { root: '#root' })
42
39
  ```
43
40
 
44
- | 方法 | 说明 |
41
+ | API | 说明 |
45
42
  |------|------|
46
- | `createApp()` | 创建应用实例 |
47
- | `app.use(mw)` | 注册 AppMiddleware |
48
- | `app.mount(selector, RootComponent)` | 挂载到 DOM |
49
- | `app.destroy()` | 卸载应用 |
43
+ | `new UIRouter()` | 创建路由实例 |
44
+ | `app.use(mw)` | 注册中间件(AppMiddleware / UIMiddleware) |
45
+ | `app.get(path, handler)` / `use(prefix, sub)` / `notFound(handler)` | 页面路由 / 子路由树 / 404 |
46
+ | `uiServe(app, { root, hydrate })` | 客户端落地(返回 handle,`handle.close()` 卸载) |
50
47
  | `app.ctx` | 当前 WfuiContext |
51
48
 
52
49
  ---
@@ -54,7 +51,7 @@ app.destroy()
54
51
  ## 组件模型
55
52
 
56
53
  ```tsx
57
- import type { Component, WfuiContext } from 'weifuwu/client'
54
+ import type { Component, WfuiContext } from 'weifuwu/ui-dom'
58
55
 
59
56
  // 两阶段组件:mount(只一次)→ render(每次 dirty/props 变化)
60
57
  const Counter: Component = (_init, ctx) => {
@@ -74,8 +71,8 @@ const Badge: Component = () =>
74
71
  ### 类型流(props 泛型 + ctx 注入)
75
72
 
76
73
  ```tsx
77
- import type { Component } from 'weifuwu/client'
78
- import type { ApiInjected, RouteInjected } from 'weifuwu/client'
74
+ import type { Component } from 'weifuwu/ui-dom'
75
+ import type { ApiInjected, RouteInjected } from 'weifuwu/ui-dom'
79
76
 
80
77
  // ① props 泛型:JSX 使用时自动类型检查(传错类型编译期报错)
81
78
  interface DeckCardProps { title: string; pages: number }
@@ -92,13 +89,13 @@ const Home: Component<{}, ApiInjected & RouteInjected> = (_init, ctx) => {
92
89
  }
93
90
  // 未声明的注入字段编译期报错——注入从"文档约定"变成"类型保证"
94
91
 
95
- createApp()
92
+ const app = new UIRouter<{}>()
96
93
  .use(api()) // 注入 ctx.api
97
- .use(router({ routes })) // 注入 ctx.route / ctx.app
98
- .mount('#root', Home) // mount 时类型累积完整
94
+ .use(toast()) // 注入 ctx.toast
95
+ uiServe(app, { root: '#root' }) // 类型累积完整(UIRouter<C & O>)
99
96
  ```
100
97
 
101
- > 各中间件的注入接口:`api()` → `ApiInjected`、`auth()` → `AuthInjected`、`ws()` → `WsInjected`、`i18n()` → `I18nInjected`、`router()` → `RouteInjected`(均可从 `weifuwu/client` 导入)。
98
+ > 各中间件的注入接口:`api()` → `ApiInjected`、`auth()` → `AuthInjected`、`ws()` → `WsInjected`、`i18n()` → `I18nInjected`、`router()` → `RouteInjected`(均可从 `weifuwu/ui-dom` 导入)。
102
99
 
103
100
  | 规则 | 说明 |
104
101
  |------|------|
@@ -113,8 +110,8 @@ createApp()
113
110
  ### JSX 工厂
114
111
 
115
112
  ```tsx
116
- // 由 esbuild 自动调用(jsxImportSource: 'weifuwu/client')
117
- import { h, jsx, jsxs, jsxDEV, Fragment } from 'weifuwu/client'
113
+ // 由 esbuild 自动调用(jsxImportSource: 'weifuwu/ui-dom')
114
+ import { h, jsx, jsxs, jsxDEV, Fragment } from 'weifuwu/ui-dom'
118
115
 
119
116
  // h 支持 variadic children
120
117
  h('div', { class: 'x' }, child1, child2)
@@ -131,7 +128,7 @@ h('div', { class: 'x' }, child1, child2)
131
128
  | `Portal` / `createPortal(children, portalKey?)` | 渲染到 `document.body#__wf_portal` 独立容器(弹层/对话框,脱离父级 overflow 裁剪) |
132
129
 
133
130
  ```tsx
134
- import { createPortal } from 'weifuwu/client'
131
+ import { createPortal } from 'weifuwu/ui-dom'
135
132
 
136
133
  // 内容渲染到 body 下的独立容器(不在父组件的 DOM 树内)
137
134
  const Tooltip = (_init, ctx) =>
@@ -754,7 +751,7 @@ const UserProfile: Component = (initProps, ctx) => {
754
751
  }
755
752
  ```
756
753
 
757
- ### async 组件(原生——无需 asyncComponent 包装)
754
+ ### async 组件(原生)
758
755
 
759
756
  组件 = 函数,async 组件 = async 函数:签名与同步组件一致 `(initProps, ctx) => renderFn`,渲染器按「返回值是 Promise」原生判别。数据经闭包注入,渲染无 loading 分支:
760
757
 
@@ -771,9 +768,9 @@ const UserProfile = async (initProps, ctx) => {
771
768
  }
772
769
  ```
773
770
 
774
- - **客户端**:首次渲染占位 → 工厂 resolve 后整树重渲染补全(SPA);数据经 `ctx.data` 缓存(hydration 时从 `__DATA__` 同步命中,不重跑请求)
771
+ - **客户端**:主路径 `buildVNode` async 预构建(await 全部工厂;兄弟并行)→ 落地零占位;动态挂载兑底占位 → 局部补全——N 处实例 = N 次工厂调用,数据走 `ctx.data` 则零成本(缓存 + 并发合并)
775
772
  - **服务端**:`ctx.ui.ssr()` 直接 await 工厂 → 数据进 HTML(无占位)
776
- - 工厂缓存绑定页面上下文:路由导航/登录登出时自动失效,工厂以新 ctx 重新执行
773
+ - **占位显示**:动态挂载的 async 组件占位 = 注释节点,resolve 后局部补全(Suspense 边界已裁剪)
777
774
  - 会变的数据:初始值 seed 自服务端数据(`$.count = data.count`),交互改 `$`;初始状态必须确定性(禁止 `window.innerWidth` 直接初始化 → SSR/hydration mismatch)
778
775
 
779
776
  ---
@@ -781,15 +778,15 @@ const UserProfile = async (initProps, ctx) => {
781
778
  ## 前端类型
782
779
 
783
780
  ```tsx
784
- import type { VNode, VNodeType, Component, WfuiContext, AppMiddleware, RouteDef } from 'weifuwu/client'
785
- import type { ApiClient, ApiOptions, ApiRequestOptions, ApiError } from 'weifuwu/client'
786
- import type { AuthClient, AuthOptions } from 'weifuwu/client'
787
- import type { ErrorBoundaryProps } from 'weifuwu/client'
788
- import type { I18nOptions, I18nState, LocalePackage } from 'weifuwu/client'
789
- import type { PopupPositionOptions, PopupPosition } from 'weifuwu/client'
781
+ import type { VNode, VNodeType, Component, WfuiContext, AppMiddleware, RouteDef } from 'weifuwu/ui-dom'
782
+ import type { ApiClient, ApiOptions, ApiRequestOptions, ApiError } from 'weifuwu/ui-dom'
783
+ import type { AuthClient, AuthOptions } from 'weifuwu/ui-dom'
784
+ import type { ErrorBoundaryProps } from 'weifuwu/ui-dom'
785
+ import type { I18nOptions, I18nState, LocalePackage } from 'weifuwu/ui-dom'
786
+ import type { PopupPositionOptions, PopupPosition } from 'weifuwu/ui-dom'
790
787
  import type { ConfirmProps, ConfirmOptions } from 'weifuwu/components'
791
788
  import type { ToastOptions, ToastPosition } from 'weifuwu/components'
792
- import type { RouterOptions } from 'weifuwu/client'
789
+ import type { RouterOptions } from 'weifuwu/ui-dom'
793
790
  ```
794
791
 
795
792
  | 类型 | 说明 |
package/docs/layout.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 本页为 weifuwu 官方文档拆分页 · [返回 README](../README.md)
4
4
 
5
- 纯 CSS 57 个布局原语 + 136 个工具类 + 167 个主题 Token。不绑定任何 JS 框架。
5
+ 纯 CSS 58 个布局原语 + 136 个工具类 + 167 个主题 Token。不绑定任何 JS 框架。
6
6
 
7
7
  > **学习路径与命名规范**:见 [`design/style-guide.md`](../design/style-guide.md)——统一语法 `wf-<域>-<名>`、三档学习(组件 → 10 核心原语 → 完整速查)、场景速查、变量定制。
8
8
 
@@ -25,7 +25,7 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
25
25
 
26
26
  也支持相对路径:`ctx.ui.css('./src/style.css')`。
27
27
 
28
- ## 57 个布局原语
28
+ ## 58 个布局原语
29
29
 
30
30
  | 类别 | 原语 | 效果 |
31
31
  |------|------|------|
package/docs/realtime.md CHANGED
@@ -70,13 +70,12 @@ app.get('/page', (req, ctx) => ctx.ui.html`
70
70
  ```ts
71
71
  app.get('/app.js', (req, ctx) => ctx.ui.js('./src/main.tsx')) // 相对路径
72
72
 
73
- > ⚠️ **weifuwu/client 已删除**——前端运行时唯一入口为 `weifuwu/ui-dom`,见 [frontend-ui-dom.md](frontend-ui-dom.md)。
74
- app.get('/app.js', (req, ctx) => ctx.ui.js('weifuwu/client')) // 或包名
73
+ app.get('/app.js', (req, ctx) => ctx.ui.js('weifuwu/ui-dom')) // 或包名(exports map 解析)
75
74
  ```
76
75
 
77
76
  使用 esbuild 编译:
78
77
  - `bundle: true`, `format: 'esm'`, `platform: 'browser'`
79
- - `jsx: 'automatic'`, `jsxImportSource: 'weifuwu/client'`
78
+ - `jsx: 'automatic'`, `jsxImportSource: 'weifuwu/ui-dom'`
80
79
  - 带 mtime 缓存验证(开发时编辑文件后自动失效)
81
80
 
82
81
  ### ctx.ui.css — CSS 编译
@@ -129,10 +128,11 @@ app.get('/blog/:slug', async (req, ctx) => {
129
128
  服务端 HTML + `window.__DATA__`(ctx.data 种子)到达客户端后,`mount(..., { hydrate: true })` **收养现有 DOM**(不重建、不闪跳),只接线事件/ref/$:
130
129
 
131
130
  ```ts
132
- import { createApp } from 'weifuwu/client'
131
+ import { UIRouter, uiServe } from 'weifuwu/ui-dom'
133
132
 
134
- createApp()
135
- .mount('#root', BlogPage, { hydrate: true }) // 容器已有服务端 HTML
133
+ const app = new UIRouter()
134
+ app.get('/blog/:slug', (loc, ctx) => h(BlogPage, { slug: ctx.params.slug }))
135
+ uiServe(app, { root: '#root', hydrate: true }) // 容器已有服务端 HTML → 收养
136
136
  ```
137
137
 
138
138
  - **游标收养**:元素/文本按位置匹配现有 DOM;tag 不匹配 → 局部替换;文本不一致 → 就地修正;服务端多余节点 → 收尾清理
@@ -140,32 +140,27 @@ createApp()
140
140
  - hydration 后 `$`/dirty/事件全量可用(与纯 SPA 无差别)
141
141
  - 诚实裁剪:Portal 内容就地收养(不移动到 `#__wf_portal`);渲染期非确定性(Date/random)会导致 mismatch(dev 警告)
142
142
 
143
- ### uiSsr — 路由级 SSR(声明即渲染)
143
+ ### 路由级 SSR(UIRouter 两端共享)
144
144
 
145
- 共享路由定义,前后端同一份声明——后端匹配即自动 SSR,无需手写 handler/模板/序列化:
145
+ 同一份 UIRouter 路由定义,后端匹配即自动 SSR,无需手写 handler/模板/序列化:
146
146
 
147
147
  ```tsx
148
- // routes.tsx —— 前后端共用
149
- import type { RouteDef } from 'weifuwu/client'
150
- import { BlogPage } from './pages/BlogPage.tsx'
151
-
152
- export const routes: RouteDef[] = [
153
- { path: '/blog/:slug', component: BlogPage, title: '博客' },
154
- ]
155
-
156
- // server.ts —— 一行中间件:GET 匹配 → 注入 ctx.route.params → await 组件工厂 → 完整 HTML + __DATA__ + bundle
157
- import { uiSsr } from 'weifuwu'
158
- app.use(uiSsr({ routes, bundle: '/static/blog.js' }))
159
-
160
- // blog-hydrate.ts —— 客户端:同一份 routes,router() 注入 ctx.route.params(两端同源)
161
- createApp()
162
- .use(router({ routes }))
163
- .mount('#root', routes[0].component, { hydrate: true })
148
+ // router.ts —— 前后端共用
149
+ import { UIRouter, h } from 'weifuwu/ui-dom'
150
+
151
+ export const app = new UIRouter()
152
+ app.get('/blog/:slug', (loc, ctx) => h(BlogPage, { slug: ctx.params.slug }))
153
+
154
+ // server.ts —— ssrPage 服务端落地:完整 HTML + __DATA__ + styles
155
+ import { ssrPage } from 'weifuwu/ui-dom'
156
+ const { page } = await ssrPage(app, { url: '/blog/hello', styles: ['/components.css'] })
157
+
158
+ // client.ts —— uiServe 客户端收养(hydrate: true)
159
+ uiServe(app, { root: '#root', hydrate: true })
164
160
  ```
165
161
 
166
- - 组件工厂读 `ctx.route.params`(`/blog/:slug` → `ctx.route.params.slug`)——后端 uiSsr / 前端 router **同源注入**
167
- - 未匹配 → next()(交给 API/静态/404);非 GET → next()
168
- - 可自定义 `title` / `template`
162
+ - 组件工厂读 `ctx.params`(`/blog/:slug` → `ctx.params.slug`)——两端同源注入
163
+ - `ssrPage` 选项:`styles`(stylesheet link)/ `title` / `lang` / `rootId`
169
164
 
170
165
  ### weifuwu/dev — 服务端直接跑 .tsx
171
166
 
@@ -180,7 +175,7 @@ Node 原生 TS 只剥离类型(不支持 JSX)。`weifuwu/dev` 注册 esbuild
180
175
  }
181
176
  ```
182
177
 
183
- - 前后端同一 JSX 运行时(`jsxImportSource: weifuwu/client`)→ 两端 VNode 一致 → hydration 可靠
178
+ - 前后端同一 JSX 运行时(`jsxImportSource: weifuwu/ui-dom`)→ 两端 VNode 一致 → hydration 可靠
184
179
  - 与 `ctx.ui.js` 前端动态编译同一理念:无构建、无产物、改代码即生效
185
180
 
186
181
  ---
package/docs/saas.md CHANGED
@@ -12,8 +12,9 @@
12
12
  ```ts
13
13
  import { rateLimit } from 'weifuwu'
14
14
 
15
- app.use(redis()) // 依赖 ctx.redis
16
- app.use(rateLimit({ windowMs: 60_000, max: 100 })) // 全局限流(默认固定窗口)
15
+ const r = redis() // 注意:REDIS_URL 缺 env 构造抛错
16
+ app.use(r) // 依赖 ctx.redis
17
+ app.use(rateLimit({ redis: r.redis, windowMs: 60_000, max: 100 })) // redis 必传(模式 A 显式注入)
17
18
 
18
19
  app.get('/api/search', async (req, ctx) => {
19
20
  await ctx.limit('search', { max: 30, windowMs: 60_000 }) // 手动限流,超限抛 429
@@ -77,7 +78,7 @@ app.post('/secure', (req, ctx) => { ctx.auth.requireAuth(); ... })
77
78
 
78
79
 
79
80
  > ⚠️ **weifuwu/client 已删除**——前端运行时唯一入口为 `weifuwu/ui-dom`,见 [frontend-ui-dom.md](frontend-ui-dom.md)。
80
- - **安全基线**:scrypt 密码哈希(per-user salt + timing-safe,异步不阻塞);access token = HMAC-SHA256 JWT(与 `weifuwu/client` 的 `auth()` 天然配对);refresh token = 不透明随机串,DB 只存哈希,logout/轮换即撤销
81
+ - **安全基线**:scrypt 密码哈希(per-user salt + timing-safe,异步不阻塞);access token = HMAC-SHA256 JWT(与 `weifuwu/ui-dom` 的 `auth()` 天然配对);refresh token = 不透明随机串,DB 只存哈希,logout/轮换即撤销
81
82
  - **防枚举**:登录失败统一 401(不泄露邮箱是否存在)
82
83
  - **`ctx.auth` 方法面**:`register` / `login` / `logout` / `requireAuth` / `setPassword(userId, newPwd)` / `createToken(type, payload, { ttlSeconds })`(邮箱验证/密码重置自接)
83
84
  - **多租户感知**:`issueSession` 的 token payload 携带 `tenantId`(来自 `user.tenant`)——中间件自动注入 `ctx.tenantId`,并将会话字段(userId/tenantId/email/name/role)合并到 `ctx.auth`,多租户应用免写 token 解码/租户中间件(数据隔离 SQL 是应用职责)
@@ -117,7 +118,8 @@ ctx.msg.sendTo('u2', { type: 'mention' }) // 用户维度点对点
117
118
  ```ts
118
119
  import { queue } from 'weifuwu'
119
120
 
120
- const q = queue() // 默认 REDIS_URL
121
+ const r = redis(); app.use(r) // Redis 中间件(注入 ctx.redis)
122
+ const q = queue({ redis: r.redis }) // queue 必传 Redis(池命令走轮询连接)
121
123
  app.use(q) // 注入 ctx.queue
122
124
 
123
125
  app.post('/api/generate', async (req, ctx) => {
@@ -184,10 +186,10 @@ app.post('/api/approve', async (req, ctx) => {
184
186
  })
185
187
  ```
186
188
 
187
- 前端解码(`weifuwu/client`):
189
+ 前端解码(`weifuwu/ui-dom`):
188
190
 
189
191
  ```ts
190
- import { aiStream } from 'weifuwu/client'
192
+ import { aiStream } from 'weifuwu/ui-dom'
191
193
 
192
194
  const handle = aiStream('/api/chat', { messages }, {
193
195
  onToken: (text) => { /* 增量 append 到消息 */ },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.74.0",
4
+ "version": "0.76.0",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {
@@ -16,6 +16,10 @@
16
16
  "types": "./dist/ui-dom/index.d.ts",
17
17
  "default": "./dist/ui-dom/jsx-runtime.js"
18
18
  },
19
+ "./ui-dom/testing": {
20
+ "types": "./dist/ui-dom/testing.d.ts",
21
+ "default": "./dist/ui-dom/testing.js"
22
+ },
19
23
  "./layout": "./dist/layout/weifuwu-layout.css",
20
24
  "./layout/style.css": "./dist/layout/weifuwu-layout.css",
21
25
  "./components": {
@@ -1,41 +0,0 @@
1
- /**
2
- * uiSsr — 路由级 SSR 中间件:前端路由表驱动的自动服务端渲染
3
- *
4
- * 开发者只声明路由(path + component),GET 匹配即自动 SSR:
5
- * 匹配 → 注入 params → ctx.ui.ssr 渲染组件(async 工厂 await)→ 页面模板 + __DATA__
6
- * 未匹配 → next()(交给后续 API/静态/404)
7
- *
8
- * 与客户端 router() 共用同一份路由定义(route-match.ts),SPA/SSR 一个声明。
9
- */
10
- import type { Middleware } from '../types.ts';
11
- import type { RouteDef } from '../ui-dom/types.ts';
12
- export interface UiSsrOptions {
13
- /** 前端路由定义(与 router() 共享同一份) */
14
- routes: RouteDef[];
15
- /** 客户端 bundle 路径(自动注入 <script src>,如 '/static/app.js') */
16
- bundle?: string;
17
- /** CSS 路径数组(自动注入 <link rel="stylesheet">,如 ['/static/style.css']) */
18
- styles?: string[];
19
- /** 自定义 title(默认取路由定义 title 或 'weifuwu') */
20
- title?: (ctx: {
21
- params: Record<string, string>;
22
- def: RouteDef;
23
- }) => string;
24
- /** 自定义页面模板:返回完整 HTML(默认内置,含 head/root/__DATA__/styles/bundle) */
25
- template?: (parts: {
26
- html: string;
27
- dataScript: string;
28
- title: string;
29
- bundle?: string;
30
- styles?: string[];
31
- }) => string;
32
- }
33
- /**
34
- * 路由级 SSR 中间件:
35
- *
36
- * ```ts
37
- * const app = new Router()
38
- * app.use(uiSsr({ routes, bundle: '/static/app.js' }))
39
- * ```
40
- */
41
- export declare function uiSsr(opts: UiSsrOptions): Middleware;
package/dist/ui/ssr.d.ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * 服务端 SSR 字符串遍历器 — async 工厂组件(形态 C)的 HTML 序列化
3
- *
4
- * 支持:
5
- * - 同步组件(mount → render → VNode → HTML)
6
- * - async 工厂组件(await 工厂 → 数据进 HTML;服务端不缓存定义,数据 per-request)
7
- * - Fragment / Portal(内联子节点)
8
- * - innerHTML prop(原样输出)、class/style 对象序列化
9
- * - 事件处理器 / ref 剥离、文本自动转义(XSS)
10
- * - 服务端 ctx shim($ no-op dirty、ctx.data 预取、selfId 隔离)
11
- *
12
- * 产物为 HtmlSafe 标记的安全 HTML 片段,可直接内联进 ctx.ui.html 模板。
13
- */
14
- import type { Component, AsyncComponent } from '../ui-dom/vnode.ts';
15
- import { HtmlSafe } from './html-safe.ts';
16
- export { HtmlSafe } from './html-safe.ts';
17
- export interface SsrOptions {
18
- /** 服务端数据存储:ctx.data 预取结果写入此处,调用方序列化进 __DATA__ */
19
- data?: Map<string, unknown>;
20
- }
21
- /**
22
- * SSR 渲染组件 → HTML 片段(HtmlSafe 标记,可直接内联进 ctx.ui.html 模板,不二次转义)。
23
- *
24
- * ```ts
25
- * const data = new Map()
26
- * const html = await ctx.ui.ssr(BlogPage, { slug }, { data })
27
- * return ctx.ui.html`
28
- * <div id="root">${html}</div>
29
- * ${ctx.ui.ssrData(data)}
30
- * `
31
- * ```
32
- */
33
- export declare function ssrToString(Comp: Component | AsyncComponent, props: Record<string, any>, serverCtx: any, opts?: SsrOptions): Promise<HtmlSafe>;
34
- /** 序列化数据存储 → window.__DATA__ 脚本(防 XSS:转义 <) */
35
- export declare function serializeData(data: Map<string, unknown>): string;
@@ -1,28 +0,0 @@
1
- /**
2
- * weifuwu/client 增量 diff — patchValue 及全部 diff 辅助
3
- *
4
- * 从 render.ts 拆出(P2 结构拆分)。依赖方向:
5
- * diff.ts → render.ts(renderValue/mountComponent/patchPortal/renderPortal)
6
- * diff.ts → registry.ts(callRefCleanup)
7
- * render.ts 不依赖 diff.ts(单向,无环)
8
- */
9
- import type { VNodeChild } from './vnode.ts';
10
- import type { WfuiContext } from './types.ts';
11
- export declare function patchValue(parent: Node, oldNode: Node | null, oldInput: VNodeChild, newInput: VNodeChild, ctx: WfuiContext): Node | null;
12
- export declare function patchProps(el: Element, oldProps: Record<string, unknown> | null, newProps: Record<string, unknown>): void;
13
- /** 为无 key 的子节点自动分配位置 key,确保 keyed diff 正确性 */
14
- export declare function ensureKeys(oldChildren: VNodeChild[], newChildren: VNodeChild[]): void;
15
- /**
16
- * 将 children 映射到它们实际产生的 DOM 节点(每个 child 一个 Node[])。
17
- *
18
- * 1 VNode ≠ 1 DOM 节点:Fragment 展开成多个直属节点(_childNodes),
19
- * null/boolean/Portal 产生 0 个。explicitNodes 提供限定范围(Fragment 子项
20
- * 用其自身 _childNodes),否则从 source(parent.childNodes 或给定快照)按计数游标分配。
21
- */
22
- export declare function mapChildDomNodes(source: Node[], children: VNodeChild[]): (Node[] | null)[];
23
- export declare function normalize(children: VNodeChild): VNodeChild[];
24
- /**
25
- * keyed 子节点 diff。返回每个 new child 的 DOM 节点范围(newNodes,可为空)。
26
- * oldNodes:old children → 实际 DOM 节点(mapChildDomNodes 结果)。
27
- */
28
- export declare function patchKeyedChildren(parent: Node, oldChildren: VNodeChild[], newChildren: VNodeChild[], ctx: WfuiContext, oldNodes?: (Node[] | null)[], rangeStart?: Node | null): (Node[] | null)[];
@@ -1,13 +0,0 @@
1
- /**
2
- * weifuwu/client Hydration(游标收养)— 收养服务端 HTML,不重建 DOM
3
- *
4
- * 从 render.ts 拆出(P2 结构拆分)。依赖:render.ts(flattenChildren/SVG 常量)、
5
- * diff.ts(patchProps)、registry.ts(idRegistry/nextComponentId/resolveAsyncFactory)。
6
- */
7
- import type { VNode } from './vnode.ts';
8
- import type { WfuiContext } from './types.ts';
9
- /**
10
- * Hydration 挂载入口:收养 container 内现有服务端 HTML。
11
- * 渲染完收尾:删除服务端有、客户端没有的残留 DOM。
12
- */
13
- export declare function hydrateVNode(container: Element, vnode: VNode, ctx: WfuiContext): Promise<void>;
@@ -1,6 +0,0 @@
1
- /**
2
- * 创建响应式状态容器:深度 Proxy,任意层级属性赋值自动触发 dirty。
3
- *
4
- * 纯 JS(无 DOM),客户端(ctx.ui.$())与服务端(SSR ctx shim)共用。
5
- */
6
- export declare function createReactiveState(dirty: () => void): Record<string, any>;
@@ -1,39 +0,0 @@
1
- /**
2
- * weifuwu/ui-dom 注册表 — 工厂版(serve 创建局部实例,隔离于 createApp 的模块级状态)
3
- *
4
- * 复制自 client/registry.ts(算法相同),但**无模块级单例**——uiServe 创建实例
5
- * 注入 ctx.__registry,render/diff/ui 经 getRegistry(ctx) 读取,与 createApp 零交叉。
6
- */
7
- import type { VNode, Component, AsyncComponent } from './vnode.ts';
8
- import type { WfuiContext } from './types.ts';
9
- type UnmountHook = (id: string) => void;
10
- interface FactoryEntry {
11
- promise: Promise<Component<any, any>>;
12
- resolved?: Component<any, any>;
13
- }
14
- /** 注册表实例状态 */
15
- export interface Registry {
16
- idCounter: number;
17
- idRegistry: Map<string, VNode>;
18
- unmountHooks: UnmountHook[];
19
- asyncFactoryCache: WeakMap<AsyncComponent<any, any>, FactoryEntry>;
20
- }
21
- /** 创建局部注册表(uiServe 每实例一个——组件 id/dirty/卸载钩子与 createApp 隔离) */
22
- export declare function createRegistry(): Registry;
23
- /** 从 ctx 取注入的注册表(render/diff/ui 统一入口;serve 注入 __registry) */
24
- export declare function getRegistry(ctx: any): Registry;
25
- /** 指定实例生成组件 ID */
26
- export declare function nextComponentIdFor(reg: Registry): string;
27
- /** 指定实例注册卸载钩子(组件从 idRegistry 注销时触发) */
28
- export declare function onComponentUnmountFor(reg: Registry, hook: UnmountHook): () => void;
29
- /** 启动 async 工厂(幂等,缓存) */
30
- export declare function startAsyncFactory(reg: Registry, Comp: AsyncComponent, ctx: WfuiContext): FactoryEntry;
31
- /** async 模式:await 工厂定义 */
32
- export declare function resolveAsyncFactory(reg: Registry, Comp: AsyncComponent, ctx: WfuiContext): Promise<Component>;
33
- /** sync 模式:工厂已解析 → 定义;未解析 → undefined */
34
- export declare function resolveAsyncFactorySync(reg: Registry, Comp: AsyncComponent): Component | undefined;
35
- /** 包裹 ref 回调调用——用户 ref 逻辑抛错时不中断渲染/卸载管线 */
36
- export declare function safeCallRef(ref: Function, arg: any, phase: 'mount' | 'cleanup', name?: string): void;
37
- /** 通知 ref 清理 + Portal 子容器清理(指定实例) */
38
- export declare function callRefCleanupFor(input: any, reg: Registry): void;
39
- export {};
@@ -1,46 +0,0 @@
1
- /**
2
- * weifuwu/client 渲染器 — VNode → DOM + patchValue diff
3
- *
4
- * render(vnode, ctx) → 首次渲染,返回 DOM
5
- * patchValue(el, old, new, ctx) → 增量更新
6
- *
7
- * 支持:
8
- * - key 属性(keyed diff)
9
- * - ref / keyed diff
10
- *
11
- * 状态管理:组件使用闭包变量 + ctx.ui.render() 手动触发重渲染。
12
- */
13
- import type { VNode, VNodeChild, Component, AsyncComponent } from './vnode.ts';
14
- import type { WfuiContext } from './types.ts';
15
- export declare const SVG_NS = "http://www.w3.org/2000/svg";
16
- export declare const SVG_TAGS: Set<string>;
17
- export declare function render(input: VNodeChild, ctx: WfuiContext): Node | null;
18
- export declare function renderValue(v: VNodeChild, ctx: WfuiContext): Node | null;
19
- /**
20
- * 异步组件工厂缓存:同一工厂只执行一次,多实例/多渲染共享。
21
- * resolved 记录已解析的定义(同步快速路径用)。
22
- * (缓存本体在 registry.ts,本文件仅保留依赖 render 的调度函数)
23
- */
24
- /**
25
- * 占位完成后整树重渲染(async 工厂已解析,diff 收敛到目标位置)。
26
- * 优先走 serve 的调度(重新执行 handler → 新树 → diff 传递 _asyncDef);
27
- * 无 serve 注入(SSR/独立渲染)时回退 ui.render(['_wf_root'])。
28
- */
29
- export declare function scheduleFullReRender(ctx: WfuiContext): void;
30
- /**
31
- * 同步 mount 组件(async 工厂占位策略):
32
- * - 同步组件 → def(props, ctx) → render fn → 输出 VNode
33
- * - async 工厂已解析 → 同同步组件
34
- * - async 工厂未解析 → 占位(返回 null)+ 启动工厂 + 完成后整树重渲染
35
- */
36
- export declare function mountComponent(Comp: Component | AsyncComponent, props: VNode['props'], vnode: VNode, ctx: WfuiContext): VNode | null;
37
- /** 首次渲染 Portal:创建远程容器、渲染子节点(不返回占位节点) */
38
- export declare function renderPortal(vnode: VNode, ctx: WfuiContext): void;
39
- /** 更新 Portal:复用远程容器,patch 子节点(不操作父 DOM) */
40
- export declare function patchPortal(oldV: VNode | null, newV: VNode, ctx: WfuiContext): void;
41
- /** 展平嵌套数组 */
42
- export declare function flattenChildren(children: VNodeChild): VNodeChild[];
43
- export declare function mountVNode(container: Element, vnode: VNode, ctx: WfuiContext): void;
44
- export { patchValue } from './diff.ts';
45
- /** 模块级兼容(无 ctx 场景——独立清理,不影响活跃 registry) */
46
- export declare function callRefCleanup(input: any): void;
@@ -1,22 +0,0 @@
1
- /**
2
- * 路由匹配纯函数 — 客户端 router 与服务端 uiSsr 共用(无 DOM 依赖)
3
- *
4
- * 支持路径参数 :id、通配符 *、嵌套路由(children → chain)。
5
- * 与后端 Router(Trie)独立——本模块服务于"前端路由表驱动的页面"。
6
- */
7
- import type { RouteDef } from './types.ts';
8
- export interface FlattenedRoute {
9
- re: RegExp;
10
- keys: string[];
11
- def: RouteDef;
12
- chain: RouteDef[];
13
- }
14
- export declare function joinPaths(a: string, b: string): string;
15
- export declare function compilePath(path: string): {
16
- re: RegExp;
17
- keys: string[];
18
- };
19
- export declare function flattenRoutes(routes: RouteDef[], basePath?: string, chain?: RouteDef[]): FlattenedRoute[];
20
- export declare function matchRoute(path: string, routes: FlattenedRoute[]): FlattenedRoute | null;
21
- /** 从匹配结果提取路径参数 */
22
- export declare function extractParams(path: string, match: FlattenedRoute): Record<string, string>;
@@ -1,28 +0,0 @@
1
- /**
2
- * weifuwu/ui-dom serve — 渲染运行时(VDOM 落地机制)
3
- *
4
- * 定稿架构:serveUI = VDOM(落地,对齐后端 serve(router) = HTTP 传输)。
5
- *
6
- * uiServe(router, { root }) 调用时路由已全部注册——serve 负责:
7
- * 1. 创建渲染运行时:局部 registry + createUi(19 原语)+ popup-tracker + ctx.data/browser
8
- * 2. 监听 popstate/hashchange → router.match(params 注入)→ 中间件链 → handler → vnode
9
- * 3. renderValue / patchValue diff/patch DOM;组件 $ dirty → renderByIds(局部 registry)
10
- *
11
- * 与 createApp 隔离:registry/popup-tracker/dirty 集合全部局部实例(client 零改动)。
12
- */
13
- import type { UIRouter } from './router.ts';
14
- import type { WfuiContext } from './types.ts';
15
- /** uiServe 选项 */
16
- export interface UIServeOptions {
17
- root: string | Element;
18
- hydrate?: boolean;
19
- }
20
- /** serve 句柄 */
21
- export interface UIServeHandle<C extends object = {}> {
22
- /** 释放全部资源(监听/渲染状态/注册表) */
23
- close(): void;
24
- /** 当前 ctx(调试/测试用)——含 UIRouter ctx 注入的类型扩展 */
25
- ctx: WfuiContext & C;
26
- }
27
- /** uiServe — 绑定唯一根节点 + URL 驱动渲染(= VDOM 落地,对齐 serve(router)) */
28
- export declare function uiServe<RC extends object = {}>(router: UIRouter<RC>, options: UIServeOptions): UIServeHandle<RC>;