twikoo-func 1.7.24 → 2.0.0-beta.1
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 +41 -4
- package/dist/index.d.mts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +215 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +210 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +39 -21
- package/index.js +0 -1178
- package/utils/cap.js +0 -311
- package/utils/constants.js +0 -20
- package/utils/image.js +0 -376
- package/utils/import.js +0 -290
- package/utils/index.js +0 -512
- package/utils/lib.js +0 -79
- package/utils/logger.js +0 -22
- package/utils/notify.js +0 -309
- package/utils/spam.js +0 -241
package/README.md
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# twikoo-func
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](./LICENSE)
|
|
3
|
+
Twikoo 2.0 服务端适配器。业务逻辑在 `@twikoojs/common`,本包仅做平台入口与注入。
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
## ⚠️ 2.0 升级须知(同名不同义)
|
|
6
|
+
|
|
7
|
+
**包名没变,但包的内容变了。**
|
|
8
|
+
|
|
9
|
+
| | 1.x | 2.0 |
|
|
10
|
+
| ------------------------------------------- | ----------------------------------------- | ------------------------------------------ |
|
|
11
|
+
| 定位 | 公共逻辑 + CloudBase 入口(代码全在这里) | **仅 CloudBase 薄适配器**(< 150 行) |
|
|
12
|
+
| 公共逻辑位置 | `twikoo-func` 内部(`utils/`、`fn/` 等) | **`@twikoojs/common`** |
|
|
13
|
+
| 旧导出(`require('twikoo-func').utils` 等) | 可用 | **不再导出**(1.x 的整包转发已移除,见下) |
|
|
14
|
+
|
|
15
|
+
**旧导出已移除**:2.0 初版曾在 `twikoo-func` 上留了一层转发导出
|
|
16
|
+
(`export * from '@twikoojs/common'` + 加载即打印的弃用告警)作为 1.x 引用路径的过渡;
|
|
17
|
+
经确认**没有外部依赖**,已于 2026-09-19 移除(原计划 2.2.0,提前执行)。
|
|
18
|
+
`require('twikoo-func').xxx` 形式的公共函数调用不再可用——请把对公共逻辑的引用改为
|
|
19
|
+
`@twikoojs/common`(例外:`main`,它是 CloudBase 的挂载入口,长期保留)。
|
|
20
|
+
|
|
21
|
+
**部署方式变更**:`tcb fn deploy` / `npm run deploy` / `login` / `logout`
|
|
22
|
+
脚本与 `@cloudbase/cli` 依赖已移除,**2.0 起只支持控制台部署**(下方流程)。
|
|
23
|
+
这不影响云函数功能,也不影响控制台流程。
|
|
24
|
+
|
|
25
|
+
## 部署(CloudBase 控制台)
|
|
26
|
+
|
|
27
|
+
1. CloudBase 控制台 → 云函数 → 创建函数(模板 Node.js Hello World,运行时 Node 24.11)
|
|
28
|
+
2. 在线编辑器粘贴:`exports.main = require("twikoo-func").main`
|
|
29
|
+
3. 在线装依赖:仅声明 `twikoo-func`(其余依赖随 npm 安装自动拉取)
|
|
30
|
+
4. 创建后状态「正常」即部署完成
|
|
31
|
+
|
|
32
|
+
## 平台核对清单(查阅日期 2026-09-17)
|
|
33
|
+
|
|
34
|
+
- [x] 函数入口 `exports.main(event, context)` 结构与返回体格式(docs.cloudbase.net 云函数章节)
|
|
35
|
+
- [x] 控制台「在线编辑器 + 在线装依赖」流程;CLI(tcb fn deploy)2.0 起不再支持
|
|
36
|
+
- [x] 运行时创建后不可改版本
|
|
37
|
+
- [ ] 环境变量注入方式实测(人工项)
|
|
38
|
+
- [ ] MONGODB_URI 外接 Mongo 实测(人工项)
|
|
39
|
+
|
|
40
|
+
## 注意
|
|
41
|
+
|
|
42
|
+
- `exports.main` 导出名不可改(CloudBase 以此挂载)
|
|
43
|
+
- 环境变量:`MONGODB_URI`(可选外接 Mongo)、`TWIKOO_*` 业务配置存于数据库配置
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { CloudBaseDatabaseLike, PostSubmitDispatcher, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/** `@cloudbase/node-sdk` 的 app 实例(`init()` 返回值) */
|
|
4
|
+
interface TcbAppLike {
|
|
5
|
+
/**
|
|
6
|
+
* 取数据库实例。
|
|
7
|
+
* @returns 数据库句柄
|
|
8
|
+
*/
|
|
9
|
+
database(): CloudBaseDatabaseLike;
|
|
10
|
+
/**
|
|
11
|
+
* 调用另一个云函数(POST_SUBMIT 递归自调用用)。
|
|
12
|
+
* @param options 目标函数名与事件数据
|
|
13
|
+
* @param config 调用配置(timeout:毫秒)
|
|
14
|
+
* @returns 调用结果
|
|
15
|
+
*/
|
|
16
|
+
callFunction(options: {
|
|
17
|
+
name: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
}, config?: {
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/** `@cloudbase/node-sdk` 静态形态(v2 具名导出 / v3 default 导出,形状一致) */
|
|
24
|
+
interface TcbSdkStatic {
|
|
25
|
+
/** 当前环境占位符(`init({ env })` 用) */
|
|
26
|
+
SYMBOL_CURRENT_ENV: symbol;
|
|
27
|
+
/**
|
|
28
|
+
* 初始化 SDK。
|
|
29
|
+
* @param options 初始化项(env:环境标识)
|
|
30
|
+
* @returns app 实例
|
|
31
|
+
*/
|
|
32
|
+
init(options: {
|
|
33
|
+
env: symbol;
|
|
34
|
+
}): TcbAppLike;
|
|
35
|
+
}
|
|
36
|
+
/** 云函数调用上下文(`exports.main` 第二参数) */
|
|
37
|
+
interface TcbContextLike {
|
|
38
|
+
/** 当前云函数名(递归自调用时需要) */
|
|
39
|
+
function_name?: string;
|
|
40
|
+
/**
|
|
41
|
+
* 平台注入的上下文环境变量(含 `TCB_SOURCE_IP`)。
|
|
42
|
+
* 与 `@cloudbase/node-sdk` 的 `getCloudbaseContext()` 取值来源一致。
|
|
43
|
+
*/
|
|
44
|
+
environment?: Record<string, string>;
|
|
45
|
+
/** 同上(SDK 兼容的另一个字段名,`getCloudbaseContext` 会先看 environment 再看它) */
|
|
46
|
+
environ?: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/transform.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* CloudBase 事件 → 内部统一请求(1.x 语义:事件本身即请求体)。
|
|
52
|
+
*
|
|
53
|
+
* **IP 解析优先级**(1.x `auth.getClientIP()` 直接读平台注入的 `TCB_SOURCE_IP`,
|
|
54
|
+
* 即 CloudBase 云函数的权威来源):
|
|
55
|
+
* 1. `TCB_SOURCE_IP`——上下文环境变量,`process.env` 兜底;
|
|
56
|
+
* 2. `x-real-ip` 请求头(HTTP 访问服务场景);
|
|
57
|
+
* 3. `x-forwarded-for` 首跳;
|
|
58
|
+
* 4. `event.requestContext.http.sourceIp`。
|
|
59
|
+
*
|
|
60
|
+
* ⚠️ 本链必须用 `||` 而非 `??`:链上各中间项都是字符串,**空串不是 nullish**。
|
|
61
|
+
* 曾写成 `a ?? (cond ? v : "") ?? b`——中间项在条件不成立时求值为 `""`,
|
|
62
|
+
* 把后面的来源全部短路掉,使 `sourceIp` 成为永远取不到的死代码。
|
|
63
|
+
*
|
|
64
|
+
* @param event 云函数事件
|
|
65
|
+
* @param context 云函数调用上下文(取 TCB_SOURCE_IP)
|
|
66
|
+
* @returns 内部统一请求
|
|
67
|
+
*/
|
|
68
|
+
export declare function toTkRequest(event: unknown, context?: TcbContextLike): TkRequest;
|
|
69
|
+
/**
|
|
70
|
+
* 内部统一响应 → 云函数返回体(网关层承载状态码/CORS)。
|
|
71
|
+
* @param response 内部统一响应
|
|
72
|
+
* @returns 云函数返回体
|
|
73
|
+
*/
|
|
74
|
+
export declare function fromTkResponse(response: TkResponse): Record<string, unknown>;
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/main.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* 创建 CloudBase 请求处理器(database / app 可注入供测试;缺省懒加载 TCB SDK)。
|
|
79
|
+
* @param options 注入项
|
|
80
|
+
* @returns 逐请求处理器(第二参数为云函数上下文,供 IP 解析与递归自调用取函数名)
|
|
81
|
+
*/
|
|
82
|
+
export declare function createTwikooFunc(options?: {
|
|
83
|
+
database?: CloudBaseDatabaseLike;
|
|
84
|
+
app?: TcbAppLike;
|
|
85
|
+
}): (event: unknown, context?: TcbContextLike) => Promise<Record<string, unknown>>;
|
|
86
|
+
/**
|
|
87
|
+
* 云函数入口(exports.main 导出名硬约束)。
|
|
88
|
+
* @param event 云函数事件
|
|
89
|
+
* @param context 云函数上下文(IP 解析 + 递归自调用取 function_name)
|
|
90
|
+
* @returns 云函数返回体
|
|
91
|
+
*/
|
|
92
|
+
export declare function main(event: unknown, context?: TcbContextLike): Promise<Record<string, unknown>>;
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/dispatch.d.ts
|
|
95
|
+
/**
|
|
96
|
+
* 创建 CloudBase 的派发实现。
|
|
97
|
+
* @param app CloudBase SDK app 实例(提供 callFunction)
|
|
98
|
+
* @param context 本次调用的云函数上下文(取 function_name)
|
|
99
|
+
* @returns POST_SUBMIT 派发实现
|
|
100
|
+
*/
|
|
101
|
+
export declare function createCloudBaseDispatcher(app: TcbAppLike, context?: TcbContextLike): PostSubmitDispatcher;
|
|
102
|
+
//#endregion
|
|
103
|
+
export type { TcbAppLike, TcbContextLike, TcbSdkStatic };
|
|
104
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { CloudBaseDatabaseLike, PostSubmitDispatcher, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/** `@cloudbase/node-sdk` 的 app 实例(`init()` 返回值) */
|
|
4
|
+
interface TcbAppLike {
|
|
5
|
+
/**
|
|
6
|
+
* 取数据库实例。
|
|
7
|
+
* @returns 数据库句柄
|
|
8
|
+
*/
|
|
9
|
+
database(): CloudBaseDatabaseLike;
|
|
10
|
+
/**
|
|
11
|
+
* 调用另一个云函数(POST_SUBMIT 递归自调用用)。
|
|
12
|
+
* @param options 目标函数名与事件数据
|
|
13
|
+
* @param config 调用配置(timeout:毫秒)
|
|
14
|
+
* @returns 调用结果
|
|
15
|
+
*/
|
|
16
|
+
callFunction(options: {
|
|
17
|
+
name: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
}, config?: {
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/** `@cloudbase/node-sdk` 静态形态(v2 具名导出 / v3 default 导出,形状一致) */
|
|
24
|
+
interface TcbSdkStatic {
|
|
25
|
+
/** 当前环境占位符(`init({ env })` 用) */
|
|
26
|
+
SYMBOL_CURRENT_ENV: symbol;
|
|
27
|
+
/**
|
|
28
|
+
* 初始化 SDK。
|
|
29
|
+
* @param options 初始化项(env:环境标识)
|
|
30
|
+
* @returns app 实例
|
|
31
|
+
*/
|
|
32
|
+
init(options: {
|
|
33
|
+
env: symbol;
|
|
34
|
+
}): TcbAppLike;
|
|
35
|
+
}
|
|
36
|
+
/** 云函数调用上下文(`exports.main` 第二参数) */
|
|
37
|
+
interface TcbContextLike {
|
|
38
|
+
/** 当前云函数名(递归自调用时需要) */
|
|
39
|
+
function_name?: string;
|
|
40
|
+
/**
|
|
41
|
+
* 平台注入的上下文环境变量(含 `TCB_SOURCE_IP`)。
|
|
42
|
+
* 与 `@cloudbase/node-sdk` 的 `getCloudbaseContext()` 取值来源一致。
|
|
43
|
+
*/
|
|
44
|
+
environment?: Record<string, string>;
|
|
45
|
+
/** 同上(SDK 兼容的另一个字段名,`getCloudbaseContext` 会先看 environment 再看它) */
|
|
46
|
+
environ?: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/transform.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* CloudBase 事件 → 内部统一请求(1.x 语义:事件本身即请求体)。
|
|
52
|
+
*
|
|
53
|
+
* **IP 解析优先级**(1.x `auth.getClientIP()` 直接读平台注入的 `TCB_SOURCE_IP`,
|
|
54
|
+
* 即 CloudBase 云函数的权威来源):
|
|
55
|
+
* 1. `TCB_SOURCE_IP`——上下文环境变量,`process.env` 兜底;
|
|
56
|
+
* 2. `x-real-ip` 请求头(HTTP 访问服务场景);
|
|
57
|
+
* 3. `x-forwarded-for` 首跳;
|
|
58
|
+
* 4. `event.requestContext.http.sourceIp`。
|
|
59
|
+
*
|
|
60
|
+
* ⚠️ 本链必须用 `||` 而非 `??`:链上各中间项都是字符串,**空串不是 nullish**。
|
|
61
|
+
* 曾写成 `a ?? (cond ? v : "") ?? b`——中间项在条件不成立时求值为 `""`,
|
|
62
|
+
* 把后面的来源全部短路掉,使 `sourceIp` 成为永远取不到的死代码。
|
|
63
|
+
*
|
|
64
|
+
* @param event 云函数事件
|
|
65
|
+
* @param context 云函数调用上下文(取 TCB_SOURCE_IP)
|
|
66
|
+
* @returns 内部统一请求
|
|
67
|
+
*/
|
|
68
|
+
export declare function toTkRequest(event: unknown, context?: TcbContextLike): TkRequest;
|
|
69
|
+
/**
|
|
70
|
+
* 内部统一响应 → 云函数返回体(网关层承载状态码/CORS)。
|
|
71
|
+
* @param response 内部统一响应
|
|
72
|
+
* @returns 云函数返回体
|
|
73
|
+
*/
|
|
74
|
+
export declare function fromTkResponse(response: TkResponse): Record<string, unknown>;
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/main.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* 创建 CloudBase 请求处理器(database / app 可注入供测试;缺省懒加载 TCB SDK)。
|
|
79
|
+
* @param options 注入项
|
|
80
|
+
* @returns 逐请求处理器(第二参数为云函数上下文,供 IP 解析与递归自调用取函数名)
|
|
81
|
+
*/
|
|
82
|
+
export declare function createTwikooFunc(options?: {
|
|
83
|
+
database?: CloudBaseDatabaseLike;
|
|
84
|
+
app?: TcbAppLike;
|
|
85
|
+
}): (event: unknown, context?: TcbContextLike) => Promise<Record<string, unknown>>;
|
|
86
|
+
/**
|
|
87
|
+
* 云函数入口(exports.main 导出名硬约束)。
|
|
88
|
+
* @param event 云函数事件
|
|
89
|
+
* @param context 云函数上下文(IP 解析 + 递归自调用取 function_name)
|
|
90
|
+
* @returns 云函数返回体
|
|
91
|
+
*/
|
|
92
|
+
export declare function main(event: unknown, context?: TcbContextLike): Promise<Record<string, unknown>>;
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/dispatch.d.ts
|
|
95
|
+
/**
|
|
96
|
+
* 创建 CloudBase 的派发实现。
|
|
97
|
+
* @param app CloudBase SDK app 实例(提供 callFunction)
|
|
98
|
+
* @param context 本次调用的云函数上下文(取 function_name)
|
|
99
|
+
* @returns POST_SUBMIT 派发实现
|
|
100
|
+
*/
|
|
101
|
+
export declare function createCloudBaseDispatcher(app: TcbAppLike, context?: TcbContextLike): PostSubmitDispatcher;
|
|
102
|
+
//#endregion
|
|
103
|
+
export type { TcbAppLike, TcbContextLike, TcbSdkStatic };
|
|
104
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _twikoojs_common = require("@twikoojs/common");
|
|
3
|
+
//#region src/transform.ts
|
|
4
|
+
/**
|
|
5
|
+
* 取平台注入的上下文环境变量。
|
|
6
|
+
*
|
|
7
|
+
* 与 `@cloudbase/node-sdk` 的 `getCloudbaseContext()` 同源:该函数从
|
|
8
|
+
* `context.environment` / `context.environ` 与 `process.env` 中按
|
|
9
|
+
* `TCB_CONTEXT_KEYS` 列表取值。
|
|
10
|
+
* @param context 云函数调用上下文
|
|
11
|
+
* @returns 上下文环境变量(无则为空对象)
|
|
12
|
+
*/
|
|
13
|
+
function contextEnvOf(context) {
|
|
14
|
+
return context?.environment ?? context?.environ ?? {};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* CloudBase 事件 → 内部统一请求(1.x 语义:事件本身即请求体)。
|
|
18
|
+
*
|
|
19
|
+
* **IP 解析优先级**(1.x `auth.getClientIP()` 直接读平台注入的 `TCB_SOURCE_IP`,
|
|
20
|
+
* 即 CloudBase 云函数的权威来源):
|
|
21
|
+
* 1. `TCB_SOURCE_IP`——上下文环境变量,`process.env` 兜底;
|
|
22
|
+
* 2. `x-real-ip` 请求头(HTTP 访问服务场景);
|
|
23
|
+
* 3. `x-forwarded-for` 首跳;
|
|
24
|
+
* 4. `event.requestContext.http.sourceIp`。
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ 本链必须用 `||` 而非 `??`:链上各中间项都是字符串,**空串不是 nullish**。
|
|
27
|
+
* 曾写成 `a ?? (cond ? v : "") ?? b`——中间项在条件不成立时求值为 `""`,
|
|
28
|
+
* 把后面的来源全部短路掉,使 `sourceIp` 成为永远取不到的死代码。
|
|
29
|
+
*
|
|
30
|
+
* @param event 云函数事件
|
|
31
|
+
* @param context 云函数调用上下文(取 TCB_SOURCE_IP)
|
|
32
|
+
* @returns 内部统一请求
|
|
33
|
+
*/
|
|
34
|
+
function toTkRequest(event, context) {
|
|
35
|
+
const raw = event && typeof event === "object" ? event : {};
|
|
36
|
+
const headers = raw.headers ?? {};
|
|
37
|
+
const lowerHeaders = {};
|
|
38
|
+
for (const [key, value] of Object.entries(headers)) lowerHeaders[key.toLowerCase()] = String(value);
|
|
39
|
+
const tcbSourceIp = contextEnvOf(context).TCB_SOURCE_IP ?? process.env.TCB_SOURCE_IP ?? "";
|
|
40
|
+
const forwarded = lowerHeaders["x-forwarded-for"];
|
|
41
|
+
const forwardedIp = forwarded ? forwarded.split(",")[0].trim() : "";
|
|
42
|
+
const sourceIp = raw.requestContext?.http?.sourceIp;
|
|
43
|
+
return {
|
|
44
|
+
method: "POST",
|
|
45
|
+
path: "/",
|
|
46
|
+
query: {},
|
|
47
|
+
body: raw,
|
|
48
|
+
headers: lowerHeaders,
|
|
49
|
+
ip: tcbSourceIp || lowerHeaders["x-real-ip"] || forwardedIp || sourceIp || "",
|
|
50
|
+
raw: event
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 内部统一响应 → 云函数返回体(网关层承载状态码/CORS)。
|
|
55
|
+
* @param response 内部统一响应
|
|
56
|
+
* @returns 云函数返回体
|
|
57
|
+
*/
|
|
58
|
+
function fromTkResponse(response) {
|
|
59
|
+
return response.body;
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/dispatch.ts
|
|
63
|
+
/**
|
|
64
|
+
* CloudBase 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。
|
|
65
|
+
*
|
|
66
|
+
* **1.x 语义对齐**:`src/server/function/twikoo/index.js` 的 `commentSubmit`
|
|
67
|
+
* 保存评论后执行
|
|
68
|
+
*
|
|
69
|
+
* ```js
|
|
70
|
+
* await app.callFunction({ name: context.function_name,
|
|
71
|
+
* data: { event: 'POST_SUBMIT', comment } }, { timeout: 300 })
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* ——用 **300ms 的极短 timeout** 把 `callFunction` 变成「异步」:请求已经发出,
|
|
75
|
+
* 本实例不再等待,被调用的实例在自己的时间预算里跑完垃圾检测与通知。这样
|
|
76
|
+
* 用户请求可以立即返回,也不会因为整条副作用链超出云函数超时上限而让
|
|
77
|
+
* 「评论已入库」变成客户端错误。
|
|
78
|
+
*
|
|
79
|
+
* **为什么放在适配器**:`callFunction` 是 CloudBase 专有 API,common 不应感知;
|
|
80
|
+
* 副作用链本身仍在 common 的 postSubmit 服务里。
|
|
81
|
+
*/
|
|
82
|
+
/** 递归调用的 timeout(毫秒)。1.x 取值,仅用于「不再等待」,不是副作用的上限 */
|
|
83
|
+
const CALL_TIMEOUT_MS = 300;
|
|
84
|
+
/**
|
|
85
|
+
* 创建 CloudBase 的派发实现。
|
|
86
|
+
* @param app CloudBase SDK app 实例(提供 callFunction)
|
|
87
|
+
* @param context 本次调用的云函数上下文(取 function_name)
|
|
88
|
+
* @returns POST_SUBMIT 派发实现
|
|
89
|
+
*/
|
|
90
|
+
function createCloudBaseDispatcher(app, context) {
|
|
91
|
+
return {
|
|
92
|
+
/**
|
|
93
|
+
* 递归自调用本云函数执行 POST_SUBMIT(不等副作用完成)。
|
|
94
|
+
* @param comment 已入库的评论
|
|
95
|
+
* @param ctx 当前请求上下文
|
|
96
|
+
*/
|
|
97
|
+
async dispatch(comment, ctx) {
|
|
98
|
+
const name = process.env.TCB_FUNCTION_NAME ?? context?.function_name;
|
|
99
|
+
if (!name) {
|
|
100
|
+
ctx.logger.warn("POST_SUBMIT 派发跳过:未取到云函数名(context.function_name / TCB_FUNCTION_NAME)");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await app.callFunction({
|
|
105
|
+
name,
|
|
106
|
+
data: {
|
|
107
|
+
event: "POST_SUBMIT",
|
|
108
|
+
comment,
|
|
109
|
+
/**
|
|
110
|
+
* 转发原请求头(被调用实例据此解析客户端 IP,语义与用户请求一致),
|
|
111
|
+
* 并附上内部派发令牌——POST_SUBMIT 处理器会校验它,拒绝外部调用。
|
|
112
|
+
*/
|
|
113
|
+
headers: {
|
|
114
|
+
...ctx.request.headers,
|
|
115
|
+
[_twikoojs_common.RECURSION_HEADER]: (0, _twikoojs_common.getRecursionToken)(ctx.config)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}, { timeout: CALL_TIMEOUT_MS });
|
|
119
|
+
} catch {
|
|
120
|
+
ctx.logger.verbose("POST_SUBMIT 已派出(本实例不再等待)");
|
|
121
|
+
}
|
|
122
|
+
} };
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/main.ts
|
|
126
|
+
/**
|
|
127
|
+
* twikoo-func 主逻辑(CloudBase 薄适配器)。
|
|
128
|
+
* 业务逻辑全部在 @twikoojs/common;平台核对:docs.cloudbase.net 云函数章节(查阅 2026-09-17)。
|
|
129
|
+
*
|
|
130
|
+
* 载荷转换见 `./transform.ts`,后置副作用派发见 `./dispatch.ts`,
|
|
131
|
+
* 平台 SDK 结构面见 `./types.ts`。
|
|
132
|
+
*/
|
|
133
|
+
/** CloudBase 平台能力:全能力*/
|
|
134
|
+
const cloudbaseCapabilities = (0, _twikoojs_common.defineCapabilities)({
|
|
135
|
+
mail: true,
|
|
136
|
+
domPurify: true,
|
|
137
|
+
ip2region: true,
|
|
138
|
+
akismet: true,
|
|
139
|
+
tencentTms: true,
|
|
140
|
+
imageUpload: true,
|
|
141
|
+
qqAvatar: true,
|
|
142
|
+
ai: true
|
|
143
|
+
});
|
|
144
|
+
/**
|
|
145
|
+
* 创建 CloudBase 请求处理器(database / app 可注入供测试;缺省懒加载 TCB SDK)。
|
|
146
|
+
* @param options 注入项
|
|
147
|
+
* @returns 逐请求处理器(第二参数为云函数上下文,供 IP 解析与递归自调用取函数名)
|
|
148
|
+
*/
|
|
149
|
+
function createTwikooFunc(options = {}) {
|
|
150
|
+
let app = options.app ?? null;
|
|
151
|
+
let database = options.database ?? null;
|
|
152
|
+
let sdkPromise = null;
|
|
153
|
+
/** 注入优先;否则动态加载 @cloudbase/node-sdk(SYMBOL_CURRENT_ENV) */
|
|
154
|
+
const getApp = async () => {
|
|
155
|
+
if (app) return app;
|
|
156
|
+
sdkPromise ??= (async () => {
|
|
157
|
+
const mod = await import(
|
|
158
|
+
/* @vite-ignore */
|
|
159
|
+
"@cloudbase/node-sdk"
|
|
160
|
+
);
|
|
161
|
+
const tcb = mod.default ?? mod;
|
|
162
|
+
return tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
|
|
163
|
+
})();
|
|
164
|
+
app = await sdkPromise;
|
|
165
|
+
return app;
|
|
166
|
+
};
|
|
167
|
+
/** 数据库实例(注入优先,否则取 app 的 database()) */
|
|
168
|
+
const getDatabase = async () => {
|
|
169
|
+
database ??= (await getApp()).database();
|
|
170
|
+
return database;
|
|
171
|
+
};
|
|
172
|
+
return async (event, context) => {
|
|
173
|
+
const request = toTkRequest(event, context);
|
|
174
|
+
const db = await getDatabase();
|
|
175
|
+
return fromTkResponse(await (0, _twikoojs_common.createHandler)({
|
|
176
|
+
request: {
|
|
177
|
+
/** 事件已在上游转换为统一请求 */
|
|
178
|
+
toTkRequest: () => request },
|
|
179
|
+
response: { fromTkResponse },
|
|
180
|
+
database: new _twikoojs_common.CloudBaseDatabase({ database: db }),
|
|
181
|
+
storage: {
|
|
182
|
+
challenges: {},
|
|
183
|
+
tokens: {}
|
|
184
|
+
},
|
|
185
|
+
mailer: {
|
|
186
|
+
/** CloudBase 侧不直发邮件(由 common 内部按需处理) */
|
|
187
|
+
send: async () => {} },
|
|
188
|
+
notifier: {
|
|
189
|
+
/** CloudBase 侧不直发推送(由 common 内部按需处理) */
|
|
190
|
+
notify: async () => {} },
|
|
191
|
+
postSubmit: createCloudBaseDispatcher(await getApp(), context),
|
|
192
|
+
capabilities: cloudbaseCapabilities
|
|
193
|
+
})(request));
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** 装配缓存(main 懒加载语义) */
|
|
197
|
+
let mainFn = null;
|
|
198
|
+
/**
|
|
199
|
+
* 云函数入口(exports.main 导出名硬约束)。
|
|
200
|
+
* @param event 云函数事件
|
|
201
|
+
* @param context 云函数上下文(IP 解析 + 递归自调用取 function_name)
|
|
202
|
+
* @returns 云函数返回体
|
|
203
|
+
*/
|
|
204
|
+
async function main(event, context) {
|
|
205
|
+
mainFn ??= createTwikooFunc();
|
|
206
|
+
return mainFn(event, context);
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
exports.createCloudBaseDispatcher = createCloudBaseDispatcher;
|
|
210
|
+
exports.createTwikooFunc = createTwikooFunc;
|
|
211
|
+
exports.fromTkResponse = fromTkResponse;
|
|
212
|
+
exports.main = main;
|
|
213
|
+
exports.toTkRequest = toTkRequest;
|
|
214
|
+
|
|
215
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["RECURSION_HEADER","getRecursionToken","defineCapabilities","createHandler","CloudBaseDatabase"],"sources":["../src/transform.ts","../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * CloudBase 载荷转换(事件 ↔ 内部统一请求/响应)。\n *\n * 从 `main.ts` 抽出:入口只负责 SDK 懒加载与端口装配,平台载荷的形状知识\n * (含 IP 解析优先级)集中在本文件。\n */\nimport type { TkRequest, TkResponse } from \"@twikoojs/common\";\nimport type { TcbContextLike } from \"./types\";\n\n/**\n * 取平台注入的上下文环境变量。\n *\n * 与 `@cloudbase/node-sdk` 的 `getCloudbaseContext()` 同源:该函数从\n * `context.environment` / `context.environ` 与 `process.env` 中按\n * `TCB_CONTEXT_KEYS` 列表取值。\n * @param context 云函数调用上下文\n * @returns 上下文环境变量(无则为空对象)\n */\nfunction contextEnvOf(context?: TcbContextLike): Record<string, string> {\n return context?.environment ?? context?.environ ?? {};\n}\n\n/**\n * CloudBase 事件 → 内部统一请求(1.x 语义:事件本身即请求体)。\n *\n * **IP 解析优先级**(1.x `auth.getClientIP()` 直接读平台注入的 `TCB_SOURCE_IP`,\n * 即 CloudBase 云函数的权威来源):\n * 1. `TCB_SOURCE_IP`——上下文环境变量,`process.env` 兜底;\n * 2. `x-real-ip` 请求头(HTTP 访问服务场景);\n * 3. `x-forwarded-for` 首跳;\n * 4. `event.requestContext.http.sourceIp`。\n *\n * ⚠️ 本链必须用 `||` 而非 `??`:链上各中间项都是字符串,**空串不是 nullish**。\n * 曾写成 `a ?? (cond ? v : \"\") ?? b`——中间项在条件不成立时求值为 `\"\"`,\n * 把后面的来源全部短路掉,使 `sourceIp` 成为永远取不到的死代码。\n *\n * @param event 云函数事件\n * @param context 云函数调用上下文(取 TCB_SOURCE_IP)\n * @returns 内部统一请求\n */\nexport function toTkRequest(event: unknown, context?: TcbContextLike): TkRequest {\n const raw = (event && typeof event === \"object\" ? event : {}) as Record<string, unknown>;\n const headers = (raw.headers ?? {}) as Record<string, string>;\n const lowerHeaders: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n lowerHeaders[key.toLowerCase()] = String(value);\n }\n const tcbSourceIp = contextEnvOf(context).TCB_SOURCE_IP ?? process.env.TCB_SOURCE_IP ?? \"\";\n const forwarded = lowerHeaders[\"x-forwarded-for\"];\n const forwardedIp = forwarded ? forwarded.split(\",\")[0].trim() : \"\";\n const sourceIp = (raw.requestContext as { http?: { sourceIp?: string } } | undefined)?.http\n ?.sourceIp;\n const ip = tcbSourceIp || lowerHeaders[\"x-real-ip\"] || forwardedIp || sourceIp || \"\";\n // 事件本身即请求体(1.x 语义)\n const body = raw as TkRequest[\"body\"];\n return { method: \"POST\", path: \"/\", query: {}, body, headers: lowerHeaders, ip, raw: event };\n}\n\n/**\n * 内部统一响应 → 云函数返回体(网关层承载状态码/CORS)。\n * @param response 内部统一响应\n * @returns 云函数返回体\n */\nexport function fromTkResponse(response: TkResponse): Record<string, unknown> {\n return response.body;\n}\n","/**\n * CloudBase 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **1.x 语义对齐**:`src/server/function/twikoo/index.js` 的 `commentSubmit`\n * 保存评论后执行\n *\n * ```js\n * await app.callFunction({ name: context.function_name,\n * data: { event: 'POST_SUBMIT', comment } }, { timeout: 300 })\n * ```\n *\n * ——用 **300ms 的极短 timeout** 把 `callFunction` 变成「异步」:请求已经发出,\n * 本实例不再等待,被调用的实例在自己的时间预算里跑完垃圾检测与通知。这样\n * 用户请求可以立即返回,也不会因为整条副作用链超出云函数超时上限而让\n * 「评论已入库」变成客户端错误。\n *\n * **为什么放在适配器**:`callFunction` 是 CloudBase 专有 API,common 不应感知;\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport { RECURSION_HEADER, getRecursionToken, type PostSubmitDispatcher } from \"@twikoojs/common\";\nimport type { TcbAppLike, TcbContextLike } from \"./types\";\n\n/** 递归调用的 timeout(毫秒)。1.x 取值,仅用于「不再等待」,不是副作用的上限 */\nconst CALL_TIMEOUT_MS = 300;\n\n/**\n * 创建 CloudBase 的派发实现。\n * @param app CloudBase SDK app 实例(提供 callFunction)\n * @param context 本次调用的云函数上下文(取 function_name)\n * @returns POST_SUBMIT 派发实现\n */\nexport function createCloudBaseDispatcher(\n app: TcbAppLike,\n context?: TcbContextLike,\n): PostSubmitDispatcher {\n return {\n /**\n * 递归自调用本云函数执行 POST_SUBMIT(不等副作用完成)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文\n */\n async dispatch(comment, ctx): Promise<void> {\n const name = process.env.TCB_FUNCTION_NAME ?? context?.function_name;\n if (!name) {\n ctx.logger.warn(\n \"POST_SUBMIT 派发跳过:未取到云函数名(context.function_name / TCB_FUNCTION_NAME)\",\n );\n return;\n }\n try {\n await app.callFunction(\n {\n name,\n data: {\n event: \"POST_SUBMIT\",\n comment,\n /**\n * 转发原请求头(被调用实例据此解析客户端 IP,语义与用户请求一致),\n * 并附上内部派发令牌——POST_SUBMIT 处理器会校验它,拒绝外部调用。\n */\n headers: {\n ...ctx.request.headers,\n [RECURSION_HEADER]: getRecursionToken(ctx.config),\n },\n },\n },\n { timeout: CALL_TIMEOUT_MS },\n );\n } catch {\n // 1.x 语义:300ms 超时是「异步化」手段——超时说明目标实例已在执行,属预期\n ctx.logger.verbose(\"POST_SUBMIT 已派出(本实例不再等待)\");\n }\n },\n };\n}\n","/**\n * twikoo-func 主逻辑(CloudBase 薄适配器)。\n * 业务逻辑全部在 @twikoojs/common;平台核对:docs.cloudbase.net 云函数章节(查阅 2026-09-17)。\n *\n * 载荷转换见 `./transform.ts`,后置副作用派发见 `./dispatch.ts`,\n * 平台 SDK 结构面见 `./types.ts`。\n */\nimport {\n Capabilities,\n CloudBaseDatabase,\n createHandler,\n defineCapabilities,\n type CloudBaseDatabaseLike,\n} from \"@twikoojs/common\";\nimport { fromTkResponse, toTkRequest } from \"./transform\";\nimport { createCloudBaseDispatcher } from \"./dispatch\";\nimport type { TcbAppLike, TcbContextLike, TcbSdkStatic } from \"./types\";\n\nexport { fromTkResponse, toTkRequest } from \"./transform\";\n\n/** CloudBase 平台能力:全能力*/\nconst cloudbaseCapabilities: Capabilities = defineCapabilities({\n mail: true,\n domPurify: true,\n ip2region: true,\n akismet: true,\n tencentTms: true,\n imageUpload: true,\n qqAvatar: true,\n ai: true,\n});\n\n/**\n * 创建 CloudBase 请求处理器(database / app 可注入供测试;缺省懒加载 TCB SDK)。\n * @param options 注入项\n * @returns 逐请求处理器(第二参数为云函数上下文,供 IP 解析与递归自调用取函数名)\n */\nexport function createTwikooFunc(\n options: { database?: CloudBaseDatabaseLike; app?: TcbAppLike } = {},\n): (event: unknown, context?: TcbContextLike) => Promise<Record<string, unknown>> {\n let app: TcbAppLike | null = options.app ?? null;\n let database: CloudBaseDatabaseLike | null = options.database ?? null;\n let sdkPromise: Promise<TcbAppLike> | null = null;\n /** 注入优先;否则动态加载 @cloudbase/node-sdk(SYMBOL_CURRENT_ENV) */\n const getApp = async (): Promise<TcbAppLike> => {\n if (app) return app;\n sdkPromise ??= (async () => {\n const specifier = \"@cloudbase/node-sdk\";\n // v3 起 ESM 命名导出不可用(命名空间仅 default/module.exports/version),\n // 统一回退取 default,兼容 v2 具名与 v3 default 两种形态。\n const mod = (await import(/* @vite-ignore */ specifier)) as unknown as {\n default?: TcbSdkStatic;\n } & TcbSdkStatic;\n const tcb: TcbSdkStatic = mod.default ?? mod;\n return tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });\n })();\n app = await sdkPromise;\n return app;\n };\n /** 数据库实例(注入优先,否则取 app 的 database()) */\n const getDatabase = async (): Promise<CloudBaseDatabaseLike> => {\n database ??= (await getApp()).database();\n return database;\n };\n return async (event: unknown, context?: TcbContextLike) => {\n const request = toTkRequest(event, context);\n const db = await getDatabase();\n const handler = createHandler({\n request: {\n /** 事件已在上游转换为统一请求 */\n toTkRequest: () => request,\n },\n response: { fromTkResponse },\n database: new CloudBaseDatabase({ database: db }),\n storage: { challenges: {} as never, tokens: {} as never },\n mailer: {\n /** CloudBase 侧不直发邮件(由 common 内部按需处理) */\n send: async () => {},\n },\n notifier: {\n /** CloudBase 侧不直发推送(由 common 内部按需处理) */\n notify: async () => {},\n },\n postSubmit: createCloudBaseDispatcher(await getApp(), context),\n capabilities: cloudbaseCapabilities,\n });\n return fromTkResponse(await handler(request));\n };\n}\n\n/** 装配缓存(main 懒加载语义) */\nlet mainFn:\n ((event: unknown, context?: TcbContextLike) => Promise<Record<string, unknown>>) | null = null;\n\n/**\n * 云函数入口(exports.main 导出名硬约束)。\n * @param event 云函数事件\n * @param context 云函数上下文(IP 解析 + 递归自调用取 function_name)\n * @returns 云函数返回体\n */\nexport async function main(\n event: unknown,\n context?: TcbContextLike,\n): Promise<Record<string, unknown>> {\n mainFn ??= createTwikooFunc();\n return mainFn(event, context);\n}\n"],"mappings":"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"}
|