@twikoojs/aws-lambda 2.0.0-beta.2 → 2.0.0-beta.4
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/dist/index.js +29 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +30 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -81,6 +81,9 @@ async dispatch(comment, ctx) {
|
|
|
81
81
|
*
|
|
82
82
|
* 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以
|
|
83
83
|
* 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。
|
|
84
|
+
*
|
|
85
|
+
* **异常不外抛**(1.x 语义):请求处理中的任何异常都转成 HTTP 200 + `code: 1000`。
|
|
86
|
+
* 1.x 的 Lambda 函数是复用 vercel 核心的薄壳,而核心函数从不外抛。
|
|
84
87
|
*/
|
|
85
88
|
/** AWS Lambda 平台能力:全能力*/
|
|
86
89
|
const lambdaCapabilities = _twikoojs_common.FULL_CAPABILITIES;
|
|
@@ -134,25 +137,38 @@ function fromTkResponse(tkRes) {
|
|
|
134
137
|
*/
|
|
135
138
|
function createLambdaFunc(options = {}) {
|
|
136
139
|
let database = options.database ?? null;
|
|
137
|
-
/**
|
|
138
|
-
|
|
140
|
+
/**
|
|
141
|
+
* 懒建数据库实例(inject 优先)。**不在这里 init**:pipeline 自己会 init 且在同一处 catch 内,
|
|
142
|
+
* 提前 init 会让「连不上数据库」在 pipeline 之外抛错(1.x 连不上也是 200 + code 1000)。
|
|
143
|
+
*/
|
|
144
|
+
const getDatabase = () => {
|
|
139
145
|
database ??= new _twikoojs_common.MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? "" });
|
|
140
|
-
await database.init();
|
|
141
146
|
return database;
|
|
142
147
|
};
|
|
143
148
|
return async (event) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
try {
|
|
150
|
+
const request = toTkRequest(event);
|
|
151
|
+
return fromTkResponse(await (0, _twikoojs_common.createHandler)((0, _twikoojs_common.scaffoldAdapters)({
|
|
152
|
+
request: {
|
|
153
|
+
/** 请求恒等透传(归一化见 toTkRequest) */
|
|
148
154
|
toTkRequest: () => request },
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
response: {
|
|
156
|
+
/** TkResponse 恒等透传(序列化见 fromTkResponse) */
|
|
151
157
|
fromTkResponse: (r) => r },
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
database: getDatabase(),
|
|
159
|
+
capabilities: lambdaCapabilities,
|
|
160
|
+
postSubmit: lambdaPostSubmitDispatcher
|
|
161
|
+
}))(request));
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return {
|
|
164
|
+
statusCode: 200,
|
|
165
|
+
headers: { "Content-Type": "application/json" },
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
code: _twikoojs_common.RES_CODE.FAIL,
|
|
168
|
+
message: e instanceof Error ? e.message : String(e)
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
}
|
|
156
172
|
};
|
|
157
173
|
}
|
|
158
174
|
/** 装配缓存(handler 懒加载语义) */
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["RECURSION_HEADER","getRecursionToken","FULL_CAPABILITIES","MongoDatabase","createHandler","scaffoldAdapters"],"sources":["../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **与 1.x 的差异(修正)**:1.x 的 aws-lambda 适配器是\n * `require('twikoo-vercel')` 纯转发壳,继承了 vercel 的 HTTP 递归——而 vercel\n * 取的是 `VERCEL_URL`,在 Lambda 上并不存在,自调用实际发不出去(副作用静默\n * 丢失)。2.0 独立实现,改用 Lambda **原生异步调用**:这是最干净的机制,\n * `InvocationType: \"Event\"` 让平台立即返回 202,副作用在被调用的实例里\n * 独立跑完,无需任何有界竞速。\n *\n * **依赖说明**:`@aws-sdk/client-lambda` 由 Lambda 的 Node.js 运行时内置\n * (Node 18+ 运行时随附 AWS SDK v3),因此**不声明为包依赖**,与仓库\n * `lib-loader` 的「变量 specifier 动态加载」同一手法,避免打包期解析。\n *\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport { RECURSION_HEADER, getRecursionToken, type PostSubmitDispatcher } from \"@twikoojs/common\";\n\n/** `@aws-sdk/client-lambda` 的最小结构面(只声明本实现用到的成员) */\ninterface LambdaSdkLike {\n /** 客户端构造器 */\n LambdaClient: new (config: Record<string, unknown>) => {\n /**\n * 发送命令。\n * @param command 命令实例\n * @returns 响应\n */\n send(command: unknown): Promise<unknown>;\n };\n /** Invoke 命令构造器 */\n InvokeCommand: new (input: {\n FunctionName: string;\n InvocationType: string;\n Payload: Uint8Array;\n }) => unknown;\n}\n\n/**\n * 解析自身函数名(运行时注入的 `AWS_LAMBDA_FUNCTION_NAME`)。\n * @returns 函数名;缺失时为空串\n */\nfunction resolveFunctionName(): string {\n return process.env.AWS_LAMBDA_FUNCTION_NAME ?? \"\";\n}\n\n/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */\nexport const lambdaPostSubmitDispatcher: PostSubmitDispatcher = {\n /**\n * 异步调用本函数执行 POST_SUBMIT(平台级 fire-and-forget,不等待)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文\n */\n async dispatch(comment, ctx): Promise<void> {\n const name = resolveFunctionName();\n if (!name) {\n ctx.logger.warn(\"POST_SUBMIT 派发跳过:未取到函数名(AWS_LAMBDA_FUNCTION_NAME)\");\n return;\n }\n const specifier = \"@aws-sdk/client-lambda\";\n const sdk = (await import(/* @vite-ignore */ specifier)) as unknown as LambdaSdkLike;\n const client = new sdk.LambdaClient({});\n /**\n * 载荷构造为 API Gateway 代理事件形态——被调用的实例与处理真实请求时\n * 走完全相同的归一化路径(toTkRequest),因此 IP、请求头、body 语义一致。\n */\n const payload = {\n httpMethod: \"POST\",\n requestContext: { http: { method: \"POST\", sourceIp: ctx.request.ip } },\n headers: { ...ctx.request.headers, [RECURSION_HEADER]: getRecursionToken(ctx.config) },\n body: JSON.stringify({ event: \"POST_SUBMIT\", comment }),\n isBase64Encoded: false,\n };\n await client.send(\n new sdk.InvokeCommand({\n FunctionName: name,\n /** 异步调用:平台立即返回 202,副作用在另一实例中执行 */\n InvocationType: \"Event\",\n Payload: new TextEncoder().encode(JSON.stringify(payload)),\n }),\n );\n },\n};\n","/**\n * @twikoojs/aws-lambda 主逻辑(AWS Lambda 薄适配器)。\n * 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo。\n * 平台核对:API Gateway 代理集成 payload——REST API(v1:\n * httpMethod 顶层 + requestContext.identity.sourceIp)与 HTTP API(v2:\n * requestContext.http.method/sourceIp + isBase64Encoded)双形态均处理;\n * 返回体 body 必须字符串化——docs.aws.amazon.com/lambda(查阅 2026-09-17)。\n *\n * 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以\n * 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。\n */\nimport {\n FULL_CAPABILITIES,\n MongoDatabase,\n createHandler,\n scaffoldAdapters,\n type Database,\n type TkRequest,\n type TkResponse,\n} from \"@twikoojs/common\";\nimport { lambdaPostSubmitDispatcher } from \"./dispatch\";\n\n/** AWS Lambda 平台能力:全能力*/\nconst lambdaCapabilities = FULL_CAPABILITIES;\n\n/** API Gateway 代理事件的最小结构面(v1 REST / v2 HTTP API 双形态) */\nexport interface ApiGatewayEventLike {\n httpMethod?: string;\n requestContext: {\n http?: { method?: string; sourceIp?: string };\n identity?: { sourceIp?: string };\n };\n headers: Record<string, string | string[] | undefined>;\n body: string | null;\n isBase64Encoded?: boolean;\n}\n\n/** Lambda 返回体(代理集成:body 必须字符串化) */\nexport interface LambdaResult {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\n/**\n * API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。\n * @param event 代理事件\n * @returns 内部统一请求\n */\nexport function toTkRequest(event: ApiGatewayEventLike): TkRequest {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.headers ?? {})) {\n if (value !== undefined) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n const forwarded = headers[\"x-forwarded-for\"];\n const ip =\n event.requestContext?.http?.sourceIp ??\n event.requestContext?.identity?.sourceIp ??\n headers[\"x-real-ip\"] ??\n (forwarded ? forwarded.split(\",\")[0].trim() : \"\") ??\n \"\";\n const method = event.requestContext?.http?.method ?? event.httpMethod ?? \"POST\";\n let body: TkRequest[\"body\"] = {} as TkRequest[\"body\"];\n try {\n const raw = event.isBase64Encoded\n ? Buffer.from(event.body ?? \"\", \"base64\").toString(\"utf-8\")\n : event.body;\n const parsed = raw ? JSON.parse(raw) : {};\n if (parsed && typeof parsed === \"object\") body = parsed;\n } catch {\n // 非法 JSON 按空体处理(1.x try/catch 对齐)\n }\n return {\n method: String(method).toUpperCase(),\n path: \"/\",\n query: {},\n body,\n headers,\n ip,\n raw: event,\n };\n}\n\n/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */\nexport function fromTkResponse(tkRes: TkResponse): LambdaResult {\n if (tkRes.status === 204) {\n return { statusCode: 204, headers: {}, body: \"\" };\n }\n return {\n statusCode: tkRes.status,\n headers: { ...tkRes.headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(tkRes.body),\n };\n}\n\n/**\n * 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。\n * @param options 注入项\n * @returns 逐事件处理器\n */\nexport function createLambdaFunc(\n options: { database?: Database; mongoUri?: string } = {},\n): (event: ApiGatewayEventLike) => Promise<LambdaResult> {\n let database: Database | null = options.database ?? null;\n /** 懒建连(inject 优先;init 幂等) */\n const getDatabase = async (): Promise<Database> => {\n database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? \"\" });\n await database.init();\n return database;\n };\n return async (event) => {\n const request = toTkRequest(event);\n const handler = createHandler(\n scaffoldAdapters({\n request: {\n /** 请求恒等透传(归一化见 toTkRequest) */\n toTkRequest: () => request,\n },\n response: {\n /** TkResponse 恒等透传(序列化见 fromTkResponse) */\n fromTkResponse: (r: TkResponse) => r,\n },\n database: await getDatabase(),\n capabilities: lambdaCapabilities,\n postSubmit: lambdaPostSubmitDispatcher,\n }),\n );\n return fromTkResponse(await handler(request));\n };\n}\n\n/** 装配缓存(handler 懒加载语义) */\nlet handlerFn: ((event: ApiGatewayEventLike) => Promise<LambdaResult>) | null = null;\n\n/**\n * AWS Lambda 入口(exports.handler 形态)。\n * @param event 代理事件\n * @returns Lambda 返回体\n */\nexport async function handler(event: ApiGatewayEventLike): Promise<LambdaResult> {\n handlerFn ??= createLambdaFunc();\n return handlerFn(event);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["RECURSION_HEADER","getRecursionToken","FULL_CAPABILITIES","MongoDatabase","createHandler","scaffoldAdapters","RES_CODE"],"sources":["../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **与 1.x 的差异(修正)**:1.x 的 aws-lambda 适配器是\n * `require('twikoo-vercel')` 纯转发壳,继承了 vercel 的 HTTP 递归——而 vercel\n * 取的是 `VERCEL_URL`,在 Lambda 上并不存在,自调用实际发不出去(副作用静默\n * 丢失)。2.0 独立实现,改用 Lambda **原生异步调用**:这是最干净的机制,\n * `InvocationType: \"Event\"` 让平台立即返回 202,副作用在被调用的实例里\n * 独立跑完,无需任何有界竞速。\n *\n * **依赖说明**:`@aws-sdk/client-lambda` 由 Lambda 的 Node.js 运行时内置\n * (Node 18+ 运行时随附 AWS SDK v3),因此**不声明为包依赖**,与仓库\n * `lib-loader` 的「变量 specifier 动态加载」同一手法,避免打包期解析。\n *\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport { RECURSION_HEADER, getRecursionToken, type PostSubmitDispatcher } from \"@twikoojs/common\";\n\n/** `@aws-sdk/client-lambda` 的最小结构面(只声明本实现用到的成员) */\ninterface LambdaSdkLike {\n /** 客户端构造器 */\n LambdaClient: new (config: Record<string, unknown>) => {\n /**\n * 发送命令。\n * @param command 命令实例\n * @returns 响应\n */\n send(command: unknown): Promise<unknown>;\n };\n /** Invoke 命令构造器 */\n InvokeCommand: new (input: {\n FunctionName: string;\n InvocationType: string;\n Payload: Uint8Array;\n }) => unknown;\n}\n\n/**\n * 解析自身函数名(运行时注入的 `AWS_LAMBDA_FUNCTION_NAME`)。\n * @returns 函数名;缺失时为空串\n */\nfunction resolveFunctionName(): string {\n return process.env.AWS_LAMBDA_FUNCTION_NAME ?? \"\";\n}\n\n/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */\nexport const lambdaPostSubmitDispatcher: PostSubmitDispatcher = {\n /**\n * 异步调用本函数执行 POST_SUBMIT(平台级 fire-and-forget,不等待)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文\n */\n async dispatch(comment, ctx): Promise<void> {\n const name = resolveFunctionName();\n if (!name) {\n ctx.logger.warn(\"POST_SUBMIT 派发跳过:未取到函数名(AWS_LAMBDA_FUNCTION_NAME)\");\n return;\n }\n const specifier = \"@aws-sdk/client-lambda\";\n const sdk = (await import(/* @vite-ignore */ specifier)) as unknown as LambdaSdkLike;\n const client = new sdk.LambdaClient({});\n /**\n * 载荷构造为 API Gateway 代理事件形态——被调用的实例与处理真实请求时\n * 走完全相同的归一化路径(toTkRequest),因此 IP、请求头、body 语义一致。\n */\n const payload = {\n httpMethod: \"POST\",\n requestContext: { http: { method: \"POST\", sourceIp: ctx.request.ip } },\n headers: { ...ctx.request.headers, [RECURSION_HEADER]: getRecursionToken(ctx.config) },\n body: JSON.stringify({ event: \"POST_SUBMIT\", comment }),\n isBase64Encoded: false,\n };\n await client.send(\n new sdk.InvokeCommand({\n FunctionName: name,\n /** 异步调用:平台立即返回 202,副作用在另一实例中执行 */\n InvocationType: \"Event\",\n Payload: new TextEncoder().encode(JSON.stringify(payload)),\n }),\n );\n },\n};\n","/**\n * @twikoojs/aws-lambda 主逻辑(AWS Lambda 薄适配器)。\n * 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo。\n * 平台核对:API Gateway 代理集成 payload——REST API(v1:\n * httpMethod 顶层 + requestContext.identity.sourceIp)与 HTTP API(v2:\n * requestContext.http.method/sourceIp + isBase64Encoded)双形态均处理;\n * 返回体 body 必须字符串化——docs.aws.amazon.com/lambda(查阅 2026-09-17)。\n *\n * 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以\n * 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。\n *\n * **异常不外抛**(1.x 语义):请求处理中的任何异常都转成 HTTP 200 + `code: 1000`。\n * 1.x 的 Lambda 函数是复用 vercel 核心的薄壳,而核心函数从不外抛。\n */\nimport {\n FULL_CAPABILITIES,\n MongoDatabase,\n RES_CODE,\n createHandler,\n scaffoldAdapters,\n type Database,\n type TkRequest,\n type TkResponse,\n} from \"@twikoojs/common\";\nimport { lambdaPostSubmitDispatcher } from \"./dispatch\";\n\n/** AWS Lambda 平台能力:全能力*/\nconst lambdaCapabilities = FULL_CAPABILITIES;\n\n/** API Gateway 代理事件的最小结构面(v1 REST / v2 HTTP API 双形态) */\nexport interface ApiGatewayEventLike {\n httpMethod?: string;\n requestContext: {\n http?: { method?: string; sourceIp?: string };\n identity?: { sourceIp?: string };\n };\n headers: Record<string, string | string[] | undefined>;\n body: string | null;\n isBase64Encoded?: boolean;\n}\n\n/** Lambda 返回体(代理集成:body 必须字符串化) */\nexport interface LambdaResult {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\n/**\n * API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。\n * @param event 代理事件\n * @returns 内部统一请求\n */\nexport function toTkRequest(event: ApiGatewayEventLike): TkRequest {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.headers ?? {})) {\n if (value !== undefined) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n const forwarded = headers[\"x-forwarded-for\"];\n const ip =\n event.requestContext?.http?.sourceIp ??\n event.requestContext?.identity?.sourceIp ??\n headers[\"x-real-ip\"] ??\n (forwarded ? forwarded.split(\",\")[0].trim() : \"\") ??\n \"\";\n const method = event.requestContext?.http?.method ?? event.httpMethod ?? \"POST\";\n let body: TkRequest[\"body\"] = {} as TkRequest[\"body\"];\n try {\n const raw = event.isBase64Encoded\n ? Buffer.from(event.body ?? \"\", \"base64\").toString(\"utf-8\")\n : event.body;\n const parsed = raw ? JSON.parse(raw) : {};\n if (parsed && typeof parsed === \"object\") body = parsed;\n } catch {\n // 非法 JSON 按空体处理(1.x try/catch 对齐)\n }\n return {\n method: String(method).toUpperCase(),\n path: \"/\",\n query: {},\n body,\n headers,\n ip,\n raw: event,\n };\n}\n\n/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */\nexport function fromTkResponse(tkRes: TkResponse): LambdaResult {\n if (tkRes.status === 204) {\n return { statusCode: 204, headers: {}, body: \"\" };\n }\n return {\n statusCode: tkRes.status,\n headers: { ...tkRes.headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(tkRes.body),\n };\n}\n\n/**\n * 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。\n * @param options 注入项\n * @returns 逐事件处理器\n */\nexport function createLambdaFunc(\n options: { database?: Database; mongoUri?: string } = {},\n): (event: ApiGatewayEventLike) => Promise<LambdaResult> {\n let database: Database | null = options.database ?? null;\n /**\n * 懒建数据库实例(inject 优先)。**不在这里 init**:pipeline 自己会 init 且在同一处 catch 内,\n * 提前 init 会让「连不上数据库」在 pipeline 之外抛错(1.x 连不上也是 200 + code 1000)。\n */\n const getDatabase = (): Database => {\n database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? \"\" });\n return database;\n };\n return async (event) => {\n try {\n const request = toTkRequest(event);\n const handler = createHandler(\n scaffoldAdapters({\n request: {\n /** 请求恒等透传(归一化见 toTkRequest) */\n toTkRequest: () => request,\n },\n response: {\n /** TkResponse 恒等透传(序列化见 fromTkResponse) */\n fromTkResponse: (r: TkResponse) => r,\n },\n database: getDatabase(),\n capabilities: lambdaCapabilities,\n postSubmit: lambdaPostSubmitDispatcher,\n }),\n );\n return fromTkResponse(await handler(request));\n } catch (e) {\n // 1.x 语义:不外抛(外抛 API Gateway 回 502,前端拿不到 code/message)\n return {\n statusCode: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n code: RES_CODE.FAIL,\n message: e instanceof Error ? e.message : String(e),\n }),\n };\n }\n };\n}\n\n/** 装配缓存(handler 懒加载语义) */\nlet handlerFn: ((event: ApiGatewayEventLike) => Promise<LambdaResult>) | null = null;\n\n/**\n * AWS Lambda 入口(exports.handler 形态)。\n * @param event 代理事件\n * @returns Lambda 返回体\n */\nexport async function handler(event: ApiGatewayEventLike): Promise<LambdaResult> {\n handlerFn ??= createLambdaFunc();\n return handlerFn(event);\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FULL_CAPABILITIES, MongoDatabase, RECURSION_HEADER, createHandler, getRecursionToken, scaffoldAdapters } from "@twikoojs/common";
|
|
1
|
+
import { FULL_CAPABILITIES, MongoDatabase, RECURSION_HEADER, RES_CODE, createHandler, getRecursionToken, scaffoldAdapters } from "@twikoojs/common";
|
|
2
2
|
//#region src/dispatch.ts
|
|
3
3
|
/**
|
|
4
4
|
* AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。
|
|
@@ -80,6 +80,9 @@ async dispatch(comment, ctx) {
|
|
|
80
80
|
*
|
|
81
81
|
* 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以
|
|
82
82
|
* 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。
|
|
83
|
+
*
|
|
84
|
+
* **异常不外抛**(1.x 语义):请求处理中的任何异常都转成 HTTP 200 + `code: 1000`。
|
|
85
|
+
* 1.x 的 Lambda 函数是复用 vercel 核心的薄壳,而核心函数从不外抛。
|
|
83
86
|
*/
|
|
84
87
|
/** AWS Lambda 平台能力:全能力*/
|
|
85
88
|
const lambdaCapabilities = FULL_CAPABILITIES;
|
|
@@ -133,25 +136,38 @@ function fromTkResponse(tkRes) {
|
|
|
133
136
|
*/
|
|
134
137
|
function createLambdaFunc(options = {}) {
|
|
135
138
|
let database = options.database ?? null;
|
|
136
|
-
/**
|
|
137
|
-
|
|
139
|
+
/**
|
|
140
|
+
* 懒建数据库实例(inject 优先)。**不在这里 init**:pipeline 自己会 init 且在同一处 catch 内,
|
|
141
|
+
* 提前 init 会让「连不上数据库」在 pipeline 之外抛错(1.x 连不上也是 200 + code 1000)。
|
|
142
|
+
*/
|
|
143
|
+
const getDatabase = () => {
|
|
138
144
|
database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? "" });
|
|
139
|
-
await database.init();
|
|
140
145
|
return database;
|
|
141
146
|
};
|
|
142
147
|
return async (event) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
try {
|
|
149
|
+
const request = toTkRequest(event);
|
|
150
|
+
return fromTkResponse(await createHandler(scaffoldAdapters({
|
|
151
|
+
request: {
|
|
152
|
+
/** 请求恒等透传(归一化见 toTkRequest) */
|
|
147
153
|
toTkRequest: () => request },
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
response: {
|
|
155
|
+
/** TkResponse 恒等透传(序列化见 fromTkResponse) */
|
|
150
156
|
fromTkResponse: (r) => r },
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
database: getDatabase(),
|
|
158
|
+
capabilities: lambdaCapabilities,
|
|
159
|
+
postSubmit: lambdaPostSubmitDispatcher
|
|
160
|
+
}))(request));
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return {
|
|
163
|
+
statusCode: 200,
|
|
164
|
+
headers: { "Content-Type": "application/json" },
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
code: RES_CODE.FAIL,
|
|
167
|
+
message: e instanceof Error ? e.message : String(e)
|
|
168
|
+
})
|
|
169
|
+
};
|
|
170
|
+
}
|
|
155
171
|
};
|
|
156
172
|
}
|
|
157
173
|
/** 装配缓存(handler 懒加载语义) */
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **与 1.x 的差异(修正)**:1.x 的 aws-lambda 适配器是\n * `require('twikoo-vercel')` 纯转发壳,继承了 vercel 的 HTTP 递归——而 vercel\n * 取的是 `VERCEL_URL`,在 Lambda 上并不存在,自调用实际发不出去(副作用静默\n * 丢失)。2.0 独立实现,改用 Lambda **原生异步调用**:这是最干净的机制,\n * `InvocationType: \"Event\"` 让平台立即返回 202,副作用在被调用的实例里\n * 独立跑完,无需任何有界竞速。\n *\n * **依赖说明**:`@aws-sdk/client-lambda` 由 Lambda 的 Node.js 运行时内置\n * (Node 18+ 运行时随附 AWS SDK v3),因此**不声明为包依赖**,与仓库\n * `lib-loader` 的「变量 specifier 动态加载」同一手法,避免打包期解析。\n *\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport { RECURSION_HEADER, getRecursionToken, type PostSubmitDispatcher } from \"@twikoojs/common\";\n\n/** `@aws-sdk/client-lambda` 的最小结构面(只声明本实现用到的成员) */\ninterface LambdaSdkLike {\n /** 客户端构造器 */\n LambdaClient: new (config: Record<string, unknown>) => {\n /**\n * 发送命令。\n * @param command 命令实例\n * @returns 响应\n */\n send(command: unknown): Promise<unknown>;\n };\n /** Invoke 命令构造器 */\n InvokeCommand: new (input: {\n FunctionName: string;\n InvocationType: string;\n Payload: Uint8Array;\n }) => unknown;\n}\n\n/**\n * 解析自身函数名(运行时注入的 `AWS_LAMBDA_FUNCTION_NAME`)。\n * @returns 函数名;缺失时为空串\n */\nfunction resolveFunctionName(): string {\n return process.env.AWS_LAMBDA_FUNCTION_NAME ?? \"\";\n}\n\n/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */\nexport const lambdaPostSubmitDispatcher: PostSubmitDispatcher = {\n /**\n * 异步调用本函数执行 POST_SUBMIT(平台级 fire-and-forget,不等待)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文\n */\n async dispatch(comment, ctx): Promise<void> {\n const name = resolveFunctionName();\n if (!name) {\n ctx.logger.warn(\"POST_SUBMIT 派发跳过:未取到函数名(AWS_LAMBDA_FUNCTION_NAME)\");\n return;\n }\n const specifier = \"@aws-sdk/client-lambda\";\n const sdk = (await import(/* @vite-ignore */ specifier)) as unknown as LambdaSdkLike;\n const client = new sdk.LambdaClient({});\n /**\n * 载荷构造为 API Gateway 代理事件形态——被调用的实例与处理真实请求时\n * 走完全相同的归一化路径(toTkRequest),因此 IP、请求头、body 语义一致。\n */\n const payload = {\n httpMethod: \"POST\",\n requestContext: { http: { method: \"POST\", sourceIp: ctx.request.ip } },\n headers: { ...ctx.request.headers, [RECURSION_HEADER]: getRecursionToken(ctx.config) },\n body: JSON.stringify({ event: \"POST_SUBMIT\", comment }),\n isBase64Encoded: false,\n };\n await client.send(\n new sdk.InvokeCommand({\n FunctionName: name,\n /** 异步调用:平台立即返回 202,副作用在另一实例中执行 */\n InvocationType: \"Event\",\n Payload: new TextEncoder().encode(JSON.stringify(payload)),\n }),\n );\n },\n};\n","/**\n * @twikoojs/aws-lambda 主逻辑(AWS Lambda 薄适配器)。\n * 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo。\n * 平台核对:API Gateway 代理集成 payload——REST API(v1:\n * httpMethod 顶层 + requestContext.identity.sourceIp)与 HTTP API(v2:\n * requestContext.http.method/sourceIp + isBase64Encoded)双形态均处理;\n * 返回体 body 必须字符串化——docs.aws.amazon.com/lambda(查阅 2026-09-17)。\n *\n * 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以\n * 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。\n */\nimport {\n FULL_CAPABILITIES,\n MongoDatabase,\n createHandler,\n scaffoldAdapters,\n type Database,\n type TkRequest,\n type TkResponse,\n} from \"@twikoojs/common\";\nimport { lambdaPostSubmitDispatcher } from \"./dispatch\";\n\n/** AWS Lambda 平台能力:全能力*/\nconst lambdaCapabilities = FULL_CAPABILITIES;\n\n/** API Gateway 代理事件的最小结构面(v1 REST / v2 HTTP API 双形态) */\nexport interface ApiGatewayEventLike {\n httpMethod?: string;\n requestContext: {\n http?: { method?: string; sourceIp?: string };\n identity?: { sourceIp?: string };\n };\n headers: Record<string, string | string[] | undefined>;\n body: string | null;\n isBase64Encoded?: boolean;\n}\n\n/** Lambda 返回体(代理集成:body 必须字符串化) */\nexport interface LambdaResult {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\n/**\n * API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。\n * @param event 代理事件\n * @returns 内部统一请求\n */\nexport function toTkRequest(event: ApiGatewayEventLike): TkRequest {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.headers ?? {})) {\n if (value !== undefined) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n const forwarded = headers[\"x-forwarded-for\"];\n const ip =\n event.requestContext?.http?.sourceIp ??\n event.requestContext?.identity?.sourceIp ??\n headers[\"x-real-ip\"] ??\n (forwarded ? forwarded.split(\",\")[0].trim() : \"\") ??\n \"\";\n const method = event.requestContext?.http?.method ?? event.httpMethod ?? \"POST\";\n let body: TkRequest[\"body\"] = {} as TkRequest[\"body\"];\n try {\n const raw = event.isBase64Encoded\n ? Buffer.from(event.body ?? \"\", \"base64\").toString(\"utf-8\")\n : event.body;\n const parsed = raw ? JSON.parse(raw) : {};\n if (parsed && typeof parsed === \"object\") body = parsed;\n } catch {\n // 非法 JSON 按空体处理(1.x try/catch 对齐)\n }\n return {\n method: String(method).toUpperCase(),\n path: \"/\",\n query: {},\n body,\n headers,\n ip,\n raw: event,\n };\n}\n\n/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */\nexport function fromTkResponse(tkRes: TkResponse): LambdaResult {\n if (tkRes.status === 204) {\n return { statusCode: 204, headers: {}, body: \"\" };\n }\n return {\n statusCode: tkRes.status,\n headers: { ...tkRes.headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(tkRes.body),\n };\n}\n\n/**\n * 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。\n * @param options 注入项\n * @returns 逐事件处理器\n */\nexport function createLambdaFunc(\n options: { database?: Database; mongoUri?: string } = {},\n): (event: ApiGatewayEventLike) => Promise<LambdaResult> {\n let database: Database | null = options.database ?? null;\n /** 懒建连(inject 优先;init 幂等) */\n const getDatabase = async (): Promise<Database> => {\n database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? \"\" });\n await database.init();\n return database;\n };\n return async (event) => {\n const request = toTkRequest(event);\n const handler = createHandler(\n scaffoldAdapters({\n request: {\n /** 请求恒等透传(归一化见 toTkRequest) */\n toTkRequest: () => request,\n },\n response: {\n /** TkResponse 恒等透传(序列化见 fromTkResponse) */\n fromTkResponse: (r: TkResponse) => r,\n },\n database: await getDatabase(),\n capabilities: lambdaCapabilities,\n postSubmit: lambdaPostSubmitDispatcher,\n }),\n );\n return fromTkResponse(await handler(request));\n };\n}\n\n/** 装配缓存(handler 懒加载语义) */\nlet handlerFn: ((event: ApiGatewayEventLike) => Promise<LambdaResult>) | null = null;\n\n/**\n * AWS Lambda 入口(exports.handler 形态)。\n * @param event 代理事件\n * @returns Lambda 返回体\n */\nexport async function handler(event: ApiGatewayEventLike): Promise<LambdaResult> {\n handlerFn ??= createLambdaFunc();\n return handlerFn(event);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **与 1.x 的差异(修正)**:1.x 的 aws-lambda 适配器是\n * `require('twikoo-vercel')` 纯转发壳,继承了 vercel 的 HTTP 递归——而 vercel\n * 取的是 `VERCEL_URL`,在 Lambda 上并不存在,自调用实际发不出去(副作用静默\n * 丢失)。2.0 独立实现,改用 Lambda **原生异步调用**:这是最干净的机制,\n * `InvocationType: \"Event\"` 让平台立即返回 202,副作用在被调用的实例里\n * 独立跑完,无需任何有界竞速。\n *\n * **依赖说明**:`@aws-sdk/client-lambda` 由 Lambda 的 Node.js 运行时内置\n * (Node 18+ 运行时随附 AWS SDK v3),因此**不声明为包依赖**,与仓库\n * `lib-loader` 的「变量 specifier 动态加载」同一手法,避免打包期解析。\n *\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport { RECURSION_HEADER, getRecursionToken, type PostSubmitDispatcher } from \"@twikoojs/common\";\n\n/** `@aws-sdk/client-lambda` 的最小结构面(只声明本实现用到的成员) */\ninterface LambdaSdkLike {\n /** 客户端构造器 */\n LambdaClient: new (config: Record<string, unknown>) => {\n /**\n * 发送命令。\n * @param command 命令实例\n * @returns 响应\n */\n send(command: unknown): Promise<unknown>;\n };\n /** Invoke 命令构造器 */\n InvokeCommand: new (input: {\n FunctionName: string;\n InvocationType: string;\n Payload: Uint8Array;\n }) => unknown;\n}\n\n/**\n * 解析自身函数名(运行时注入的 `AWS_LAMBDA_FUNCTION_NAME`)。\n * @returns 函数名;缺失时为空串\n */\nfunction resolveFunctionName(): string {\n return process.env.AWS_LAMBDA_FUNCTION_NAME ?? \"\";\n}\n\n/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */\nexport const lambdaPostSubmitDispatcher: PostSubmitDispatcher = {\n /**\n * 异步调用本函数执行 POST_SUBMIT(平台级 fire-and-forget,不等待)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文\n */\n async dispatch(comment, ctx): Promise<void> {\n const name = resolveFunctionName();\n if (!name) {\n ctx.logger.warn(\"POST_SUBMIT 派发跳过:未取到函数名(AWS_LAMBDA_FUNCTION_NAME)\");\n return;\n }\n const specifier = \"@aws-sdk/client-lambda\";\n const sdk = (await import(/* @vite-ignore */ specifier)) as unknown as LambdaSdkLike;\n const client = new sdk.LambdaClient({});\n /**\n * 载荷构造为 API Gateway 代理事件形态——被调用的实例与处理真实请求时\n * 走完全相同的归一化路径(toTkRequest),因此 IP、请求头、body 语义一致。\n */\n const payload = {\n httpMethod: \"POST\",\n requestContext: { http: { method: \"POST\", sourceIp: ctx.request.ip } },\n headers: { ...ctx.request.headers, [RECURSION_HEADER]: getRecursionToken(ctx.config) },\n body: JSON.stringify({ event: \"POST_SUBMIT\", comment }),\n isBase64Encoded: false,\n };\n await client.send(\n new sdk.InvokeCommand({\n FunctionName: name,\n /** 异步调用:平台立即返回 202,副作用在另一实例中执行 */\n InvocationType: \"Event\",\n Payload: new TextEncoder().encode(JSON.stringify(payload)),\n }),\n );\n },\n};\n","/**\n * @twikoojs/aws-lambda 主逻辑(AWS Lambda 薄适配器)。\n * 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo。\n * 平台核对:API Gateway 代理集成 payload——REST API(v1:\n * httpMethod 顶层 + requestContext.identity.sourceIp)与 HTTP API(v2:\n * requestContext.http.method/sourceIp + isBase64Encoded)双形态均处理;\n * 返回体 body 必须字符串化——docs.aws.amazon.com/lambda(查阅 2026-09-17)。\n *\n * 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以\n * 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。\n *\n * **异常不外抛**(1.x 语义):请求处理中的任何异常都转成 HTTP 200 + `code: 1000`。\n * 1.x 的 Lambda 函数是复用 vercel 核心的薄壳,而核心函数从不外抛。\n */\nimport {\n FULL_CAPABILITIES,\n MongoDatabase,\n RES_CODE,\n createHandler,\n scaffoldAdapters,\n type Database,\n type TkRequest,\n type TkResponse,\n} from \"@twikoojs/common\";\nimport { lambdaPostSubmitDispatcher } from \"./dispatch\";\n\n/** AWS Lambda 平台能力:全能力*/\nconst lambdaCapabilities = FULL_CAPABILITIES;\n\n/** API Gateway 代理事件的最小结构面(v1 REST / v2 HTTP API 双形态) */\nexport interface ApiGatewayEventLike {\n httpMethod?: string;\n requestContext: {\n http?: { method?: string; sourceIp?: string };\n identity?: { sourceIp?: string };\n };\n headers: Record<string, string | string[] | undefined>;\n body: string | null;\n isBase64Encoded?: boolean;\n}\n\n/** Lambda 返回体(代理集成:body 必须字符串化) */\nexport interface LambdaResult {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\n/**\n * API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。\n * @param event 代理事件\n * @returns 内部统一请求\n */\nexport function toTkRequest(event: ApiGatewayEventLike): TkRequest {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.headers ?? {})) {\n if (value !== undefined) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n const forwarded = headers[\"x-forwarded-for\"];\n const ip =\n event.requestContext?.http?.sourceIp ??\n event.requestContext?.identity?.sourceIp ??\n headers[\"x-real-ip\"] ??\n (forwarded ? forwarded.split(\",\")[0].trim() : \"\") ??\n \"\";\n const method = event.requestContext?.http?.method ?? event.httpMethod ?? \"POST\";\n let body: TkRequest[\"body\"] = {} as TkRequest[\"body\"];\n try {\n const raw = event.isBase64Encoded\n ? Buffer.from(event.body ?? \"\", \"base64\").toString(\"utf-8\")\n : event.body;\n const parsed = raw ? JSON.parse(raw) : {};\n if (parsed && typeof parsed === \"object\") body = parsed;\n } catch {\n // 非法 JSON 按空体处理(1.x try/catch 对齐)\n }\n return {\n method: String(method).toUpperCase(),\n path: \"/\",\n query: {},\n body,\n headers,\n ip,\n raw: event,\n };\n}\n\n/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */\nexport function fromTkResponse(tkRes: TkResponse): LambdaResult {\n if (tkRes.status === 204) {\n return { statusCode: 204, headers: {}, body: \"\" };\n }\n return {\n statusCode: tkRes.status,\n headers: { ...tkRes.headers, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(tkRes.body),\n };\n}\n\n/**\n * 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。\n * @param options 注入项\n * @returns 逐事件处理器\n */\nexport function createLambdaFunc(\n options: { database?: Database; mongoUri?: string } = {},\n): (event: ApiGatewayEventLike) => Promise<LambdaResult> {\n let database: Database | null = options.database ?? null;\n /**\n * 懒建数据库实例(inject 优先)。**不在这里 init**:pipeline 自己会 init 且在同一处 catch 内,\n * 提前 init 会让「连不上数据库」在 pipeline 之外抛错(1.x 连不上也是 200 + code 1000)。\n */\n const getDatabase = (): Database => {\n database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? \"\" });\n return database;\n };\n return async (event) => {\n try {\n const request = toTkRequest(event);\n const handler = createHandler(\n scaffoldAdapters({\n request: {\n /** 请求恒等透传(归一化见 toTkRequest) */\n toTkRequest: () => request,\n },\n response: {\n /** TkResponse 恒等透传(序列化见 fromTkResponse) */\n fromTkResponse: (r: TkResponse) => r,\n },\n database: getDatabase(),\n capabilities: lambdaCapabilities,\n postSubmit: lambdaPostSubmitDispatcher,\n }),\n );\n return fromTkResponse(await handler(request));\n } catch (e) {\n // 1.x 语义:不外抛(外抛 API Gateway 回 502,前端拿不到 code/message)\n return {\n statusCode: 200,\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n code: RES_CODE.FAIL,\n message: e instanceof Error ? e.message : String(e),\n }),\n };\n }\n };\n}\n\n/** 装配缓存(handler 懒加载语义) */\nlet handlerFn: ((event: ApiGatewayEventLike) => Promise<LambdaResult>) | null = null;\n\n/**\n * AWS Lambda 入口(exports.handler 形态)。\n * @param event 代理事件\n * @returns Lambda 返回体\n */\nexport async function handler(event: ApiGatewayEventLike): Promise<LambdaResult> {\n handlerFn ??= createLambdaFunc();\n return handlerFn(event);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twikoojs/aws-lambda",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
4
4
|
"description": "Twikoo comment system backend for AWS Lambda",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@cap.js/server": "^4.0.5",
|
|
28
28
|
"@imaegoo/node-ip2region": "^2.1.1",
|
|
29
|
-
"@twikoojs/common": "2.0.0-beta.
|
|
30
|
-
"@twikoojs/shared": "2.0.0-beta.
|
|
29
|
+
"@twikoojs/common": "2.0.0-beta.4",
|
|
30
|
+
"@twikoojs/shared": "2.0.0-beta.4",
|
|
31
31
|
"@xsai/generate-text": "^0.5.0",
|
|
32
32
|
"akismet-api": "^6.0.0",
|
|
33
33
|
"axios": "^1.20.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"marked": "^18.0.13",
|
|
40
40
|
"mongodb": "^7.6.0",
|
|
41
41
|
"nodemailer": "^10.0.10",
|
|
42
|
-
"pushoo": "^2.0.0-beta.
|
|
42
|
+
"pushoo": "^2.0.0-beta.4",
|
|
43
43
|
"tencentcloud-sdk-nodejs-tms": "^4.1.311",
|
|
44
44
|
"xml2js": "^0.6.2"
|
|
45
45
|
},
|