@doubao-apps/ai 0.0.19 → 0.0.20

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.
@@ -0,0 +1,216 @@
1
+ # Lifecycle Mapping
2
+
3
+ > **Mapping Version**: 1.0.0
4
+ > **Source Framework**: 抖音小程序 (Douyin Mini Program)
5
+ > **Target Framework**: Doubao Apps (React Lynx)
6
+ > **Last Updated**: 2026-04-01
7
+
8
+ ## 官方事实依据
9
+
10
+ - App: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/framework/app`
11
+ - 组件生命周期: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/custom-component/lifetime`
12
+ - 小程序启动流程: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/experience-optimization/launch/process`
13
+
14
+ ## 目标端事实边界
15
+
16
+ 本文件的 Doubao 侧结论以当前仓库源码为准:
17
+
18
+ - `packages/framework/src/define-api/types.ts`
19
+ - `packages/framework/src/define-api/define-page.ts`
20
+ - `packages/framework/src/define-api/define-app.ts`
21
+
22
+ 未在上述源码中检索到的 Hook,不应写成“同名机械映射”。
23
+
24
+ ---
25
+
26
+ ## 页面生命周期(Page)
27
+
28
+ 抖音小程序 `Page` 不能机械映射成一组同名的 Doubao 页面 Hook。当前仓库源码可确认的 `definePage` 生命周期只有 `onCreated / onMounted / onShow / onHide / onDestroy / onError / render()`。
29
+
30
+ | 抖音小程序 | 当前 Doubao 仓库可确认能力 | 迁移策略 |
31
+ |-----------|-----------------------------|----------|
32
+ | `onLoad(options)` | 无同名页面钩子 | 页面输入通过 `getViewData()/getViewContext()` 或页面容器显式注入;初始化请求放 `onMounted` 或组件内 `useEffect` |
33
+ | `onShow()` | `onShow()` | 页面显示 |
34
+ | `onReady()` | `onMounted()` | 首次渲染完成后的逻辑 |
35
+ | `onHide()` | `onHide()` | 页面隐藏 |
36
+ | `onUnload()` | `onDestroy()` | 页面销毁 |
37
+ | `onPullDownRefresh()` | 无同名页面钩子 | 结合页面容器或下拉交互重写 |
38
+ | `onReachBottom()` | 无同名页面钩子 | 优先使用列表或 `scroll-view` 的触底事件 |
39
+ | `onPageScroll({ scrollTop })` | 无同名页面钩子 | 通过页面容器或 `scroll-view` 事件重写 |
40
+ | `onShareAppMessage()` | 视宿主能力决定 | 分享能力,不做机械映射 |
41
+
42
+ ### 页面生命周期示例
43
+
44
+ ```tsx
45
+ // Before (抖音小程序)
46
+ Page({
47
+ data: { id: '', detail: null },
48
+ onLoad(options) {
49
+ this.setData({ id: options.id });
50
+ this.loadDetail(options.id);
51
+ },
52
+ onHide() {
53
+ console.log('page hide');
54
+ },
55
+ onUnload() {
56
+ clearInterval(this.timer);
57
+ },
58
+ loadDetail(id) {
59
+ tt.request({
60
+ url: `/api/detail/${id}`,
61
+ success: (res) => this.setData({ detail: res.data }),
62
+ });
63
+ },
64
+ });
65
+
66
+ // After (Doubao Apps)
67
+ import { FC, definePage, getViewData, useEffect, useState } from '@doubao-apps/framework';
68
+ import { request } from '@doubao-apps/framework/api';
69
+
70
+ const DetailPage: FC = () => {
71
+ const input = getViewData() as { id?: string } | undefined;
72
+ const id = input?.id ?? '';
73
+ const [detail, setDetail] = useState<any>(null);
74
+
75
+ useEffect(() => {
76
+ if (!id) return;
77
+ request({ url: `/api/detail/${id}` }).then((res) => setDetail(res.data));
78
+ }, [id]);
79
+
80
+ return <view className="detail-page">{detail ? <text>{detail.title}</text> : null}</view>;
81
+ };
82
+
83
+ export default definePage({
84
+ onMounted() {
85
+ console.log('page mounted');
86
+ },
87
+ onHide() {
88
+ console.log('page hide');
89
+ },
90
+ onDestroy() {
91
+ // cleanup
92
+ },
93
+ render() {
94
+ return <DetailPage />;
95
+ },
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 页面初始化顺序
102
+
103
+ 官方“小程序启动流程”文档说明,启动页面在渲染树构建过程中会先触发 `Page.onLoad`,视图层页面树构建后通知逻辑层触发 `Page.onShow`。
104
+
105
+ **迁移建议**:
106
+
107
+ - 当前仓库没有公开的 `definePage.onLoad` / `render(query)` 接口,不要把源端页面参数读取逻辑原样平移
108
+ - 页面输入优先通过 `getViewData()/getViewContext()` 或页面容器显式注入
109
+ - 首次数据请求尽量放到 `onMounted` 或组件内 `useEffect`
110
+ - 页面曝光日志可保留在 `onShow`
111
+
112
+ ---
113
+
114
+ ## 组件生命周期(Component)
115
+
116
+ | 抖音小程序 Component | Doubao Apps | 说明 |
117
+ |---------------------|-------------|------|
118
+ | `created()` | 普通变量初始化 | 不能直接等价成 Hook 生命周期 |
119
+ | `attached()` | `useEffect(() => {...}, [])` | 组件挂载初始化 |
120
+ | `ready()` | `useEffect(() => {...}, [])` 或布局完成后逻辑 | 视具体需求合并 |
121
+ | `detached()` | `useEffect(() => () => {...}, [])` | 清理资源 |
122
+
123
+ ### 组件生命周期示例
124
+
125
+ ```tsx
126
+ // Before (抖音小程序)
127
+ Component({
128
+ properties: { itemId: String },
129
+ data: { detail: null },
130
+ lifetimes: {
131
+ attached() {
132
+ this.loadData();
133
+ },
134
+ detached() {
135
+ clearInterval(this.timer);
136
+ },
137
+ },
138
+ methods: {
139
+ loadData() {
140
+ tt.request({
141
+ url: `/api/item/${this.properties.itemId}`,
142
+ success: (res) => this.setData({ detail: res.data }),
143
+ });
144
+ },
145
+ },
146
+ });
147
+
148
+ // After (Doubao Apps)
149
+ import { FC, useEffect, useState } from '@doubao-apps/framework';
150
+ import { request } from '@doubao-apps/framework/api';
151
+
152
+ export const ItemCard: FC<{ itemId: string }> = ({ itemId }) => {
153
+ const [detail, setDetail] = useState<any>(null);
154
+
155
+ useEffect(() => {
156
+ request({ url: `/api/item/${itemId}` }).then((res) => setDetail(res.data));
157
+
158
+ return () => {
159
+ // cleanup
160
+ };
161
+ }, [itemId]);
162
+
163
+ return <view className="item-card">{detail ? <text>{detail.name}</text> : null}</view>;
164
+ };
165
+ ```
166
+
167
+ ---
168
+
169
+ ## `pageLifetimes` 迁移
170
+
171
+ 官方组件生命周期文档说明,自定义组件从基础库 `2.41.0` 起支持:
172
+
173
+ - `pageLifetimes.show`
174
+ - `pageLifetimes.hide`
175
+ - `pageLifetimes.resize`
176
+
177
+ | 抖音小程序 | Doubao Apps | 迁移策略 |
178
+ |-----------|-------------|----------|
179
+ | `pageLifetimes.show({ showFrom })` | 页面 `onShow` + props 透传 | 组件感知页面展示 |
180
+ | `pageLifetimes.hide()` | 页面 `onHide` + props 透传 | 组件感知页面隐藏 |
181
+ | `pageLifetimes.resize(size)` | 页面容器统一监听,再透传尺寸状态 | 不要留在叶子组件内 |
182
+
183
+ ### 推荐改写方式
184
+
185
+ 1. 在页面层统一持有“是否可见”“窗口尺寸”“分屏比例”等状态。
186
+ 2. 通过 props 传给原组件迁移后的 FC。
187
+ 3. 组件内部只保留纯展示和基于 props 的副作用。
188
+
189
+ ---
190
+
191
+ ## App 生命周期
192
+
193
+ | 抖音小程序 | 当前 Doubao 仓库可确认能力 | 说明 |
194
+ |-----------|----------------------------|------|
195
+ | `onLaunch(options)` | `onLaunch()` | 冷启动,仅一次 |
196
+ | `onShow(options)` | `onForeground()` | 应用切前台;若需要监听页面打开,可结合 `onPageOpened({ viewId })` |
197
+ | `onHide()` | `onBackground()` | 应用切后台 |
198
+ | `onError(msg)` | `onError(error)` | 错误监听 |
199
+ | `onPageNotFound(res)` | 未检索到同名 Hook | 需在路由层或宿主层兜底 |
200
+ | `onUnhandledRejection(res)` | 未检索到同名 Hook | Promise 拒绝兜底需放到应用初始化层 |
201
+
202
+ **迁移建议**:
203
+
204
+ - `globalData` 不要直接复制,优先迁移成显式模块状态或上下文
205
+ - `onPageNotFound` / `onUnhandledRejection` 不要假设存在同名 `defineApp` Hook
206
+ - `onUnhandledRejection` 若目标 runtime 无等价入口,则放到应用初始化层统一兜底
207
+
208
+ ---
209
+
210
+ ## 不应机械迁移的生命周期
211
+
212
+ - `onShareAppMessage`
213
+ - 视频挂载 / 直播挂载相关回调
214
+ - 与抖音宿主分屏、分享入口强绑定的页面钩子
215
+
216
+ 这些能力必须结合目标宿主能力重新设计。
@@ -0,0 +1,195 @@
1
+ # Style Mapping
2
+
3
+ > **Mapping Version**: 1.0.0
4
+ > **Source Framework**: 抖音小程序 (Douyin Mini Program)
5
+ > **Target Framework**: Doubao Apps (React Lynx)
6
+ > **Last Updated**: 2026-04-01
7
+
8
+ ## 官方事实依据
9
+
10
+ - 组件概述: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/component/overview/`
11
+ - 小程序启动流程(TTML/TTSS 编译说明): `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/experience-optimization/launch/process`
12
+
13
+ ---
14
+
15
+ ## 样式系统差异总览
16
+
17
+ | 特性 | 抖音小程序 | Doubao Apps |
18
+ |------|-----------|-------------|
19
+ | 样式文件 | `.ttss` | `index.scss` |
20
+ | 类名属性 | `class="xxx"` | `className="xxx"` |
21
+ | 内联样式 | `style="..."` | `style={{...}}` 或尽量收敛到 `index.scss` |
22
+ | 竖向滚动约束 | `scroll-view` 需要固定高度 | 同样需要明确高度 |
23
+ | 单位 | `px`, `rpx` | 以目标样式能力和视觉校验为准 |
24
+
25
+ ---
26
+
27
+ ## 基本规则
28
+
29
+ ### 文件与导入
30
+
31
+ ```tsx
32
+ import './index.scss';
33
+ ```
34
+
35
+ ### 类名迁移
36
+
37
+ ```tsx
38
+ // Before
39
+ <view class="card title-wrap"></view>
40
+
41
+ // After
42
+ <view className="card title-wrap"></view>
43
+ ```
44
+
45
+ ### 内联样式迁移
46
+
47
+ ```tsx
48
+ // Before
49
+ <view style="padding: 16rpx; background: #fff;"></view>
50
+
51
+ // After
52
+ <view className="card"></view>
53
+ ```
54
+
55
+ ```scss
56
+ .card {
57
+ padding: 16rpx;
58
+ background: #fff;
59
+ }
60
+ ```
61
+
62
+ **优先级**:
63
+
64
+ 1. 能落到 `index.scss` 的,优先落样式文件
65
+ 2. 只有动态值才用 `style={{}}`
66
+
67
+ ---
68
+
69
+ ## TTSS → `index.scss`
70
+
71
+ ### 保留 class 语义
72
+
73
+ ```scss
74
+ /* Before (page.ttss) */
75
+ .page {
76
+ padding: 24rpx;
77
+ }
78
+
79
+ .card {
80
+ border-radius: 16rpx;
81
+ background: #fff;
82
+ }
83
+ ```
84
+
85
+ ```tsx
86
+ // After (index.tsx)
87
+ <view className="page">
88
+ <view className="card"></view>
89
+ </view>
90
+ ```
91
+
92
+ ```scss
93
+ /* After (index.scss) */
94
+ .page {
95
+ padding: 24rpx;
96
+ }
97
+
98
+ .card {
99
+ border-radius: 16rpx;
100
+ background: #fff;
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 动态样式处理
107
+
108
+ ### `hidden`
109
+
110
+ 官方源端支持 `hidden` 通用属性;迁移到 Doubao 时优先用条件渲染,其次再考虑 class 切换。
111
+
112
+ ```tsx
113
+ // Before
114
+ <view hidden="{{isLoading}}"></view>
115
+
116
+ // After
117
+ {!isLoading ? <view className="content"></view> : null}
118
+ ```
119
+
120
+ ### 动态 class
121
+
122
+ ```tsx
123
+ // Before
124
+ <view class="item {{active ? 'item--active' : ''}}"></view>
125
+
126
+ // After
127
+ <view className={active ? 'item item--active' : 'item'}></view>
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 选择器迁移策略
133
+
134
+ ### 推荐保留的选择器
135
+
136
+ - 单类选择器
137
+ - 多类组合
138
+ - 简单后代选择器
139
+ - 伪类较少、作用域明确的局部样式
140
+
141
+ ### 需要谨慎处理的选择器
142
+
143
+ - 过深层级选择器
144
+ - 大范围全局重置
145
+ - 依赖宿主默认样式继承的写法
146
+ - 强耦合页面结构的选择器
147
+
148
+ **迁移建议**:把复杂样式收敛到组件局部 class,不要把源项目整套全局样式表原样搬过来。
149
+
150
+ ---
151
+
152
+ ## `scroll-view` 样式
153
+
154
+ 官方源端要求竖向滚动时给定固定高度。迁移到 Doubao 时同样要保留这个约束:
155
+
156
+ ```scss
157
+ .feed-scroll {
158
+ height: 400rpx;
159
+ }
160
+ ```
161
+
162
+ ```tsx
163
+ <scroll-view className="feed-scroll" scrollOrientation="vertical">
164
+ ...
165
+ </scroll-view>
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 图片与布局样式
171
+
172
+ ```tsx
173
+ // Before
174
+ <image class="banner" src="{{url}}" mode="aspectFill" />
175
+
176
+ // After
177
+ <image className="banner" src={url} mode="aspectFill" />
178
+ ```
179
+
180
+ ```scss
181
+ .banner {
182
+ width: 100%;
183
+ height: 320rpx;
184
+ border-radius: 24rpx;
185
+ }
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 样式迁移禁忌
191
+
192
+ - 不要把所有 `.ttss` 强行展开成超长内联对象
193
+ - 不要保留无效 class 名但删除对应样式
194
+ - 不要依赖抖音宿主默认按钮、输入框外观
195
+ - 不要在没有视觉回归的前提下批量改单位
@@ -0,0 +1,223 @@
1
+ # 抖音小程序 API 参考
2
+
3
+ > 本文档为迁移参考,仅列出常用 API 与页面/组件模型。
4
+ > 事实优先来源为抖音开放平台官方文档,已于 `2026-04-01` 校验。
5
+
6
+ ## 官方文档入口
7
+
8
+ - 框架概述: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/miniapp-framework/introduction`
9
+ - 应用 / `App()`: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/framework/app`
10
+ - `Component()` 构造器: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/custom-component/component-constructor`
11
+ - 组件生命周期: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/custom-component/lifetime`
12
+ - 网络连接 / `tt.request`: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/basic-ability/net-connection`
13
+ - `tt.login`: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/api/open-interface/log-in/tt-login`
14
+ - 分享: `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/open-capabilities/general-capabilities/share`
15
+ - `tt.authorize`(逐步废弃): `https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/api/open-interface/authorization/tt-authorize`
16
+
17
+ ---
18
+
19
+ ## 页面 / 组件 / 应用定义
20
+
21
+ ### Page(options)
22
+
23
+ 抖音小程序页面通常通过 `Page({})` 定义,常见能力包括:
24
+
25
+ ```js
26
+ Page({
27
+ data: { key: value },
28
+
29
+ onLoad(options) {},
30
+ onShow() {},
31
+ onReady() {},
32
+ onHide() {},
33
+ onUnload() {},
34
+
35
+ onPullDownRefresh() {},
36
+ onReachBottom() {},
37
+ onPageScroll({ scrollTop }) {},
38
+ onShareAppMessage() {},
39
+
40
+ customMethod() {},
41
+ });
42
+ ```
43
+
44
+ **迁移**:不要假设 Doubao 存在 `onLoad/onReady/onReachBottom/onPageScroll` 这类同名页面钩子;应按当前项目真实的 `definePage` 接口拆到 `onMounted/onShow/onHide/onDestroy` 与组件内 `useEffect`,页面输入优先通过 `getViewData()/getViewContext()` 或页面容器显式注入。
45
+
46
+ ### Component(options)
47
+
48
+ 抖音小程序组件通过 `Component({})` 定义,支持 `properties`、`data`、`methods`、`lifetimes`、`pageLifetimes`:
49
+
50
+ ```js
51
+ Component({
52
+ properties: {
53
+ title: {
54
+ type: String,
55
+ value: '',
56
+ observer(newVal, oldVal) {},
57
+ },
58
+ count: Number,
59
+ },
60
+ data: {},
61
+ lifetimes: {
62
+ attached() {},
63
+ detached() {},
64
+ },
65
+ pageLifetimes: {
66
+ show() {},
67
+ hide() {},
68
+ resize(size) {},
69
+ },
70
+ methods: {
71
+ handleTap() {},
72
+ },
73
+ });
74
+ ```
75
+
76
+ **迁移**:优先转为 `FC<Props>`;若源页面本身是用 `Component()` 构造,则仍按页面迁移到 `definePage({})`。
77
+
78
+ ### App(options)
79
+
80
+ ```js
81
+ App({
82
+ onLaunch(options) {},
83
+ onShow(options) {},
84
+ onHide() {},
85
+ onError(msg) {},
86
+ onPageNotFound(res) {},
87
+ onUnhandledRejection(res) {},
88
+ globalData: {},
89
+ });
90
+ ```
91
+
92
+ **迁移**:按当前项目真实的 `defineApp` 接口重写;当前仓库可确认的是 `onLaunch/onPageOpened/onForeground/onBackground/onDestroy/onError`,不要直接假设存在 `onShow/onHide/onPageNotFound` 同名 Hook。
93
+
94
+ ---
95
+
96
+ ## 生命周期与页面关系
97
+
98
+ 根据官方文档:
99
+
100
+ - `App.onLaunch` 在冷启动完成时触发,全局只触发一次
101
+ - `App.onShow` 在小程序启动或从后台进入前台时触发
102
+ - `App.onHide` 在小程序从前台进入后台时触发
103
+ - 组件 `created / attached / detached` 是组件实例生命周期关键节点
104
+ - 自定义组件 `pageLifetimes.show/hide/resize` 在基础库 `2.41.0` 起可用
105
+
106
+ **迁移建议**:
107
+
108
+ - App 生命周期保留到 `defineApp`,但要按当前项目真实接口改写
109
+ - 页面生命周期保留到 `definePage`,但不要假设存在源端同名 Hook
110
+ - 组件生命周期转为 `useEffect`
111
+ - `pageLifetimes` 上提到页面容器或通过 props 显式透传
112
+
113
+ ---
114
+
115
+ ## 网络
116
+
117
+ ### `tt.request`
118
+
119
+ 官方网络教程明确使用 `tt.request` 发起 HTTP(S) 请求:
120
+
121
+ ```js
122
+ Page({
123
+ data: {
124
+ serverText: '',
125
+ },
126
+ sendRequest() {
127
+ tt.request({
128
+ url: 'http://localhost:3000/',
129
+ success: (res) => {
130
+ if (res.statusCode === 200) {
131
+ this.setData({
132
+ serverText: res.data,
133
+ });
134
+ }
135
+ },
136
+ });
137
+ },
138
+ });
139
+ ```
140
+
141
+ **迁移**:→ `request({ url, method, data, header })`,返回 Promise。
142
+
143
+ ---
144
+
145
+ ## 登录
146
+
147
+ ### `tt.login`
148
+
149
+ 官方文档要点:
150
+
151
+ - `tt.login(options)` 为异步方法
152
+ - `force` 默认值为 `true`
153
+ - 登录成功时可能返回 `code`、`anonymousCode`
154
+ - `code`、`anonymousCode` 均有效期 5 分钟
155
+ - `code` 用于服务端换取 `openid / unionid / session_key`
156
+
157
+ ```js
158
+ tt.login({
159
+ force: true,
160
+ success(res) {
161
+ console.log(res.code, res.anonymousCode);
162
+ },
163
+ });
164
+ ```
165
+
166
+ **迁移判断**:
167
+
168
+ - 这不是简单的 UI API 替换
169
+ - 如果目标 Doubao 项目仍需要登录态,必须重建服务端登录链路
170
+ - 不要把 `tt.login` 机械替换成普通请求或本地 mock
171
+
172
+ ---
173
+
174
+ ## 分享与挂载
175
+
176
+ 官方分享教程说明,抖音小程序分享入口包括:
177
+
178
+ - `button open-type="share"`
179
+ - 右上角更多面板分享
180
+ - 截图分享(页面实现 `onShareAppMessage` 后触发)
181
+
182
+ **迁移判断**:
183
+
184
+ - `onShareAppMessage`
185
+ - `button open-type="share"`
186
+ - 抖音视频/直播挂载能力
187
+
188
+ 以上都属于宿主平台能力,不应被当作普通按钮点击逻辑直接平移。
189
+
190
+ ---
191
+
192
+ ## 授权
193
+
194
+ ### `tt.authorize`
195
+
196
+ 官方文档明确写明:
197
+
198
+ - 该接口“将逐步废弃,后续不再维护”
199
+ - 授权仅支持对应接口触发,不再支持提前向用户发起统一授权请求
200
+
201
+ **迁移建议**:
202
+
203
+ - 遇到旧代码调用 `tt.authorize`,按具体能力重构
204
+ - 不要在新目标代码里继续沿用“先统一授权,再调用能力”的思路
205
+
206
+ ---
207
+
208
+ ## 缓存 / 导航 / UI 交互
209
+
210
+ 官方框架概述明确列举了以下典型能力:
211
+
212
+ - `tt.showToast`:界面交互提示
213
+ - `tt.navigateTo`:页面跳转
214
+
215
+ 基于官方概述与源项目常见实践,抖音小程序还广泛使用:
216
+
217
+ - 数据缓存:`tt.getStorage` / `tt.setStorage` / 同步版本
218
+ - 导航:`tt.navigateBack` / `tt.redirectTo` / `tt.switchTab` / `tt.reLaunch`
219
+ - UI 交互:`tt.showLoading` / `tt.hideLoading` / `tt.showModal`
220
+
221
+ > 上述细分 API 名称与行为属于对官方概述、源项目实践和宿主小程序一致性的综合归纳;迁移时若某个 API 成为关键路径,应再回读对应官方 API 页面。
222
+
223
+ **迁移**:在 Doubao 侧通常对应 `@doubao-apps/framework/api` 的同名或近似 API。