@twikoojs/aws-lambda 0.0.1 → 2.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +15 -7
- package/dist/index.d.mts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +176 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +171 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -4
- package/index.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020-present iMaeGoo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# @twikoojs/aws-lambda
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
Twikoo 2.0 服务端适配器。业务逻辑在 `@twikoojs/common`,本包仅做平台入口与注入。
|
|
4
|
+
|
|
5
|
+
## 部署(AWS Lambda + API Gateway)
|
|
6
|
+
|
|
7
|
+
1. Lambda → 创建函数(Node 20+ 运行时)
|
|
8
|
+
2. 函数代码:`exports.handler`(本包默认导出同形态)
|
|
9
|
+
3. API Gateway 代理集成(REST v1 或 HTTP v2 均支持)
|
|
10
|
+
4. 环境变量:`MONGODB_URI`(必填)
|
|
11
|
+
|
|
12
|
+
## 平台核对清单(查阅日期 2026-09-17)
|
|
13
|
+
|
|
14
|
+
- [x] API Gateway v1/v2 payload 双形态(docs.aws.amazon.com/lambda)
|
|
15
|
+
- [x] `requestContext.identity.sourceIp`(v1)与 `requestContext.http.sourceIp`(v2)
|
|
16
|
+
- [x] 返回体 body 字符串化
|
|
17
|
+
- [ ] 真机冷启动与 Mongo 连接实测(人工项)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Database, PostSubmitDispatcher, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
//#region src/main.d.ts
|
|
3
|
+
/** API Gateway 代理事件的最小结构面(v1 REST / v2 HTTP API 双形态) */
|
|
4
|
+
interface ApiGatewayEventLike {
|
|
5
|
+
httpMethod?: string;
|
|
6
|
+
requestContext: {
|
|
7
|
+
http?: {
|
|
8
|
+
method?: string;
|
|
9
|
+
sourceIp?: string;
|
|
10
|
+
};
|
|
11
|
+
identity?: {
|
|
12
|
+
sourceIp?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
headers: Record<string, string | string[] | undefined>;
|
|
16
|
+
body: string | null;
|
|
17
|
+
isBase64Encoded?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Lambda 返回体(代理集成:body 必须字符串化) */
|
|
20
|
+
interface LambdaResult {
|
|
21
|
+
statusCode: number;
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。
|
|
27
|
+
* @param event 代理事件
|
|
28
|
+
* @returns 内部统一请求
|
|
29
|
+
*/
|
|
30
|
+
export declare function toTkRequest(event: ApiGatewayEventLike): TkRequest;
|
|
31
|
+
/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */
|
|
32
|
+
export declare function fromTkResponse(tkRes: TkResponse): LambdaResult;
|
|
33
|
+
/**
|
|
34
|
+
* 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。
|
|
35
|
+
* @param options 注入项
|
|
36
|
+
* @returns 逐事件处理器
|
|
37
|
+
*/
|
|
38
|
+
export declare function createLambdaFunc(options?: {
|
|
39
|
+
database?: Database;
|
|
40
|
+
mongoUri?: string;
|
|
41
|
+
}): (event: ApiGatewayEventLike) => Promise<LambdaResult>;
|
|
42
|
+
/**
|
|
43
|
+
* AWS Lambda 入口(exports.handler 形态)。
|
|
44
|
+
* @param event 代理事件
|
|
45
|
+
* @returns Lambda 返回体
|
|
46
|
+
*/
|
|
47
|
+
export declare function handler(event: ApiGatewayEventLike): Promise<LambdaResult>;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/dispatch.d.ts
|
|
50
|
+
/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */
|
|
51
|
+
export declare const lambdaPostSubmitDispatcher: PostSubmitDispatcher;
|
|
52
|
+
//#endregion
|
|
53
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Database, PostSubmitDispatcher, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
//#region src/main.d.ts
|
|
3
|
+
/** API Gateway 代理事件的最小结构面(v1 REST / v2 HTTP API 双形态) */
|
|
4
|
+
interface ApiGatewayEventLike {
|
|
5
|
+
httpMethod?: string;
|
|
6
|
+
requestContext: {
|
|
7
|
+
http?: {
|
|
8
|
+
method?: string;
|
|
9
|
+
sourceIp?: string;
|
|
10
|
+
};
|
|
11
|
+
identity?: {
|
|
12
|
+
sourceIp?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
headers: Record<string, string | string[] | undefined>;
|
|
16
|
+
body: string | null;
|
|
17
|
+
isBase64Encoded?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Lambda 返回体(代理集成:body 必须字符串化) */
|
|
20
|
+
interface LambdaResult {
|
|
21
|
+
statusCode: number;
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。
|
|
27
|
+
* @param event 代理事件
|
|
28
|
+
* @returns 内部统一请求
|
|
29
|
+
*/
|
|
30
|
+
export declare function toTkRequest(event: ApiGatewayEventLike): TkRequest;
|
|
31
|
+
/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */
|
|
32
|
+
export declare function fromTkResponse(tkRes: TkResponse): LambdaResult;
|
|
33
|
+
/**
|
|
34
|
+
* 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。
|
|
35
|
+
* @param options 注入项
|
|
36
|
+
* @returns 逐事件处理器
|
|
37
|
+
*/
|
|
38
|
+
export declare function createLambdaFunc(options?: {
|
|
39
|
+
database?: Database;
|
|
40
|
+
mongoUri?: string;
|
|
41
|
+
}): (event: ApiGatewayEventLike) => Promise<LambdaResult>;
|
|
42
|
+
/**
|
|
43
|
+
* AWS Lambda 入口(exports.handler 形态)。
|
|
44
|
+
* @param event 代理事件
|
|
45
|
+
* @returns Lambda 返回体
|
|
46
|
+
*/
|
|
47
|
+
export declare function handler(event: ApiGatewayEventLike): Promise<LambdaResult>;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/dispatch.d.ts
|
|
50
|
+
/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */
|
|
51
|
+
export declare const lambdaPostSubmitDispatcher: PostSubmitDispatcher;
|
|
52
|
+
//#endregion
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _twikoojs_common = require("@twikoojs/common");
|
|
3
|
+
//#region src/dispatch.ts
|
|
4
|
+
/**
|
|
5
|
+
* AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。
|
|
6
|
+
*
|
|
7
|
+
* **与 1.x 的差异(修正)**:1.x 的 aws-lambda 适配器是
|
|
8
|
+
* `require('twikoo-vercel')` 纯转发壳,继承了 vercel 的 HTTP 递归——而 vercel
|
|
9
|
+
* 取的是 `VERCEL_URL`,在 Lambda 上并不存在,自调用实际发不出去(副作用静默
|
|
10
|
+
* 丢失)。2.0 独立实现,改用 Lambda **原生异步调用**:这是最干净的机制,
|
|
11
|
+
* `InvocationType: "Event"` 让平台立即返回 202,副作用在被调用的实例里
|
|
12
|
+
* 独立跑完,无需任何有界竞速。
|
|
13
|
+
*
|
|
14
|
+
* **依赖说明**:`@aws-sdk/client-lambda` 由 Lambda 的 Node.js 运行时内置
|
|
15
|
+
* (Node 18+ 运行时随附 AWS SDK v3),因此**不声明为包依赖**,与仓库
|
|
16
|
+
* `lib-loader` 的「变量 specifier 动态加载」同一手法,避免打包期解析。
|
|
17
|
+
*
|
|
18
|
+
* 副作用链本身仍在 common 的 postSubmit 服务里。
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* 解析自身函数名(运行时注入的 `AWS_LAMBDA_FUNCTION_NAME`)。
|
|
22
|
+
* @returns 函数名;缺失时为空串
|
|
23
|
+
*/
|
|
24
|
+
function resolveFunctionName() {
|
|
25
|
+
return process.env.AWS_LAMBDA_FUNCTION_NAME ?? "";
|
|
26
|
+
}
|
|
27
|
+
/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */
|
|
28
|
+
const lambdaPostSubmitDispatcher = {
|
|
29
|
+
/**
|
|
30
|
+
* 异步调用本函数执行 POST_SUBMIT(平台级 fire-and-forget,不等待)。
|
|
31
|
+
* @param comment 已入库的评论
|
|
32
|
+
* @param ctx 当前请求上下文
|
|
33
|
+
*/
|
|
34
|
+
async dispatch(comment, ctx) {
|
|
35
|
+
const name = resolveFunctionName();
|
|
36
|
+
if (!name) {
|
|
37
|
+
ctx.logger.warn("POST_SUBMIT 派发跳过:未取到函数名(AWS_LAMBDA_FUNCTION_NAME)");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const sdk = await import(
|
|
41
|
+
/* @vite-ignore */
|
|
42
|
+
"@aws-sdk/client-lambda"
|
|
43
|
+
);
|
|
44
|
+
const client = new sdk.LambdaClient({});
|
|
45
|
+
/**
|
|
46
|
+
* 载荷构造为 API Gateway 代理事件形态——被调用的实例与处理真实请求时
|
|
47
|
+
* 走完全相同的归一化路径(toTkRequest),因此 IP、请求头、body 语义一致。
|
|
48
|
+
*/
|
|
49
|
+
const payload = {
|
|
50
|
+
httpMethod: "POST",
|
|
51
|
+
requestContext: { http: {
|
|
52
|
+
method: "POST",
|
|
53
|
+
sourceIp: ctx.request.ip
|
|
54
|
+
} },
|
|
55
|
+
headers: {
|
|
56
|
+
...ctx.request.headers,
|
|
57
|
+
[_twikoojs_common.RECURSION_HEADER]: (0, _twikoojs_common.getRecursionToken)(ctx.config)
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
event: "POST_SUBMIT",
|
|
61
|
+
comment
|
|
62
|
+
}),
|
|
63
|
+
isBase64Encoded: false
|
|
64
|
+
};
|
|
65
|
+
await client.send(new sdk.InvokeCommand({
|
|
66
|
+
FunctionName: name,
|
|
67
|
+
/** 异步调用:平台立即返回 202,副作用在另一实例中执行 */
|
|
68
|
+
InvocationType: "Event",
|
|
69
|
+
Payload: new TextEncoder().encode(JSON.stringify(payload))
|
|
70
|
+
}));
|
|
71
|
+
} };
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/main.ts
|
|
74
|
+
/**
|
|
75
|
+
* @twikoojs/aws-lambda 主逻辑(AWS Lambda 薄适配器)。
|
|
76
|
+
* 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo。
|
|
77
|
+
* 平台核对:API Gateway 代理集成 payload——REST API(v1:
|
|
78
|
+
* httpMethod 顶层 + requestContext.identity.sourceIp)与 HTTP API(v2:
|
|
79
|
+
* requestContext.http.method/sourceIp + isBase64Encoded)双形态均处理;
|
|
80
|
+
* 返回体 body 必须字符串化——docs.aws.amazon.com/lambda(查阅 2026-09-17)。
|
|
81
|
+
*
|
|
82
|
+
* 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以
|
|
83
|
+
* 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。
|
|
84
|
+
*/
|
|
85
|
+
/** AWS Lambda 平台能力:全能力*/
|
|
86
|
+
const lambdaCapabilities = _twikoojs_common.FULL_CAPABILITIES;
|
|
87
|
+
/**
|
|
88
|
+
* API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。
|
|
89
|
+
* @param event 代理事件
|
|
90
|
+
* @returns 内部统一请求
|
|
91
|
+
*/
|
|
92
|
+
function toTkRequest(event) {
|
|
93
|
+
const headers = {};
|
|
94
|
+
for (const [key, value] of Object.entries(event.headers ?? {})) if (value !== void 0) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
95
|
+
const forwarded = headers["x-forwarded-for"];
|
|
96
|
+
const ip = event.requestContext?.http?.sourceIp ?? event.requestContext?.identity?.sourceIp ?? headers["x-real-ip"] ?? (forwarded ? forwarded.split(",")[0].trim() : "") ?? "";
|
|
97
|
+
const method = event.requestContext?.http?.method ?? event.httpMethod ?? "POST";
|
|
98
|
+
let body = {};
|
|
99
|
+
try {
|
|
100
|
+
const raw = event.isBase64Encoded ? Buffer.from(event.body ?? "", "base64").toString("utf-8") : event.body;
|
|
101
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
102
|
+
if (parsed && typeof parsed === "object") body = parsed;
|
|
103
|
+
} catch {}
|
|
104
|
+
return {
|
|
105
|
+
method: String(method).toUpperCase(),
|
|
106
|
+
path: "/",
|
|
107
|
+
query: {},
|
|
108
|
+
body,
|
|
109
|
+
headers,
|
|
110
|
+
ip,
|
|
111
|
+
raw: event
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */
|
|
115
|
+
function fromTkResponse(tkRes) {
|
|
116
|
+
if (tkRes.status === 204) return {
|
|
117
|
+
statusCode: 204,
|
|
118
|
+
headers: {},
|
|
119
|
+
body: ""
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
statusCode: tkRes.status,
|
|
123
|
+
headers: {
|
|
124
|
+
...tkRes.headers,
|
|
125
|
+
"Content-Type": "application/json"
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify(tkRes.body)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。
|
|
132
|
+
* @param options 注入项
|
|
133
|
+
* @returns 逐事件处理器
|
|
134
|
+
*/
|
|
135
|
+
function createLambdaFunc(options = {}) {
|
|
136
|
+
let database = options.database ?? null;
|
|
137
|
+
/** 懒建连(inject 优先;init 幂等) */
|
|
138
|
+
const getDatabase = async () => {
|
|
139
|
+
database ??= new _twikoojs_common.MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? "" });
|
|
140
|
+
await database.init();
|
|
141
|
+
return database;
|
|
142
|
+
};
|
|
143
|
+
return async (event) => {
|
|
144
|
+
const request = toTkRequest(event);
|
|
145
|
+
return fromTkResponse(await (0, _twikoojs_common.createHandler)((0, _twikoojs_common.scaffoldAdapters)({
|
|
146
|
+
request: {
|
|
147
|
+
/** 请求恒等透传(归一化见 toTkRequest) */
|
|
148
|
+
toTkRequest: () => request },
|
|
149
|
+
response: {
|
|
150
|
+
/** TkResponse 恒等透传(序列化见 fromTkResponse) */
|
|
151
|
+
fromTkResponse: (r) => r },
|
|
152
|
+
database: await getDatabase(),
|
|
153
|
+
capabilities: lambdaCapabilities,
|
|
154
|
+
postSubmit: lambdaPostSubmitDispatcher
|
|
155
|
+
}))(request));
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** 装配缓存(handler 懒加载语义) */
|
|
159
|
+
let handlerFn = null;
|
|
160
|
+
/**
|
|
161
|
+
* AWS Lambda 入口(exports.handler 形态)。
|
|
162
|
+
* @param event 代理事件
|
|
163
|
+
* @returns Lambda 返回体
|
|
164
|
+
*/
|
|
165
|
+
async function handler(event) {
|
|
166
|
+
handlerFn ??= createLambdaFunc();
|
|
167
|
+
return handlerFn(event);
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
exports.createLambdaFunc = createLambdaFunc;
|
|
171
|
+
exports.fromTkResponse = fromTkResponse;
|
|
172
|
+
exports.handler = handler;
|
|
173
|
+
exports.lambdaPostSubmitDispatcher = lambdaPostSubmitDispatcher;
|
|
174
|
+
exports.toTkRequest = toTkRequest;
|
|
175
|
+
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAS,sBAA8B;CACrC,OAAO,QAAQ,IAAI,4BAA4B;AACjD;;AAGA,MAAa,6BAAmD;;;;;;AAM9D,MAAM,SAAS,SAAS,KAAoB;CAC1C,MAAM,OAAO,oBAAoB;CACjC,IAAI,CAAC,MAAM;EACT,IAAI,OAAO,KAAK,mDAAmD;EACnE;CACF;CAEA,MAAM,MAAO,MAAM;;EAA0B;;CAC7C,MAAM,SAAS,IAAI,IAAI,aAAa,CAAC,CAAC;;;;;CAKtC,MAAM,UAAU;EACd,YAAY;EACZ,gBAAgB,EAAE,MAAM;GAAE,QAAQ;GAAQ,UAAU,IAAI,QAAQ;EAAG,EAAE;EACrE,SAAS;GAAE,GAAG,IAAI,QAAQ;IAAUA,iBAAAA,oBAAAA,GAAmBC,iBAAAA,kBAAAA,CAAkB,IAAI,MAAM;EAAE;EACrF,MAAM,KAAK,UAAU;GAAE,OAAO;GAAe;EAAQ,CAAC;EACtD,iBAAiB;CACnB;CACA,MAAM,OAAO,KACX,IAAI,IAAI,cAAc;EACpB,cAAc;;EAEd,gBAAgB;EAChB,SAAS,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,OAAO,CAAC;CAC3D,CAAC,CACH;AACF,EACF;;;;;;;;;;;;;;;AC1DA,MAAM,qBAAqBC,iBAAAA;;;;;;AA0B3B,SAAgB,YAAY,OAAuC;CACjE,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,GAC3D,IAAI,UAAU,KAAA,GAAW,QAAQ,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;CAE1F,MAAM,YAAY,QAAQ;CAC1B,MAAM,KACJ,MAAM,gBAAgB,MAAM,YAC5B,MAAM,gBAAgB,UAAU,YAChC,QAAQ,iBACP,YAAY,UAAU,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,OAC9C;CACF,MAAM,SAAS,MAAM,gBAAgB,MAAM,UAAU,MAAM,cAAc;CACzE,IAAI,OAA0B,CAAC;CAC/B,IAAI;EACF,MAAM,MAAM,MAAM,kBACd,OAAO,KAAK,MAAM,QAAQ,IAAI,QAAQ,CAAC,CAAC,SAAS,OAAO,IACxD,MAAM;EACV,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;EACxC,IAAI,UAAU,OAAO,WAAW,UAAU,OAAO;CACnD,QAAQ,CAER;CACA,OAAO;EACL,QAAQ,OAAO,MAAM,CAAC,CAAC,YAAY;EACnC,MAAM;EACN,OAAO,CAAC;EACR;EACA;EACA;EACA,KAAK;CACP;AACF;;AAGA,SAAgB,eAAe,OAAiC;CAC9D,IAAI,MAAM,WAAW,KACnB,OAAO;EAAE,YAAY;EAAK,SAAS,CAAC;EAAG,MAAM;CAAG;CAElD,OAAO;EACL,YAAY,MAAM;EAClB,SAAS;GAAE,GAAG,MAAM;GAAS,gBAAgB;EAAmB;EAChE,MAAM,KAAK,UAAU,MAAM,IAAI;CACjC;AACF;;;;;;AAOA,SAAgB,iBACd,UAAsD,CAAC,GACA;CACvD,IAAI,WAA4B,QAAQ,YAAY;;CAEpD,MAAM,cAAc,YAA+B;EACjD,aAAa,IAAIC,iBAAAA,cAAc,EAAE,KAAK,QAAQ,YAAY,QAAQ,IAAI,eAAe,GAAG,CAAC;EACzF,MAAM,SAAS,KAAK;EACpB,OAAO;CACT;CACA,OAAO,OAAO,UAAU;EACtB,MAAM,UAAU,YAAY,KAAK;EAgBjC,OAAO,eAAe,OAAA,GAfNC,iBAAAA,cAAAA,EAAAA,GACdC,iBAAAA,iBAAAA,CAAiB;GACf,SAAS;;AAEP,mBAAmB,QACrB;GACA,UAAU;;AAER,iBAAiB,MAAkB,EACrC;GACA,UAAU,MAAM,YAAY;GAC5B,cAAc;GACd,YAAY;EACd,CAAC,CAE+B,CAAC,CAAC,OAAO,CAAC;CAC9C;AACF;;AAGA,IAAI,YAA4E;;;;;;AAOhF,eAAsB,QAAQ,OAAmD;CAC/E,cAAc,iBAAiB;CAC/B,OAAO,UAAU,KAAK;AACxB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { FULL_CAPABILITIES, MongoDatabase, RECURSION_HEADER, createHandler, getRecursionToken, scaffoldAdapters } from "@twikoojs/common";
|
|
2
|
+
//#region src/dispatch.ts
|
|
3
|
+
/**
|
|
4
|
+
* AWS Lambda 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。
|
|
5
|
+
*
|
|
6
|
+
* **与 1.x 的差异(修正)**:1.x 的 aws-lambda 适配器是
|
|
7
|
+
* `require('twikoo-vercel')` 纯转发壳,继承了 vercel 的 HTTP 递归——而 vercel
|
|
8
|
+
* 取的是 `VERCEL_URL`,在 Lambda 上并不存在,自调用实际发不出去(副作用静默
|
|
9
|
+
* 丢失)。2.0 独立实现,改用 Lambda **原生异步调用**:这是最干净的机制,
|
|
10
|
+
* `InvocationType: "Event"` 让平台立即返回 202,副作用在被调用的实例里
|
|
11
|
+
* 独立跑完,无需任何有界竞速。
|
|
12
|
+
*
|
|
13
|
+
* **依赖说明**:`@aws-sdk/client-lambda` 由 Lambda 的 Node.js 运行时内置
|
|
14
|
+
* (Node 18+ 运行时随附 AWS SDK v3),因此**不声明为包依赖**,与仓库
|
|
15
|
+
* `lib-loader` 的「变量 specifier 动态加载」同一手法,避免打包期解析。
|
|
16
|
+
*
|
|
17
|
+
* 副作用链本身仍在 common 的 postSubmit 服务里。
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* 解析自身函数名(运行时注入的 `AWS_LAMBDA_FUNCTION_NAME`)。
|
|
21
|
+
* @returns 函数名;缺失时为空串
|
|
22
|
+
*/
|
|
23
|
+
function resolveFunctionName() {
|
|
24
|
+
return process.env.AWS_LAMBDA_FUNCTION_NAME ?? "";
|
|
25
|
+
}
|
|
26
|
+
/** AWS Lambda 的 POST_SUBMIT 派发实现(原生异步 Invoke) */
|
|
27
|
+
const lambdaPostSubmitDispatcher = {
|
|
28
|
+
/**
|
|
29
|
+
* 异步调用本函数执行 POST_SUBMIT(平台级 fire-and-forget,不等待)。
|
|
30
|
+
* @param comment 已入库的评论
|
|
31
|
+
* @param ctx 当前请求上下文
|
|
32
|
+
*/
|
|
33
|
+
async dispatch(comment, ctx) {
|
|
34
|
+
const name = resolveFunctionName();
|
|
35
|
+
if (!name) {
|
|
36
|
+
ctx.logger.warn("POST_SUBMIT 派发跳过:未取到函数名(AWS_LAMBDA_FUNCTION_NAME)");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const sdk = await import(
|
|
40
|
+
/* @vite-ignore */
|
|
41
|
+
"@aws-sdk/client-lambda"
|
|
42
|
+
);
|
|
43
|
+
const client = new sdk.LambdaClient({});
|
|
44
|
+
/**
|
|
45
|
+
* 载荷构造为 API Gateway 代理事件形态——被调用的实例与处理真实请求时
|
|
46
|
+
* 走完全相同的归一化路径(toTkRequest),因此 IP、请求头、body 语义一致。
|
|
47
|
+
*/
|
|
48
|
+
const payload = {
|
|
49
|
+
httpMethod: "POST",
|
|
50
|
+
requestContext: { http: {
|
|
51
|
+
method: "POST",
|
|
52
|
+
sourceIp: ctx.request.ip
|
|
53
|
+
} },
|
|
54
|
+
headers: {
|
|
55
|
+
...ctx.request.headers,
|
|
56
|
+
[RECURSION_HEADER]: getRecursionToken(ctx.config)
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
event: "POST_SUBMIT",
|
|
60
|
+
comment
|
|
61
|
+
}),
|
|
62
|
+
isBase64Encoded: false
|
|
63
|
+
};
|
|
64
|
+
await client.send(new sdk.InvokeCommand({
|
|
65
|
+
FunctionName: name,
|
|
66
|
+
/** 异步调用:平台立即返回 202,副作用在另一实例中执行 */
|
|
67
|
+
InvocationType: "Event",
|
|
68
|
+
Payload: new TextEncoder().encode(JSON.stringify(payload))
|
|
69
|
+
}));
|
|
70
|
+
} };
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/main.ts
|
|
73
|
+
/**
|
|
74
|
+
* @twikoojs/aws-lambda 主逻辑(AWS Lambda 薄适配器)。
|
|
75
|
+
* 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo。
|
|
76
|
+
* 平台核对:API Gateway 代理集成 payload——REST API(v1:
|
|
77
|
+
* httpMethod 顶层 + requestContext.identity.sourceIp)与 HTTP API(v2:
|
|
78
|
+
* requestContext.http.method/sourceIp + isBase64Encoded)双形态均处理;
|
|
79
|
+
* 返回体 body 必须字符串化——docs.aws.amazon.com/lambda(查阅 2026-09-17)。
|
|
80
|
+
*
|
|
81
|
+
* 后置副作用(垃圾检测 + 通知)经 {@link lambdaPostSubmitDispatcher} 以
|
|
82
|
+
* 原生异步 Invoke 派发到独立执行单元,见 `./dispatch.ts`。
|
|
83
|
+
*/
|
|
84
|
+
/** AWS Lambda 平台能力:全能力*/
|
|
85
|
+
const lambdaCapabilities = FULL_CAPABILITIES;
|
|
86
|
+
/**
|
|
87
|
+
* API Gateway 事件 → 内部统一请求(v1/v2 双形态;IP 取 sourceIp)。
|
|
88
|
+
* @param event 代理事件
|
|
89
|
+
* @returns 内部统一请求
|
|
90
|
+
*/
|
|
91
|
+
function toTkRequest(event) {
|
|
92
|
+
const headers = {};
|
|
93
|
+
for (const [key, value] of Object.entries(event.headers ?? {})) if (value !== void 0) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
94
|
+
const forwarded = headers["x-forwarded-for"];
|
|
95
|
+
const ip = event.requestContext?.http?.sourceIp ?? event.requestContext?.identity?.sourceIp ?? headers["x-real-ip"] ?? (forwarded ? forwarded.split(",")[0].trim() : "") ?? "";
|
|
96
|
+
const method = event.requestContext?.http?.method ?? event.httpMethod ?? "POST";
|
|
97
|
+
let body = {};
|
|
98
|
+
try {
|
|
99
|
+
const raw = event.isBase64Encoded ? Buffer.from(event.body ?? "", "base64").toString("utf-8") : event.body;
|
|
100
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
101
|
+
if (parsed && typeof parsed === "object") body = parsed;
|
|
102
|
+
} catch {}
|
|
103
|
+
return {
|
|
104
|
+
method: String(method).toUpperCase(),
|
|
105
|
+
path: "/",
|
|
106
|
+
query: {},
|
|
107
|
+
body,
|
|
108
|
+
headers,
|
|
109
|
+
ip,
|
|
110
|
+
raw: event
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** 内部统一响应 → Lambda 返回体(204 无体;body 字符串化)。 */
|
|
114
|
+
function fromTkResponse(tkRes) {
|
|
115
|
+
if (tkRes.status === 204) return {
|
|
116
|
+
statusCode: 204,
|
|
117
|
+
headers: {},
|
|
118
|
+
body: ""
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
statusCode: tkRes.status,
|
|
122
|
+
headers: {
|
|
123
|
+
...tkRes.headers,
|
|
124
|
+
"Content-Type": "application/json"
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify(tkRes.body)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 创建 Lambda 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连)。
|
|
131
|
+
* @param options 注入项
|
|
132
|
+
* @returns 逐事件处理器
|
|
133
|
+
*/
|
|
134
|
+
function createLambdaFunc(options = {}) {
|
|
135
|
+
let database = options.database ?? null;
|
|
136
|
+
/** 懒建连(inject 优先;init 幂等) */
|
|
137
|
+
const getDatabase = async () => {
|
|
138
|
+
database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? "" });
|
|
139
|
+
await database.init();
|
|
140
|
+
return database;
|
|
141
|
+
};
|
|
142
|
+
return async (event) => {
|
|
143
|
+
const request = toTkRequest(event);
|
|
144
|
+
return fromTkResponse(await createHandler(scaffoldAdapters({
|
|
145
|
+
request: {
|
|
146
|
+
/** 请求恒等透传(归一化见 toTkRequest) */
|
|
147
|
+
toTkRequest: () => request },
|
|
148
|
+
response: {
|
|
149
|
+
/** TkResponse 恒等透传(序列化见 fromTkResponse) */
|
|
150
|
+
fromTkResponse: (r) => r },
|
|
151
|
+
database: await getDatabase(),
|
|
152
|
+
capabilities: lambdaCapabilities,
|
|
153
|
+
postSubmit: lambdaPostSubmitDispatcher
|
|
154
|
+
}))(request));
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** 装配缓存(handler 懒加载语义) */
|
|
158
|
+
let handlerFn = null;
|
|
159
|
+
/**
|
|
160
|
+
* AWS Lambda 入口(exports.handler 形态)。
|
|
161
|
+
* @param event 代理事件
|
|
162
|
+
* @returns Lambda 返回体
|
|
163
|
+
*/
|
|
164
|
+
async function handler(event) {
|
|
165
|
+
handlerFn ??= createLambdaFunc();
|
|
166
|
+
return handlerFn(event);
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { createLambdaFunc, fromTkResponse, handler, lambdaPostSubmitDispatcher, toTkRequest };
|
|
170
|
+
|
|
171
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAS,sBAA8B;CACrC,OAAO,QAAQ,IAAI,4BAA4B;AACjD;;AAGA,MAAa,6BAAmD;;;;;;AAM9D,MAAM,SAAS,SAAS,KAAoB;CAC1C,MAAM,OAAO,oBAAoB;CACjC,IAAI,CAAC,MAAM;EACT,IAAI,OAAO,KAAK,mDAAmD;EACnE;CACF;CAEA,MAAM,MAAO,MAAM;;EAA0B;;CAC7C,MAAM,SAAS,IAAI,IAAI,aAAa,CAAC,CAAC;;;;;CAKtC,MAAM,UAAU;EACd,YAAY;EACZ,gBAAgB,EAAE,MAAM;GAAE,QAAQ;GAAQ,UAAU,IAAI,QAAQ;EAAG,EAAE;EACrE,SAAS;GAAE,GAAG,IAAI,QAAQ;IAAU,mBAAmB,kBAAkB,IAAI,MAAM;EAAE;EACrF,MAAM,KAAK,UAAU;GAAE,OAAO;GAAe;EAAQ,CAAC;EACtD,iBAAiB;CACnB;CACA,MAAM,OAAO,KACX,IAAI,IAAI,cAAc;EACpB,cAAc;;EAEd,gBAAgB;EAChB,SAAS,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,OAAO,CAAC;CAC3D,CAAC,CACH;AACF,EACF;;;;;;;;;;;;;;;AC1DA,MAAM,qBAAqB;;;;;;AA0B3B,SAAgB,YAAY,OAAuC;CACjE,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,GAC3D,IAAI,UAAU,KAAA,GAAW,QAAQ,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;CAE1F,MAAM,YAAY,QAAQ;CAC1B,MAAM,KACJ,MAAM,gBAAgB,MAAM,YAC5B,MAAM,gBAAgB,UAAU,YAChC,QAAQ,iBACP,YAAY,UAAU,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,OAC9C;CACF,MAAM,SAAS,MAAM,gBAAgB,MAAM,UAAU,MAAM,cAAc;CACzE,IAAI,OAA0B,CAAC;CAC/B,IAAI;EACF,MAAM,MAAM,MAAM,kBACd,OAAO,KAAK,MAAM,QAAQ,IAAI,QAAQ,CAAC,CAAC,SAAS,OAAO,IACxD,MAAM;EACV,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;EACxC,IAAI,UAAU,OAAO,WAAW,UAAU,OAAO;CACnD,QAAQ,CAER;CACA,OAAO;EACL,QAAQ,OAAO,MAAM,CAAC,CAAC,YAAY;EACnC,MAAM;EACN,OAAO,CAAC;EACR;EACA;EACA;EACA,KAAK;CACP;AACF;;AAGA,SAAgB,eAAe,OAAiC;CAC9D,IAAI,MAAM,WAAW,KACnB,OAAO;EAAE,YAAY;EAAK,SAAS,CAAC;EAAG,MAAM;CAAG;CAElD,OAAO;EACL,YAAY,MAAM;EAClB,SAAS;GAAE,GAAG,MAAM;GAAS,gBAAgB;EAAmB;EAChE,MAAM,KAAK,UAAU,MAAM,IAAI;CACjC;AACF;;;;;;AAOA,SAAgB,iBACd,UAAsD,CAAC,GACA;CACvD,IAAI,WAA4B,QAAQ,YAAY;;CAEpD,MAAM,cAAc,YAA+B;EACjD,aAAa,IAAI,cAAc,EAAE,KAAK,QAAQ,YAAY,QAAQ,IAAI,eAAe,GAAG,CAAC;EACzF,MAAM,SAAS,KAAK;EACpB,OAAO;CACT;CACA,OAAO,OAAO,UAAU;EACtB,MAAM,UAAU,YAAY,KAAK;EAgBjC,OAAO,eAAe,MAfN,cACd,iBAAiB;GACf,SAAS;;AAEP,mBAAmB,QACrB;GACA,UAAU;;AAER,iBAAiB,MAAkB,EACrC;GACA,UAAU,MAAM,YAAY;GAC5B,cAAc;GACd,YAAY;EACd,CAAC,CAE+B,CAAC,CAAC,OAAO,CAAC;CAC9C;AACF;;AAGA,IAAI,YAA4E;;;;;;AAOhF,eAAsB,QAAQ,OAAmD;CAC/E,cAAc,iBAAiB;CAC/B,OAAO,UAAU,KAAK;AACxB"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twikoojs/aws-lambda",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
|
+
"description": "Twikoo comment system backend for AWS Lambda",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"homepage": "https://twikoo.js.org",
|
|
7
6
|
"repository": {
|
|
8
7
|
"type": "git",
|
|
9
8
|
"url": "https://github.com/twikoojs/twikoo.git"
|
|
10
9
|
},
|
|
11
10
|
"publishConfig": {
|
|
12
11
|
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@cap.js/server": "^4.0.5",
|
|
28
|
+
"@imaegoo/node-ip2region": "^2.1.1",
|
|
29
|
+
"@twikoojs/common": "2.0.0-beta.2",
|
|
30
|
+
"@twikoojs/shared": "2.0.0-beta.2",
|
|
31
|
+
"@xsai/generate-text": "^0.5.0",
|
|
32
|
+
"akismet-api": "^6.0.0",
|
|
33
|
+
"axios": "^1.20.0",
|
|
34
|
+
"bowser": "^2.14.1",
|
|
35
|
+
"dompurify": "^3.4.15",
|
|
36
|
+
"form-data": "^4.0.6",
|
|
37
|
+
"html-to-text": "^10.0.1",
|
|
38
|
+
"jsdom": "^30.1.0",
|
|
39
|
+
"marked": "^18.0.13",
|
|
40
|
+
"mongodb": "^7.6.0",
|
|
41
|
+
"nodemailer": "^10.0.10",
|
|
42
|
+
"pushoo": "^2.0.0-beta.2",
|
|
43
|
+
"tencentcloud-sdk-nodejs-tms": "^4.1.311",
|
|
44
|
+
"xml2js": "^0.6.2"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^26.6.1"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsdown",
|
|
51
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
52
|
+
"test": "vitest run"
|
|
13
53
|
}
|
|
14
|
-
}
|
|
54
|
+
}
|
package/index.js
DELETED