@vectorx/functions-framework 0.0.0-beta-20260108133839 → 0.0.0-beta-20260320063305
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 +29 -1
- package/bin/rcb-ff.js +53 -1
- package/lib/config.js +2 -5
- package/lib/constants.js +1 -0
- package/lib/error.js +12 -4
- package/lib/framework.js +91 -31
- package/lib/function-loader.js +6 -2
- package/lib/function-registry.js +8 -0
- package/lib/function-wrapper.js +102 -9
- package/lib/index.js +1 -0
- package/lib/middlewares/index.js +3 -1
- package/lib/middlewares/middle-common-logger.js +3 -2
- package/lib/middlewares/middle-env-vars-injection.js +65 -0
- package/lib/middlewares/middle-open-gw-request.js +8 -1
- package/lib/request.js +6 -2
- package/lib/router.js +6 -0
- package/lib/server.js +19 -1
- package/lib/sse.js +8 -2
- package/lib/telemetry/langfuse.js +5 -3
- package/lib/utils/apm.config.js +2 -1
- package/lib/utils/console-intercept.js +1 -2
- package/lib/utils/helper.js +8 -6
- package/lib/wss.js +305 -0
- package/package.json +9 -6
- package/types/framework.d.ts +5 -0
- package/types/function-registry.d.ts +4 -0
- package/types/index.d.ts +1 -0
- package/types/middlewares/index.d.ts +1 -0
- package/types/middlewares/middle-env-vars-injection.d.ts +2 -0
- package/types/middlewares/middle-open-gw-request.d.ts +4 -1
- package/types/router.d.ts +4 -4
- package/types/server.d.ts +14 -0
- package/types/wss.d.ts +56 -0
package/README.md
CHANGED
|
@@ -136,6 +136,34 @@ npm test
|
|
|
136
136
|
npm run build
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
|
|
140
139
|
### 环境管理
|
|
141
140
|
读取 .env 文件,校验 env 文件的可用性,获取当前运行环境
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## rcb-ff 自动检测项目类型
|
|
144
|
+
|
|
145
|
+
当直接使用 `rcb-ff` 命令启动服务时,如果未指定 `--mode` 参数,会自动检测项目类型:
|
|
146
|
+
|
|
147
|
+
### 检测规则
|
|
148
|
+
|
|
149
|
+
1. **Agent 项目检测**:
|
|
150
|
+
- 如果存在 `project.config.json` 文件且包含 `agentId` 字段,则判定为 `agent` 项目
|
|
151
|
+
|
|
152
|
+
2. **Fun 项目检测**:
|
|
153
|
+
- 如果不存在 `project.config.json` 或 `project.config.json` 中不存在 `agentId` 字段
|
|
154
|
+
- 且存在 `agent-cloudbase-functions.json` 配置文件
|
|
155
|
+
- 则判定为 `fun` 项目
|
|
156
|
+
|
|
157
|
+
3. **默认行为**:
|
|
158
|
+
- 如果无法确定项目类型,默认使用 `agent` 模式(保持向后兼容)
|
|
159
|
+
|
|
160
|
+
### 使用示例
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# 自动检测项目类型(推荐)
|
|
164
|
+
rcb-ff --directory ./my-project
|
|
165
|
+
|
|
166
|
+
# 明确指定模式(覆盖自动检测)
|
|
167
|
+
rcb-ff --directory ./my-project --mode fun
|
|
168
|
+
rcb-ff --directory ./my-project --mode agent
|
|
169
|
+
```
|
package/bin/rcb-ff.js
CHANGED
|
@@ -7,6 +7,40 @@ const fs = require('fs');
|
|
|
7
7
|
const nodemon = require('nodemon');
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* 自动检测项目类型(agent 或 fun)
|
|
12
|
+
* 参考 fun-project-validator.ts 的检测逻辑
|
|
13
|
+
* @param {string} workDir 工作目录
|
|
14
|
+
* @returns {string} 'agent' | 'fun'
|
|
15
|
+
*/
|
|
16
|
+
function detectProjectMode(workDir) {
|
|
17
|
+
const projectConfigPath = path.join(workDir, 'project.config.json');
|
|
18
|
+
|
|
19
|
+
// 检查是否存在 project.config.json 且包含 agentId
|
|
20
|
+
if (fs.existsSync(projectConfigPath)) {
|
|
21
|
+
try {
|
|
22
|
+
const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, 'utf-8'));
|
|
23
|
+
if (projectConfig.agentId) {
|
|
24
|
+
// 存在 agentId,判定为 agent 项目
|
|
25
|
+
return 'agent';
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// project.config.json 解析失败,继续检测
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 检查是否存在 agent-cloudbase-functions.json
|
|
33
|
+
const functionsConfigPath = path.join(workDir, 'agent-cloudbase-functions.json');
|
|
34
|
+
if (fs.existsSync(functionsConfigPath)) {
|
|
35
|
+
// 不存在 project.config.json 或不存在 agentId,但存在 agent-cloudbase-functions.json
|
|
36
|
+
// 判定为 fun 项目
|
|
37
|
+
return 'fun';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 默认返回 agent(保持向后兼容)
|
|
41
|
+
return 'agent';
|
|
42
|
+
}
|
|
43
|
+
|
|
10
44
|
const program = new Command();
|
|
11
45
|
|
|
12
46
|
program
|
|
@@ -20,13 +54,22 @@ program
|
|
|
20
54
|
.option('--enable-cors', '启用 CORS')
|
|
21
55
|
.option('--allowed-origins <origins>', 'CORS 允许的域名,用逗号分隔', 'localhost,127.0.0.1')
|
|
22
56
|
.option('--config <path>', '指定配置文件路径', 'agent-cloudbase-functions.json')
|
|
57
|
+
.option('--mode <mode>', '运行模式: agent | fun(未指定时自动检测)')
|
|
23
58
|
.option('--enableRedLangfuse', '启用 Red Langfuse 上报')
|
|
59
|
+
.option('--verbose', '输出详细错误堆栈(用于排查问题)')
|
|
24
60
|
.option('-w, --watch', '启用文件监听模式,文件变化时自动重启服务');
|
|
25
61
|
|
|
26
62
|
program.parse();
|
|
27
63
|
|
|
28
64
|
const options = program.opts();
|
|
29
65
|
|
|
66
|
+
// 如果未明确指定 mode,则自动检测
|
|
67
|
+
if (!options.mode) {
|
|
68
|
+
const detectedMode = detectProjectMode(path.resolve(options.directory));
|
|
69
|
+
options.mode = detectedMode;
|
|
70
|
+
console.log(chalk.dim(`[自动检测] 项目类型: ${detectedMode}`));
|
|
71
|
+
}
|
|
72
|
+
|
|
30
73
|
async function startServer() {
|
|
31
74
|
try {
|
|
32
75
|
// 解析并验证目录路径
|
|
@@ -46,6 +89,7 @@ async function startServer() {
|
|
|
46
89
|
functionsConfigFile: path.resolve(directory, options.config),
|
|
47
90
|
enableCors: options.enableCors,
|
|
48
91
|
allowedOrigins: options.allowedOrigins.split(','),
|
|
92
|
+
mode: options.mode === 'fun' ? 'fun' : 'agent',
|
|
49
93
|
redLangfuseConfig: {
|
|
50
94
|
enable: Boolean(options.enableRedLangfuse),
|
|
51
95
|
},
|
|
@@ -74,6 +118,7 @@ async function startServer() {
|
|
|
74
118
|
// 使用独立的控制台实例打印启动信息
|
|
75
119
|
startupConsole.log('\n\n' + chalk.bold.cyan(' === RCB Functions Framework ==='));
|
|
76
120
|
startupConsole.log(`${chalk.green('✔')} ${chalk.bold('状态')}: ${chalk.green('运行中')}`);
|
|
121
|
+
startupConsole.log(`${chalk.green('✔')} ${chalk.bold('模式')}: ${chalk.yellow(frameworkOptions.mode)}`);
|
|
77
122
|
startupConsole.log(`${chalk.green('✔')} ${chalk.bold('端口')}: ${chalk.yellow(frameworkOptions.port)}`);
|
|
78
123
|
startupConsole.log(`${chalk.green('✔')} ${chalk.bold('URL')}: ${chalk.blue.underline(`http://localhost:${frameworkOptions.port}`)}`);
|
|
79
124
|
startupConsole.log(`${chalk.green('✔')} ${chalk.bold('目标函数')}: ${chalk.yellow(frameworkOptions.target)}`);
|
|
@@ -111,7 +156,14 @@ async function startServer() {
|
|
|
111
156
|
});
|
|
112
157
|
|
|
113
158
|
} catch (err) {
|
|
114
|
-
|
|
159
|
+
// 默认只输出友好错误信息(避免堆栈刷屏);需要排查时可通过 --verbose 输出完整堆栈
|
|
160
|
+
const message = (err && err.message) ? err.message : String(err);
|
|
161
|
+
console.error('启动服务器失败:');
|
|
162
|
+
console.error(message);
|
|
163
|
+
if (options.verbose) {
|
|
164
|
+
const stack = (err && err.stack) ? err.stack : '';
|
|
165
|
+
if (stack) console.error(stack);
|
|
166
|
+
}
|
|
115
167
|
process.exit(1);
|
|
116
168
|
}
|
|
117
169
|
}
|
package/lib/config.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getBaseUrl = getBaseUrl;
|
|
4
|
-
const
|
|
5
|
-
development: "https://agentbase-sandbox.xiaohongshu.com",
|
|
6
|
-
production: "https://agentbase.xiaohongshu.com",
|
|
7
|
-
};
|
|
4
|
+
const endpoints_1 = require("@vectorx/endpoints");
|
|
8
5
|
function getBaseUrl() {
|
|
9
|
-
return
|
|
6
|
+
return (0, endpoints_1.resolveUrl)("@vectorx/functions-framework", "baseUrl");
|
|
10
7
|
}
|
package/lib/constants.js
CHANGED
package/lib/error.js
CHANGED
|
@@ -30,18 +30,26 @@ function decorateErrorStack(err) {
|
|
|
30
30
|
return err.stack.split("\n").join("\n");
|
|
31
31
|
}
|
|
32
32
|
function newSysErr(err) {
|
|
33
|
-
return new AppError(ErrorCode.SYS_ERR, "System Error.", {
|
|
33
|
+
return new AppError(ErrorCode.SYS_ERR, "System Error.", {
|
|
34
|
+
cause: err,
|
|
35
|
+
});
|
|
34
36
|
}
|
|
35
37
|
function newBadRequestErr(err) {
|
|
36
38
|
const message = err instanceof Error ? err.message : err;
|
|
37
39
|
return new AppError(ErrorCode.BAD_REQUEST, message, { cause: err });
|
|
38
40
|
}
|
|
39
41
|
function newTimeoutErr() {
|
|
40
|
-
return new AppError(ErrorCode.TIMEOUT, "Request timeout", {
|
|
42
|
+
return new AppError(ErrorCode.TIMEOUT, "Request timeout", {
|
|
43
|
+
cause: new Error("Request timeout"),
|
|
44
|
+
});
|
|
41
45
|
}
|
|
42
46
|
function newTooManyRequestsErr(err) {
|
|
43
|
-
return new AppError(ErrorCode.TOO_MANY_REQUESTS, "Too many requests", {
|
|
47
|
+
return new AppError(ErrorCode.TOO_MANY_REQUESTS, "Too many requests", {
|
|
48
|
+
cause: err,
|
|
49
|
+
});
|
|
44
50
|
}
|
|
45
51
|
function newUnauthorizedErr(err) {
|
|
46
|
-
return new AppError(ErrorCode.UNAUTHORIZED, "Unauthorized", {
|
|
52
|
+
return new AppError(ErrorCode.UNAUTHORIZED, "Unauthorized", {
|
|
53
|
+
cause: err,
|
|
54
|
+
});
|
|
47
55
|
}
|
package/lib/framework.js
CHANGED
|
@@ -28,7 +28,9 @@ const apm_1 = require("./utils/apm");
|
|
|
28
28
|
const console_intercept_1 = require("./utils/console-intercept");
|
|
29
29
|
function getDependencyVersion(packageName, searchPath) {
|
|
30
30
|
try {
|
|
31
|
-
const packagePath = require.resolve(packageName, {
|
|
31
|
+
const packagePath = require.resolve(packageName, {
|
|
32
|
+
paths: [searchPath],
|
|
33
|
+
});
|
|
32
34
|
let currentPath = path_1.default.dirname(packagePath);
|
|
33
35
|
let packageJsonPath = path_1.default.join(currentPath, "package.json");
|
|
34
36
|
while (!(0, fs_1.existsSync)(packageJsonPath) && currentPath !== path_1.default.parse(currentPath).root) {
|
|
@@ -73,8 +75,9 @@ class AgentServerFramework {
|
|
|
73
75
|
}
|
|
74
76
|
startServer() {
|
|
75
77
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
-
const { functionsConfigFile } = this.options;
|
|
78
|
+
const { functionsConfigFile, mode = "agent" } = this.options;
|
|
77
79
|
let cloudFunctionConfig;
|
|
80
|
+
let projectConfig;
|
|
78
81
|
if (!process.env.OPEN_PLATFORM_STAGE) {
|
|
79
82
|
process.stdout.write(chalk_1.default.yellow(`未设置 OPEN_PLATFORM_STAGE 环境变量,使用默认值: development\n`));
|
|
80
83
|
process.env.OPEN_PLATFORM_STAGE = "development";
|
|
@@ -84,10 +87,14 @@ class AgentServerFramework {
|
|
|
84
87
|
const stageEnvFilePath = path_1.default.join(process.cwd(), `.env.${stage}`);
|
|
85
88
|
let dotEnvContent;
|
|
86
89
|
if ((0, fs_1.existsSync)(defaultEnvFilePath)) {
|
|
87
|
-
dotEnvContent = dotenv_1.default.config({
|
|
90
|
+
dotEnvContent = dotenv_1.default.config({
|
|
91
|
+
path: path_1.default.resolve(defaultEnvFilePath),
|
|
92
|
+
});
|
|
88
93
|
}
|
|
89
94
|
else if ((0, fs_1.existsSync)(stageEnvFilePath)) {
|
|
90
|
-
dotEnvContent = dotenv_1.default.config({
|
|
95
|
+
dotEnvContent = dotenv_1.default.config({
|
|
96
|
+
path: path_1.default.resolve(stageEnvFilePath),
|
|
97
|
+
});
|
|
91
98
|
}
|
|
92
99
|
if (dotEnvContent && dotEnvContent.parsed) {
|
|
93
100
|
const envKeys = Object.keys(dotEnvContent.parsed);
|
|
@@ -100,9 +107,27 @@ class AgentServerFramework {
|
|
|
100
107
|
}
|
|
101
108
|
});
|
|
102
109
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
if (mode === "agent") {
|
|
111
|
+
({ projectConfig, cloudFunctionConfig } = this.loadAgentModeConfigs(functionsConfigFile));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
({ projectConfig, cloudFunctionConfig } = this.loadFunModeConfigs(functionsConfigFile));
|
|
115
|
+
}
|
|
116
|
+
this.router = new router_1.Router(cloudFunctionConfig);
|
|
117
|
+
const server = (0, server_1.createServer)(this.options, this.router, projectConfig);
|
|
118
|
+
return server;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
getRegisteredFunction(fnName) {
|
|
122
|
+
const fnId = (0, function_registry_1.buildFnId)("@fn", fnName);
|
|
123
|
+
return (0, function_registry_1.getRegisteredFunction)(fnId);
|
|
124
|
+
}
|
|
125
|
+
loadAgentModeConfigs(functionsConfigFile) {
|
|
126
|
+
const projectConfigPath = path_1.default.join(process.cwd(), "project.config.json");
|
|
127
|
+
if (!(0, fs_1.existsSync)(projectConfigPath)) {
|
|
128
|
+
const funConfigPath = path_1.default.join(process.cwd(), "agent-cloudbase-functions.json");
|
|
129
|
+
const maybeFunProject = (0, fs_1.existsSync)(funConfigPath);
|
|
130
|
+
throw new Error(`
|
|
106
131
|
❌ 项目配置文件未找到: ${projectConfigPath}
|
|
107
132
|
|
|
108
133
|
请确保:
|
|
@@ -113,11 +138,23 @@ class AgentServerFramework {
|
|
|
113
138
|
"version": "0.0.1",
|
|
114
139
|
"desc": "发布示例",
|
|
115
140
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
141
|
+
${maybeFunProject
|
|
142
|
+
? `
|
|
143
|
+
💡 诊断提示:
|
|
144
|
+
- 检测到当前目录存在云函数配置文件(agent-cloudbase-functions.json)
|
|
145
|
+
- 这可能是一个“云函数(fun)项目”目录,但你使用了“智能体(agent)模式”启动
|
|
146
|
+
|
|
147
|
+
建议你:
|
|
148
|
+
- 改用 fun 模式启动(例如:rcb-ff --mode fun ... 或使用 rcb fun dev)
|
|
149
|
+
- 或切换到正确的 Agent 项目目录后再启动
|
|
150
|
+
|
|
151
|
+
参考文档:https://miniapp.xiaohongshu.com/doc/DC185638
|
|
152
|
+
`
|
|
153
|
+
: ""}`);
|
|
154
|
+
}
|
|
155
|
+
const projectConfig = require(projectConfigPath);
|
|
156
|
+
if (!(0, fs_1.existsSync)(functionsConfigFile)) {
|
|
157
|
+
throw new Error(`
|
|
121
158
|
❌ 配置文件未找到: ${functionsConfigFile}
|
|
122
159
|
|
|
123
160
|
请确保:
|
|
@@ -135,13 +172,42 @@ class AgentServerFramework {
|
|
|
135
172
|
]
|
|
136
173
|
}
|
|
137
174
|
`);
|
|
175
|
+
}
|
|
176
|
+
const cloudFunctionConfig = this.loadAndRegisterFunctions(functionsConfigFile, "agent");
|
|
177
|
+
return { projectConfig, cloudFunctionConfig };
|
|
178
|
+
}
|
|
179
|
+
loadFunModeConfigs(functionsConfigFile) {
|
|
180
|
+
if (!(0, fs_1.existsSync)(functionsConfigFile)) {
|
|
181
|
+
throw new Error(`
|
|
182
|
+
❌ 函数配置文件未找到: ${functionsConfigFile}
|
|
183
|
+
|
|
184
|
+
请确保:
|
|
185
|
+
1. 配置文件 'agent-cloudbase-functions.json' 存在于项目根目录
|
|
186
|
+
2. 配置文件格式正确,且 functions 字段已正确配置
|
|
187
|
+
`);
|
|
188
|
+
}
|
|
189
|
+
const cloudFunctionConfig = this.loadAndRegisterFunctions(functionsConfigFile, "fun");
|
|
190
|
+
const projectConfig = {
|
|
191
|
+
agentId: cloudFunctionConfig.agentId || "local-fun",
|
|
192
|
+
version: "0.0.0-local",
|
|
193
|
+
desc: "Local functions development (fun mode)",
|
|
194
|
+
name: "local-fun",
|
|
195
|
+
"@vectorx/agent-runtime": getDependencyVersion("@vectorx/agent-runtime", process.cwd()) || "",
|
|
196
|
+
"@vectorx/functions-framework": getDependencyVersion("@vectorx/functions-framework", process.cwd()) || "",
|
|
197
|
+
};
|
|
198
|
+
return { projectConfig, cloudFunctionConfig };
|
|
199
|
+
}
|
|
200
|
+
loadAndRegisterFunctions(functionsConfigFile, mode) {
|
|
201
|
+
try {
|
|
202
|
+
const cloudFunctionConfig = (0, function_loader_1.loadFunctionsConfig)(functionsConfigFile);
|
|
203
|
+
const fns = cloudFunctionConfig.functions || [];
|
|
204
|
+
if (!Array.isArray(fns) || fns.length === 0) {
|
|
205
|
+
throw new Error("functions 配置为空,请至少配置一个函数");
|
|
138
206
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!mainFn) {
|
|
144
|
-
throw new Error(`
|
|
207
|
+
for (const fn of fns) {
|
|
208
|
+
const mainFn = (0, function_loader_1.loadFunction)(cloudFunctionConfig.functionsRoot, fn);
|
|
209
|
+
if (!mainFn) {
|
|
210
|
+
throw new Error(`
|
|
145
211
|
❌ 函数加载失败: ${fn.name}
|
|
146
212
|
|
|
147
213
|
请检查:
|
|
@@ -149,14 +215,14 @@ class AgentServerFramework {
|
|
|
149
215
|
2. 源文件是否存在: ${fn.source}
|
|
150
216
|
3. 函数是否正确导出
|
|
151
217
|
`);
|
|
152
|
-
}
|
|
153
|
-
(0, function_registry_1.registerFunction)(fn.name, mainFn);
|
|
154
218
|
}
|
|
155
|
-
|
|
219
|
+
(0, function_registry_1.registerFunction)(fn.name, mainFn);
|
|
156
220
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
221
|
+
return cloudFunctionConfig;
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
throw new Error(`
|
|
225
|
+
❌ 函数配置加载失败(${mode} 模式)
|
|
160
226
|
|
|
161
227
|
错误详情: ${err}
|
|
162
228
|
|
|
@@ -167,13 +233,7 @@ class AgentServerFramework {
|
|
|
167
233
|
|
|
168
234
|
配置文件路径: ${functionsConfigFile}
|
|
169
235
|
`);
|
|
170
|
-
|
|
171
|
-
const server = (0, server_1.createServer)(this.options, this.router, projectConfig);
|
|
172
|
-
return server;
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
getRegisteredFunction(fnName) {
|
|
176
|
-
return (0, function_registry_1.getRegisteredFunction)(fnName);
|
|
236
|
+
}
|
|
177
237
|
}
|
|
178
238
|
}
|
|
179
239
|
function createAgentServerFramework(options) {
|
package/lib/function-loader.js
CHANGED
|
@@ -30,7 +30,10 @@ function loadFunctionsConfig(configPath) {
|
|
|
30
30
|
throw new Error(`Route '${fn.triggerPath}->${fn.name}' conflict with '${fn.triggerPath}->${existsTriggerFnName}', please check your functions config`);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
config.routes.push({
|
|
33
|
+
config.routes.push({
|
|
34
|
+
functionName: fn.name,
|
|
35
|
+
path: fn.triggerPath,
|
|
36
|
+
});
|
|
34
37
|
routeMaps.set(fn.triggerPath, fn.name);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -45,7 +48,8 @@ function loadFunction(functionsRoot, functionConfig) {
|
|
|
45
48
|
if (!functionConfig.source) {
|
|
46
49
|
functionConfig.source = exports.DEFAULT_FN_NAME;
|
|
47
50
|
}
|
|
48
|
-
const
|
|
51
|
+
const directory = functionConfig.directory || ".";
|
|
52
|
+
const indexPath = (0, path_1.join)(functionsRoot, directory, functionConfig.source);
|
|
49
53
|
if (!(0, fs_1.existsSync)(indexPath)) {
|
|
50
54
|
throw new Error(`Function entry file not found at: ${indexPath}, functionConfig: ${JSON.stringify(functionConfig)}`);
|
|
51
55
|
}
|
package/lib/function-registry.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buildFnId = buildFnId;
|
|
4
4
|
exports.registerFunction = registerFunction;
|
|
5
5
|
exports.getRegisteredFunction = getRegisteredFunction;
|
|
6
|
+
exports.getAllRegisteredFunctions = getAllRegisteredFunctions;
|
|
6
7
|
exports.clearRegistry = clearRegistry;
|
|
7
8
|
const functionRegistry = new Map();
|
|
8
9
|
function buildFnId(fnName, functionName) {
|
|
@@ -15,6 +16,13 @@ function registerFunction(fnName, fn) {
|
|
|
15
16
|
function getRegisteredFunction(fnId) {
|
|
16
17
|
return functionRegistry.get(fnId);
|
|
17
18
|
}
|
|
19
|
+
function getAllRegisteredFunctions() {
|
|
20
|
+
const result = [];
|
|
21
|
+
functionRegistry.forEach((fn, fnId) => {
|
|
22
|
+
result.push({ name: fnId, userFunction: fn });
|
|
23
|
+
});
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
18
26
|
function clearRegistry() {
|
|
19
27
|
functionRegistry.clear();
|
|
20
28
|
}
|
package/lib/function-wrapper.js
CHANGED
|
@@ -50,10 +50,62 @@ const request_1 = require("./request");
|
|
|
50
50
|
const sse_1 = require("./sse");
|
|
51
51
|
const unified_responder_1 = require("./unified-responder");
|
|
52
52
|
const helper_1 = require("./utils/helper");
|
|
53
|
+
const isPlainObject = (val) => {
|
|
54
|
+
if (!val || typeof val !== "object")
|
|
55
|
+
return false;
|
|
56
|
+
const proto = Object.getPrototypeOf(val);
|
|
57
|
+
return proto === Object.prototype || proto === null;
|
|
58
|
+
};
|
|
59
|
+
const parseCookies = (cookieHeader) => {
|
|
60
|
+
const cookies = {};
|
|
61
|
+
if (!cookieHeader || typeof cookieHeader !== "string")
|
|
62
|
+
return cookies;
|
|
63
|
+
const parts = cookieHeader.split(";");
|
|
64
|
+
for (const part of parts) {
|
|
65
|
+
const s = part.trim();
|
|
66
|
+
if (!s)
|
|
67
|
+
continue;
|
|
68
|
+
const idx = s.indexOf("=");
|
|
69
|
+
if (idx === -1)
|
|
70
|
+
continue;
|
|
71
|
+
const key = s.slice(0, idx).trim();
|
|
72
|
+
const val = s.slice(idx + 1).trim();
|
|
73
|
+
if (!key)
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
cookies[key] = decodeURIComponent(val);
|
|
77
|
+
}
|
|
78
|
+
catch (_a) {
|
|
79
|
+
cookies[key] = val;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return cookies;
|
|
83
|
+
};
|
|
84
|
+
const normalizeQuery = (query) => {
|
|
85
|
+
if (!query || typeof query !== "object")
|
|
86
|
+
return {};
|
|
87
|
+
return Object.assign({}, query);
|
|
88
|
+
};
|
|
89
|
+
const normalizeHeaders = (headers) => {
|
|
90
|
+
if (!headers || typeof headers !== "object")
|
|
91
|
+
return {};
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
94
|
+
out[String(k).toLowerCase()] = v;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
};
|
|
98
|
+
const resolveTrigger = (ctx) => {
|
|
99
|
+
var _a, _b;
|
|
100
|
+
const fromHeader = String(((_b = (_a = ctx.request) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b["xhs-cloud-trigger"]) || "").toLowerCase();
|
|
101
|
+
if (fromHeader === "online" || fromHeader === "ide_debug" || fromHeader === "console_debug")
|
|
102
|
+
return fromHeader;
|
|
103
|
+
return "online";
|
|
104
|
+
};
|
|
53
105
|
const wrapEventFunction = (execute, projectConfig) => {
|
|
54
106
|
return (ctx, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
-
var _a;
|
|
56
|
-
let
|
|
107
|
+
var _a, _b, _c, _d, _e;
|
|
108
|
+
let params;
|
|
57
109
|
if (ctx.request.files) {
|
|
58
110
|
const filesWithBuffer = {};
|
|
59
111
|
const fs = yield Promise.resolve().then(() => __importStar(require("fs")));
|
|
@@ -79,21 +131,40 @@ const wrapEventFunction = (execute, projectConfig) => {
|
|
|
79
131
|
})));
|
|
80
132
|
filesWithBuffer[key] = Array.isArray(fileOrFiles) ? processedFiles : processedFiles[0];
|
|
81
133
|
}
|
|
82
|
-
|
|
134
|
+
const fields = isPlainObject(ctx.request.body) ? ctx.request.body : {};
|
|
135
|
+
params = Object.assign(Object.assign({}, fields), filesWithBuffer);
|
|
83
136
|
}
|
|
84
|
-
else if (ctx.
|
|
85
|
-
|
|
137
|
+
else if (ctx.method === "GET" || ctx.method === "HEAD") {
|
|
138
|
+
params = ctx.query || {};
|
|
86
139
|
}
|
|
87
140
|
else {
|
|
88
|
-
|
|
141
|
+
params = (_a = ctx.request.body) !== null && _a !== void 0 ? _a : ctx.rawBody;
|
|
89
142
|
}
|
|
143
|
+
if (params === undefined || params === null) {
|
|
144
|
+
params = {};
|
|
145
|
+
}
|
|
146
|
+
const event = { params };
|
|
90
147
|
let executeDone = false;
|
|
91
148
|
let hasSwitchSSEMode = false;
|
|
149
|
+
const headers = normalizeHeaders((_b = ctx.request) === null || _b === void 0 ? void 0 : _b.headers);
|
|
150
|
+
const httpMethod = ctx.method;
|
|
151
|
+
const path = ctx.path;
|
|
152
|
+
const url = ctx.url;
|
|
153
|
+
const query = normalizeQuery(ctx.query);
|
|
154
|
+
const cookies = parseCookies(headers.cookie);
|
|
155
|
+
const trigger = resolveTrigger(ctx);
|
|
92
156
|
const context = {
|
|
93
157
|
ctxId: ctx.state.eventID,
|
|
94
158
|
eventID: ctx.state.eventID,
|
|
95
159
|
eventType: ctx.state.eventType,
|
|
96
160
|
timestamp: ctx.state.timestamp,
|
|
161
|
+
headers,
|
|
162
|
+
httpMethod,
|
|
163
|
+
path,
|
|
164
|
+
url,
|
|
165
|
+
query,
|
|
166
|
+
cookies,
|
|
167
|
+
trigger,
|
|
97
168
|
httpContext: ctx.httpContext,
|
|
98
169
|
baseUrl: (0, config_1.getBaseUrl)(),
|
|
99
170
|
request: new request_1.Request({
|
|
@@ -102,11 +173,26 @@ const wrapEventFunction = (execute, projectConfig) => {
|
|
|
102
173
|
restrictedMethods: ["get", "post", "upload", "download"],
|
|
103
174
|
defaultHeaders: {
|
|
104
175
|
"agent-id": projectConfig.agentId,
|
|
105
|
-
"open-id": ctx.request.headers["open-id"],
|
|
176
|
+
"open-id": ((_d = (_c = ctx.request) === null || _c === void 0 ? void 0 : _c.headers) === null || _d === void 0 ? void 0 : _d["open-id"]) || "UNKNOWN",
|
|
106
177
|
"x-open-agent-trace-id": ctx.state.eventID,
|
|
107
178
|
},
|
|
108
179
|
}),
|
|
109
180
|
logger: logger_1.functionsLogger,
|
|
181
|
+
set: (field, value) => {
|
|
182
|
+
ctx.set(field, value);
|
|
183
|
+
},
|
|
184
|
+
remove: (field) => {
|
|
185
|
+
ctx.remove(field);
|
|
186
|
+
},
|
|
187
|
+
status: (code) => {
|
|
188
|
+
ctx.status = code;
|
|
189
|
+
},
|
|
190
|
+
setCookie: (name, value, options) => {
|
|
191
|
+
ctx.cookies.set(name, value, options);
|
|
192
|
+
},
|
|
193
|
+
clearCookie: (name, options) => {
|
|
194
|
+
ctx.cookies.set(name, null, Object.assign(Object.assign({}, (options || {})), { expires: new Date(0), maxAge: 0 }));
|
|
195
|
+
},
|
|
110
196
|
sse: () => {
|
|
111
197
|
var _a;
|
|
112
198
|
const sseInstance = (_a = ctx.sse) === null || _a === void 0 ? void 0 : _a.call(ctx);
|
|
@@ -127,12 +213,19 @@ const wrapEventFunction = (execute, projectConfig) => {
|
|
|
127
213
|
eventID: context.eventID,
|
|
128
214
|
eventType: context.eventType,
|
|
129
215
|
timestamp: context.timestamp,
|
|
216
|
+
headers: context.headers,
|
|
217
|
+
httpMethod: context.httpMethod,
|
|
218
|
+
path: context.path,
|
|
219
|
+
url: context.url,
|
|
220
|
+
query: context.query,
|
|
221
|
+
cookies: context.cookies,
|
|
222
|
+
trigger: context.trigger,
|
|
130
223
|
httpContext: context.httpContext,
|
|
131
224
|
baseUrl: context.baseUrl,
|
|
132
225
|
request: context.request,
|
|
133
226
|
};
|
|
134
227
|
(0, helper_1.deepFreeze)(contextToFreeze);
|
|
135
|
-
const frozenContext = Object.assign(Object.assign({}, contextToFreeze), { logger: context.logger, sse: context.sse });
|
|
228
|
+
const frozenContext = Object.assign(Object.assign({}, contextToFreeze), { logger: context.logger, set: context.set, remove: context.remove, status: context.status, setCookie: context.setCookie, clearCookie: context.clearCookie, sse: context.sse });
|
|
136
229
|
ctx.state.event = event;
|
|
137
230
|
ctx.state.contextInjected = frozenContext;
|
|
138
231
|
let result;
|
|
@@ -142,7 +235,7 @@ const wrapEventFunction = (execute, projectConfig) => {
|
|
|
142
235
|
catch (e) {
|
|
143
236
|
ctx.state.error = e;
|
|
144
237
|
if (hasSwitchSSEMode) {
|
|
145
|
-
const sse = (
|
|
238
|
+
const sse = (_e = ctx.sse) === null || _e === void 0 ? void 0 : _e.call(ctx);
|
|
146
239
|
if (sse && !sse.isClosed) {
|
|
147
240
|
const errorEvent = {
|
|
148
241
|
event: "error",
|
package/lib/index.js
CHANGED
|
@@ -20,5 +20,6 @@ __exportStar(require("./function-loader"), exports);
|
|
|
20
20
|
__exportStar(require("./function-registry"), exports);
|
|
21
21
|
__exportStar(require("./sse"), exports);
|
|
22
22
|
__exportStar(require("./server"), exports);
|
|
23
|
+
__exportStar(require("./wss"), exports);
|
|
23
24
|
__exportStar(require("./logger"), exports);
|
|
24
25
|
__exportStar(require("./async-context"), exports);
|
package/lib/middlewares/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.apmMiddleware = exports.openGwRequestMiddleware = exports.logsQueryMiddleware = exports.loggerMiddleware = exports.functionRouteMiddleware = exports.eventIdMiddleware = exports.contextInjectionMiddleware = exports.asyncContextMiddleware = void 0;
|
|
3
|
+
exports.apmMiddleware = exports.openGwRequestMiddleware = exports.logsQueryMiddleware = exports.loggerMiddleware = exports.functionRouteMiddleware = exports.eventIdMiddleware = exports.envVarsInjectionMiddleware = exports.contextInjectionMiddleware = exports.asyncContextMiddleware = void 0;
|
|
4
4
|
var middle_async_context_1 = require("./middle-async-context");
|
|
5
5
|
Object.defineProperty(exports, "asyncContextMiddleware", { enumerable: true, get: function () { return middle_async_context_1.asyncContextMiddleware; } });
|
|
6
6
|
var middle_context_injection_1 = require("./middle-context-injection");
|
|
7
7
|
Object.defineProperty(exports, "contextInjectionMiddleware", { enumerable: true, get: function () { return middle_context_injection_1.contextInjectionMiddleware; } });
|
|
8
|
+
var middle_env_vars_injection_1 = require("./middle-env-vars-injection");
|
|
9
|
+
Object.defineProperty(exports, "envVarsInjectionMiddleware", { enumerable: true, get: function () { return middle_env_vars_injection_1.envVarsInjectionMiddleware; } });
|
|
8
10
|
var middle_event_id_1 = require("./middle-event-id");
|
|
9
11
|
Object.defineProperty(exports, "eventIdMiddleware", { enumerable: true, get: function () { return middle_event_id_1.eventIdMiddleware; } });
|
|
10
12
|
var middle_function_route_1 = require("./middle-function-route");
|
|
@@ -25,7 +25,7 @@ function loggerMiddleware() {
|
|
|
25
25
|
yield next();
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
process.stdout.write(chalk_1.default.greenBright(`▶️ ${chalk_1.default.gray(`[${new Date().toLocaleString()} - ${ctx.method}:${eventId}]`)} ${ctx.url}`));
|
|
28
|
+
process.stdout.write(chalk_1.default.greenBright(`▶️ ${chalk_1.default.gray(`[${new Date().toLocaleString()} - ${ctx.method}:${eventId}]`)} ${ctx.url}\n`));
|
|
29
29
|
try {
|
|
30
30
|
const accessLog = {
|
|
31
31
|
type: "HTTP_ACCESS",
|
|
@@ -44,6 +44,7 @@ function loggerMiddleware() {
|
|
|
44
44
|
finally {
|
|
45
45
|
const endTime = Date.now();
|
|
46
46
|
const duration = endTime - startTime;
|
|
47
|
+
const contentType = ctx.get("content-type") || "-";
|
|
47
48
|
const accessLog = {
|
|
48
49
|
type: "HTTP_ACCESS",
|
|
49
50
|
method: ctx.method,
|
|
@@ -65,7 +66,7 @@ function loggerMiddleware() {
|
|
|
65
66
|
asyncContext.userCodeLogsFlushTimer = undefined;
|
|
66
67
|
}
|
|
67
68
|
logger_1.functionsLogger.logUserCodelog();
|
|
68
|
-
process.stdout.write(chalk_1.default.greenBright(`▶️ ${chalk_1.default.gray(`[${new Date().toLocaleString()} - ${eventId.slice(0, -10)}]`)} - ${duration}ms\n`));
|
|
69
|
+
process.stdout.write(chalk_1.default.greenBright(`▶️ ${chalk_1.default.gray(`[${new Date().toLocaleString()} - ${eventId.slice(0, -10)}]`)} ${chalk_1.default.bold(`${ctx.method} ${ctx.path}`)} ${chalk_1.default.bold(`[${contentType}]`)} - ${duration}ms\n`));
|
|
69
70
|
process.stdout.write(chalk_1.default.greenBright(`\n`));
|
|
70
71
|
}
|
|
71
72
|
});
|