twikoo-vercel 1.7.23 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/dist/index.d.mts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +146 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +138 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -9
- package/api/index.js +0 -1192
- package/vercel.json +0 -5
package/README.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# twikoo-vercel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Twikoo 2.0 服务端适配器。业务逻辑在 `@twikoojs/common`,本包仅做平台入口与注入。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 部署(Vercel)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
1. Vercel → New Project → 导入仓库(或 `vercel deploy`)
|
|
8
|
+
2. 环境变量:`MONGODB_URI`(必填,Atlas/自建 Mongo 连接串)
|
|
9
|
+
3. 部署完成后前端配置 API 地址为部署域名
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## 平台核对清单(查阅日期 2026-09-17)
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
- [x] Serverless Function `(req, res)` 签名与 body 自动解析(vercel.com/docs/functions)
|
|
14
|
+
- [x] Node runtime 版本与默认值(项目 settings 或 vercel.json 指定 20+)
|
|
15
|
+
- [ ] 函数超时与区域配置实测(人工项)
|
|
16
|
+
- [ ] 冷启动与 Mongo 连接复用实测(人工项)
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
## 环境
|
|
19
|
+
|
|
20
|
+
- `MONGODB_URI`:Mongo 连接串(必填)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Database, PostSubmitDispatcher, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
//#region src/main.d.ts
|
|
3
|
+
/** Vercel 请求的最小结构面(Node IncomingMessage + Vercel 扩展) */
|
|
4
|
+
interface VercelRequestLike {
|
|
5
|
+
method?: string;
|
|
6
|
+
headers: Record<string, string | string[] | undefined>;
|
|
7
|
+
query?: Record<string, string | string[] | undefined>;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
url?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Vercel 响应的最小结构面(Node ServerResponse) */
|
|
12
|
+
interface VercelResponseLike {
|
|
13
|
+
status(code: number): VercelResponseLike;
|
|
14
|
+
setHeader(name: string, value: string): VercelResponseLike;
|
|
15
|
+
json(body: unknown): VercelResponseLike;
|
|
16
|
+
end(): VercelResponseLike;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Vercel 请求 → 内部统一请求(headers 小写化;query String 化;x-real-ip/转发首跳取 IP)。
|
|
20
|
+
* @param req Vercel/Node 请求
|
|
21
|
+
* @returns 内部统一请求
|
|
22
|
+
*/
|
|
23
|
+
export declare function toTkRequest(req: VercelRequestLike): TkRequest;
|
|
24
|
+
/** 内部统一响应 → Vercel 响应(状态码 + CORS 头 + JSON 体;204 无体)。 */
|
|
25
|
+
export declare function fromTkResponse(res: VercelResponseLike, tkRes: TkResponse): void;
|
|
26
|
+
/**
|
|
27
|
+
* 创建 Vercel 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连,1.x 缓存语义)。
|
|
28
|
+
* @param options 注入项(database:Database 实现;mongoUri:连接串覆写)
|
|
29
|
+
* @returns 逐请求处理器 `(req, res) => Promise<void>`
|
|
30
|
+
*/
|
|
31
|
+
export declare function createVercelFunc(options?: {
|
|
32
|
+
database?: Database;
|
|
33
|
+
mongoUri?: string;
|
|
34
|
+
}): (req: VercelRequestLike, res: VercelResponseLike) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Vercel Serverless Function 默认入口。
|
|
37
|
+
* @param req Vercel/Node 请求
|
|
38
|
+
* @param res Vercel/Node 响应
|
|
39
|
+
*/
|
|
40
|
+
export default function vercelHandler(req: VercelRequestLike, res: VercelResponseLike): Promise<void>;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/dispatch.d.ts
|
|
43
|
+
/** Vercel 的 POST_SUBMIT 派发实现(HTTP 递归自调用) */
|
|
44
|
+
export declare const vercelPostSubmitDispatcher: PostSubmitDispatcher;
|
|
45
|
+
//#endregion
|
|
46
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Database, PostSubmitDispatcher, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
//#region src/main.d.ts
|
|
3
|
+
/** Vercel 请求的最小结构面(Node IncomingMessage + Vercel 扩展) */
|
|
4
|
+
interface VercelRequestLike {
|
|
5
|
+
method?: string;
|
|
6
|
+
headers: Record<string, string | string[] | undefined>;
|
|
7
|
+
query?: Record<string, string | string[] | undefined>;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
url?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Vercel 响应的最小结构面(Node ServerResponse) */
|
|
12
|
+
interface VercelResponseLike {
|
|
13
|
+
status(code: number): VercelResponseLike;
|
|
14
|
+
setHeader(name: string, value: string): VercelResponseLike;
|
|
15
|
+
json(body: unknown): VercelResponseLike;
|
|
16
|
+
end(): VercelResponseLike;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Vercel 请求 → 内部统一请求(headers 小写化;query String 化;x-real-ip/转发首跳取 IP)。
|
|
20
|
+
* @param req Vercel/Node 请求
|
|
21
|
+
* @returns 内部统一请求
|
|
22
|
+
*/
|
|
23
|
+
export declare function toTkRequest(req: VercelRequestLike): TkRequest;
|
|
24
|
+
/** 内部统一响应 → Vercel 响应(状态码 + CORS 头 + JSON 体;204 无体)。 */
|
|
25
|
+
export declare function fromTkResponse(res: VercelResponseLike, tkRes: TkResponse): void;
|
|
26
|
+
/**
|
|
27
|
+
* 创建 Vercel 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连,1.x 缓存语义)。
|
|
28
|
+
* @param options 注入项(database:Database 实现;mongoUri:连接串覆写)
|
|
29
|
+
* @returns 逐请求处理器 `(req, res) => Promise<void>`
|
|
30
|
+
*/
|
|
31
|
+
export declare function createVercelFunc(options?: {
|
|
32
|
+
database?: Database;
|
|
33
|
+
mongoUri?: string;
|
|
34
|
+
}): (req: VercelRequestLike, res: VercelResponseLike) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Vercel Serverless Function 默认入口。
|
|
37
|
+
* @param req Vercel/Node 请求
|
|
38
|
+
* @param res Vercel/Node 响应
|
|
39
|
+
*/
|
|
40
|
+
export default function vercelHandler(req: VercelRequestLike, res: VercelResponseLike): Promise<void>;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/dispatch.d.ts
|
|
43
|
+
/** Vercel 的 POST_SUBMIT 派发实现(HTTP 递归自调用) */
|
|
44
|
+
export declare const vercelPostSubmitDispatcher: PostSubmitDispatcher;
|
|
45
|
+
//#endregion
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let _twikoojs_common = require("@twikoojs/common");
|
|
6
|
+
//#region src/dispatch.ts
|
|
7
|
+
/**
|
|
8
|
+
* Vercel 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。
|
|
9
|
+
*
|
|
10
|
+
* **1.x 语义对齐**:COMMENT_SUBMIT 保存评论后,用 **HTTP 递归自调用**把
|
|
11
|
+
* POST_SUBMIT 送到另一个函数实例执行——垃圾检测与邮件/IM 通知因此拥有
|
|
12
|
+
* 独立的执行时间预算,既不会拖慢用户请求,也不会因为用户请求的超时上限
|
|
13
|
+
* 而整条失败(1.x 实现见 `src/server/vercel/api/index.js` 的 `commentSubmit`)。
|
|
14
|
+
*
|
|
15
|
+
* **为什么放在适配器**:这是平台专有机制,common 不应感知。平台相关部分只有
|
|
16
|
+
* 两处:
|
|
17
|
+
* - 自身地址:`TWIKOO_SELF_URL`(显式覆写)→ `VERCEL_URL`(平台注入);
|
|
18
|
+
* - 内部派发令牌:走 `x-twikoo-recursion` 请求头(1.x 同名,跨版本可互认)。
|
|
19
|
+
*
|
|
20
|
+
* 副作用链本身仍在 common 的 postSubmit 服务里。
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* 派发等待上限(毫秒)。1.x 为 `Promise.race` 5 秒:超时只表示「本请求不再
|
|
24
|
+
* 继续等」,被调用的实例仍在自己的预算内跑完副作用。
|
|
25
|
+
*/
|
|
26
|
+
const DISPATCH_TIMEOUT_MS = 5e3;
|
|
27
|
+
/**
|
|
28
|
+
* 解析自身函数地址(显式覆写优先,其次平台注入的 VERCEL_URL)。
|
|
29
|
+
* @returns 绝对地址;两者皆缺失时为空串
|
|
30
|
+
*/
|
|
31
|
+
function resolveSelfUrl() {
|
|
32
|
+
const explicit = process.env.TWIKOO_SELF_URL;
|
|
33
|
+
if (explicit) return explicit;
|
|
34
|
+
const vercelUrl = process.env.VERCEL_URL;
|
|
35
|
+
return vercelUrl ? `https://${vercelUrl}` : "";
|
|
36
|
+
}
|
|
37
|
+
/** Vercel 的 POST_SUBMIT 派发实现(HTTP 递归自调用) */
|
|
38
|
+
const vercelPostSubmitDispatcher = {
|
|
39
|
+
/**
|
|
40
|
+
* 自调用本函数执行 POST_SUBMIT(不等待副作用完成,只等有界竞速)。
|
|
41
|
+
* @param comment 已入库的评论
|
|
42
|
+
* @param ctx 当前请求上下文(用于取递归令牌与记录日志)
|
|
43
|
+
*/
|
|
44
|
+
async dispatch(comment, ctx) {
|
|
45
|
+
const url = resolveSelfUrl();
|
|
46
|
+
if (!url) {
|
|
47
|
+
ctx.logger.warn("POST_SUBMIT 派发跳过:未取到自身地址(TWIKOO_SELF_URL / VERCEL_URL)");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const axios = await (0, _twikoojs_common.getAxios)();
|
|
51
|
+
await Promise.race([axios.post(url, {
|
|
52
|
+
event: "POST_SUBMIT",
|
|
53
|
+
comment
|
|
54
|
+
}, { headers: { [_twikoojs_common.RECURSION_HEADER]: (0, _twikoojs_common.getRecursionToken)(ctx.config) } }), new Promise((resolve) => setTimeout(resolve, DISPATCH_TIMEOUT_MS))]);
|
|
55
|
+
} };
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/main.ts
|
|
58
|
+
/**
|
|
59
|
+
* twikoo-vercel 主逻辑(Vercel 薄适配器,规范)。
|
|
60
|
+
* 业务逻辑全部在 @twikoojs/common;平台核对:vercel.com/docs/functions(查阅 2026-09-17)。
|
|
61
|
+
*
|
|
62
|
+
* 后置副作用(垃圾检测 + 通知)经 {@link vercelPostSubmitDispatcher} 以
|
|
63
|
+
* HTTP 递归自调用派发到独立执行单元,见 `./dispatch.ts`。
|
|
64
|
+
*/
|
|
65
|
+
/** Vercel 平台能力:全能力(能力矩阵)*/
|
|
66
|
+
const vercelCapabilities = _twikoojs_common.FULL_CAPABILITIES;
|
|
67
|
+
/**
|
|
68
|
+
* Vercel 请求 → 内部统一请求(headers 小写化;query String 化;x-real-ip/转发首跳取 IP)。
|
|
69
|
+
* @param req Vercel/Node 请求
|
|
70
|
+
* @returns 内部统一请求
|
|
71
|
+
*/
|
|
72
|
+
function toTkRequest(req) {
|
|
73
|
+
const headers = {};
|
|
74
|
+
for (const [key, value] of Object.entries(req.headers ?? {})) if (value !== void 0) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
75
|
+
const query = {};
|
|
76
|
+
for (const [key, value] of Object.entries(req.query ?? {})) query[key] = Array.isArray(value) ? String(value[0]) : String(value ?? "");
|
|
77
|
+
const forwarded = headers["x-forwarded-for"];
|
|
78
|
+
const ip = headers["x-real-ip"] ?? (forwarded ? forwarded.split(",")[0].trim() : "") ?? "";
|
|
79
|
+
const body = req.body && typeof req.body === "object" ? req.body : {};
|
|
80
|
+
return {
|
|
81
|
+
method: String(req.method ?? "POST").toUpperCase(),
|
|
82
|
+
path: String(req.url ?? "/"),
|
|
83
|
+
query,
|
|
84
|
+
body,
|
|
85
|
+
headers,
|
|
86
|
+
ip,
|
|
87
|
+
raw: req
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** 内部统一响应 → Vercel 响应(状态码 + CORS 头 + JSON 体;204 无体)。 */
|
|
91
|
+
function fromTkResponse(res, tkRes) {
|
|
92
|
+
res.status(tkRes.status);
|
|
93
|
+
for (const [name, value] of Object.entries(tkRes.headers)) res.setHeader(name, value);
|
|
94
|
+
if (tkRes.status === 204) {
|
|
95
|
+
res.end();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
res.json(tkRes.body);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 创建 Vercel 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连,1.x 缓存语义)。
|
|
102
|
+
* @param options 注入项(database:Database 实现;mongoUri:连接串覆写)
|
|
103
|
+
* @returns 逐请求处理器 `(req, res) => Promise<void>`
|
|
104
|
+
*/
|
|
105
|
+
function createVercelFunc(options = {}) {
|
|
106
|
+
let database = options.database ?? null;
|
|
107
|
+
/** 懒建连(inject 优先;init 幂等) */
|
|
108
|
+
const getDatabase = async () => {
|
|
109
|
+
database ??= new _twikoojs_common.MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? "" });
|
|
110
|
+
await database.init();
|
|
111
|
+
return database;
|
|
112
|
+
};
|
|
113
|
+
return async (req, res) => {
|
|
114
|
+
const request = toTkRequest(req);
|
|
115
|
+
fromTkResponse(res, await (0, _twikoojs_common.createHandler)((0, _twikoojs_common.scaffoldAdapters)({
|
|
116
|
+
request: {
|
|
117
|
+
/** 事件即请求体(闭包透传,见 toTkRequest) */
|
|
118
|
+
toTkRequest: () => request },
|
|
119
|
+
response: {
|
|
120
|
+
/** TkResponse 恒等透传(平台写入见 fromTkResponse) */
|
|
121
|
+
fromTkResponse: (r) => r },
|
|
122
|
+
database: await getDatabase(),
|
|
123
|
+
capabilities: vercelCapabilities,
|
|
124
|
+
postSubmit: vercelPostSubmitDispatcher
|
|
125
|
+
}))(request));
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** 装配缓存(default handler 懒加载语义) */
|
|
129
|
+
let handlerFn = null;
|
|
130
|
+
/**
|
|
131
|
+
* Vercel Serverless Function 默认入口。
|
|
132
|
+
* @param req Vercel/Node 请求
|
|
133
|
+
* @param res Vercel/Node 响应
|
|
134
|
+
*/
|
|
135
|
+
async function vercelHandler(req, res) {
|
|
136
|
+
handlerFn ??= createVercelFunc();
|
|
137
|
+
await handlerFn(req, res);
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
exports.createVercelFunc = createVercelFunc;
|
|
141
|
+
exports.default = vercelHandler;
|
|
142
|
+
exports.fromTkResponse = fromTkResponse;
|
|
143
|
+
exports.toTkRequest = toTkRequest;
|
|
144
|
+
exports.vercelPostSubmitDispatcher = vercelPostSubmitDispatcher;
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["getAxios","RECURSION_HEADER","getRecursionToken","FULL_CAPABILITIES","MongoDatabase","createHandler","scaffoldAdapters"],"sources":["../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * Vercel 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **1.x 语义对齐**:COMMENT_SUBMIT 保存评论后,用 **HTTP 递归自调用**把\n * POST_SUBMIT 送到另一个函数实例执行——垃圾检测与邮件/IM 通知因此拥有\n * 独立的执行时间预算,既不会拖慢用户请求,也不会因为用户请求的超时上限\n * 而整条失败(1.x 实现见 `src/server/vercel/api/index.js` 的 `commentSubmit`)。\n *\n * **为什么放在适配器**:这是平台专有机制,common 不应感知。平台相关部分只有\n * 两处:\n * - 自身地址:`TWIKOO_SELF_URL`(显式覆写)→ `VERCEL_URL`(平台注入);\n * - 内部派发令牌:走 `x-twikoo-recursion` 请求头(1.x 同名,跨版本可互认)。\n *\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport {\n RECURSION_HEADER,\n getAxios,\n getRecursionToken,\n type PostSubmitDispatcher,\n} from \"@twikoojs/common\";\n\n/**\n * 派发等待上限(毫秒)。1.x 为 `Promise.race` 5 秒:超时只表示「本请求不再\n * 继续等」,被调用的实例仍在自己的预算内跑完副作用。\n */\nconst DISPATCH_TIMEOUT_MS = 5000;\n\n/**\n * 解析自身函数地址(显式覆写优先,其次平台注入的 VERCEL_URL)。\n * @returns 绝对地址;两者皆缺失时为空串\n */\nfunction resolveSelfUrl(): string {\n const explicit = process.env.TWIKOO_SELF_URL;\n if (explicit) return explicit;\n const vercelUrl = process.env.VERCEL_URL;\n return vercelUrl ? `https://${vercelUrl}` : \"\";\n}\n\n/** Vercel 的 POST_SUBMIT 派发实现(HTTP 递归自调用) */\nexport const vercelPostSubmitDispatcher: PostSubmitDispatcher = {\n /**\n * 自调用本函数执行 POST_SUBMIT(不等待副作用完成,只等有界竞速)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文(用于取递归令牌与记录日志)\n */\n async dispatch(comment, ctx): Promise<void> {\n const url = resolveSelfUrl();\n if (!url) {\n ctx.logger.warn(\"POST_SUBMIT 派发跳过:未取到自身地址(TWIKOO_SELF_URL / VERCEL_URL)\");\n return;\n }\n const axios = await getAxios();\n await Promise.race([\n axios.post(\n url,\n { event: \"POST_SUBMIT\", comment },\n { headers: { [RECURSION_HEADER]: getRecursionToken(ctx.config) } },\n ),\n new Promise((resolve) => setTimeout(resolve, DISPATCH_TIMEOUT_MS)),\n ]);\n },\n};\n","/**\n * twikoo-vercel 主逻辑(Vercel 薄适配器,规范)。\n * 业务逻辑全部在 @twikoojs/common;平台核对:vercel.com/docs/functions(查阅 2026-09-17)。\n *\n * 后置副作用(垃圾检测 + 通知)经 {@link vercelPostSubmitDispatcher} 以\n * HTTP 递归自调用派发到独立执行单元,见 `./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 { vercelPostSubmitDispatcher } from \"./dispatch\";\n\n/** Vercel 平台能力:全能力(能力矩阵)*/\nconst vercelCapabilities = FULL_CAPABILITIES;\n\n/** Vercel 请求的最小结构面(Node IncomingMessage + Vercel 扩展) */\nexport interface VercelRequestLike {\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n query?: Record<string, string | string[] | undefined>;\n body?: unknown;\n url?: string;\n}\n\n/** Vercel 响应的最小结构面(Node ServerResponse) */\nexport interface VercelResponseLike {\n status(code: number): VercelResponseLike;\n setHeader(name: string, value: string): VercelResponseLike;\n json(body: unknown): VercelResponseLike;\n end(): VercelResponseLike;\n}\n\n/**\n * Vercel 请求 → 内部统一请求(headers 小写化;query String 化;x-real-ip/转发首跳取 IP)。\n * @param req Vercel/Node 请求\n * @returns 内部统一请求\n */\nexport function toTkRequest(req: VercelRequestLike): TkRequest {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers ?? {})) {\n if (value !== undefined) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n const query: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.query ?? {})) {\n query[key] = Array.isArray(value) ? String(value[0]) : String(value ?? \"\");\n }\n const forwarded = headers[\"x-forwarded-for\"];\n const ip = headers[\"x-real-ip\"] ?? (forwarded ? forwarded.split(\",\")[0].trim() : \"\") ?? \"\";\n const body = (req.body && typeof req.body === \"object\" ? req.body : {}) as TkRequest[\"body\"];\n return {\n method: String(req.method ?? \"POST\").toUpperCase(),\n path: String(req.url ?? \"/\"),\n query,\n body,\n headers,\n ip,\n raw: req,\n };\n}\n\n/** 内部统一响应 → Vercel 响应(状态码 + CORS 头 + JSON 体;204 无体)。 */\nexport function fromTkResponse(res: VercelResponseLike, tkRes: TkResponse): void {\n res.status(tkRes.status);\n for (const [name, value] of Object.entries(tkRes.headers)) {\n res.setHeader(name, value);\n }\n if (tkRes.status === 204) {\n res.end();\n return;\n }\n res.json(tkRes.body);\n}\n\n/**\n * 创建 Vercel 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连,1.x 缓存语义)。\n * @param options 注入项(database:Database 实现;mongoUri:连接串覆写)\n * @returns 逐请求处理器 `(req, res) => Promise<void>`\n */\nexport function createVercelFunc(\n options: { database?: Database; mongoUri?: string } = {},\n): (req: VercelRequestLike, res: VercelResponseLike) => Promise<void> {\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 (req, res) => {\n const request = toTkRequest(req);\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: vercelCapabilities,\n postSubmit: vercelPostSubmitDispatcher,\n }),\n );\n fromTkResponse(res, await handler(request));\n };\n}\n\n/** 装配缓存(default handler 懒加载语义) */\nlet handlerFn: ((req: VercelRequestLike, res: VercelResponseLike) => Promise<void>) | null = null;\n\n/**\n * Vercel Serverless Function 默认入口。\n * @param req Vercel/Node 请求\n * @param res Vercel/Node 响应\n */\nexport default async function vercelHandler(\n req: VercelRequestLike,\n res: VercelResponseLike,\n): Promise<void> {\n handlerFn ??= createVercelFunc();\n await handlerFn(req, res);\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { FULL_CAPABILITIES, MongoDatabase, RECURSION_HEADER, createHandler, getAxios, getRecursionToken, scaffoldAdapters } from "@twikoojs/common";
|
|
2
|
+
//#region src/dispatch.ts
|
|
3
|
+
/**
|
|
4
|
+
* Vercel 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。
|
|
5
|
+
*
|
|
6
|
+
* **1.x 语义对齐**:COMMENT_SUBMIT 保存评论后,用 **HTTP 递归自调用**把
|
|
7
|
+
* POST_SUBMIT 送到另一个函数实例执行——垃圾检测与邮件/IM 通知因此拥有
|
|
8
|
+
* 独立的执行时间预算,既不会拖慢用户请求,也不会因为用户请求的超时上限
|
|
9
|
+
* 而整条失败(1.x 实现见 `src/server/vercel/api/index.js` 的 `commentSubmit`)。
|
|
10
|
+
*
|
|
11
|
+
* **为什么放在适配器**:这是平台专有机制,common 不应感知。平台相关部分只有
|
|
12
|
+
* 两处:
|
|
13
|
+
* - 自身地址:`TWIKOO_SELF_URL`(显式覆写)→ `VERCEL_URL`(平台注入);
|
|
14
|
+
* - 内部派发令牌:走 `x-twikoo-recursion` 请求头(1.x 同名,跨版本可互认)。
|
|
15
|
+
*
|
|
16
|
+
* 副作用链本身仍在 common 的 postSubmit 服务里。
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* 派发等待上限(毫秒)。1.x 为 `Promise.race` 5 秒:超时只表示「本请求不再
|
|
20
|
+
* 继续等」,被调用的实例仍在自己的预算内跑完副作用。
|
|
21
|
+
*/
|
|
22
|
+
const DISPATCH_TIMEOUT_MS = 5e3;
|
|
23
|
+
/**
|
|
24
|
+
* 解析自身函数地址(显式覆写优先,其次平台注入的 VERCEL_URL)。
|
|
25
|
+
* @returns 绝对地址;两者皆缺失时为空串
|
|
26
|
+
*/
|
|
27
|
+
function resolveSelfUrl() {
|
|
28
|
+
const explicit = process.env.TWIKOO_SELF_URL;
|
|
29
|
+
if (explicit) return explicit;
|
|
30
|
+
const vercelUrl = process.env.VERCEL_URL;
|
|
31
|
+
return vercelUrl ? `https://${vercelUrl}` : "";
|
|
32
|
+
}
|
|
33
|
+
/** Vercel 的 POST_SUBMIT 派发实现(HTTP 递归自调用) */
|
|
34
|
+
const vercelPostSubmitDispatcher = {
|
|
35
|
+
/**
|
|
36
|
+
* 自调用本函数执行 POST_SUBMIT(不等待副作用完成,只等有界竞速)。
|
|
37
|
+
* @param comment 已入库的评论
|
|
38
|
+
* @param ctx 当前请求上下文(用于取递归令牌与记录日志)
|
|
39
|
+
*/
|
|
40
|
+
async dispatch(comment, ctx) {
|
|
41
|
+
const url = resolveSelfUrl();
|
|
42
|
+
if (!url) {
|
|
43
|
+
ctx.logger.warn("POST_SUBMIT 派发跳过:未取到自身地址(TWIKOO_SELF_URL / VERCEL_URL)");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const axios = await getAxios();
|
|
47
|
+
await Promise.race([axios.post(url, {
|
|
48
|
+
event: "POST_SUBMIT",
|
|
49
|
+
comment
|
|
50
|
+
}, { headers: { [RECURSION_HEADER]: getRecursionToken(ctx.config) } }), new Promise((resolve) => setTimeout(resolve, DISPATCH_TIMEOUT_MS))]);
|
|
51
|
+
} };
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/main.ts
|
|
54
|
+
/**
|
|
55
|
+
* twikoo-vercel 主逻辑(Vercel 薄适配器,规范)。
|
|
56
|
+
* 业务逻辑全部在 @twikoojs/common;平台核对:vercel.com/docs/functions(查阅 2026-09-17)。
|
|
57
|
+
*
|
|
58
|
+
* 后置副作用(垃圾检测 + 通知)经 {@link vercelPostSubmitDispatcher} 以
|
|
59
|
+
* HTTP 递归自调用派发到独立执行单元,见 `./dispatch.ts`。
|
|
60
|
+
*/
|
|
61
|
+
/** Vercel 平台能力:全能力(能力矩阵)*/
|
|
62
|
+
const vercelCapabilities = FULL_CAPABILITIES;
|
|
63
|
+
/**
|
|
64
|
+
* Vercel 请求 → 内部统一请求(headers 小写化;query String 化;x-real-ip/转发首跳取 IP)。
|
|
65
|
+
* @param req Vercel/Node 请求
|
|
66
|
+
* @returns 内部统一请求
|
|
67
|
+
*/
|
|
68
|
+
function toTkRequest(req) {
|
|
69
|
+
const headers = {};
|
|
70
|
+
for (const [key, value] of Object.entries(req.headers ?? {})) if (value !== void 0) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
71
|
+
const query = {};
|
|
72
|
+
for (const [key, value] of Object.entries(req.query ?? {})) query[key] = Array.isArray(value) ? String(value[0]) : String(value ?? "");
|
|
73
|
+
const forwarded = headers["x-forwarded-for"];
|
|
74
|
+
const ip = headers["x-real-ip"] ?? (forwarded ? forwarded.split(",")[0].trim() : "") ?? "";
|
|
75
|
+
const body = req.body && typeof req.body === "object" ? req.body : {};
|
|
76
|
+
return {
|
|
77
|
+
method: String(req.method ?? "POST").toUpperCase(),
|
|
78
|
+
path: String(req.url ?? "/"),
|
|
79
|
+
query,
|
|
80
|
+
body,
|
|
81
|
+
headers,
|
|
82
|
+
ip,
|
|
83
|
+
raw: req
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** 内部统一响应 → Vercel 响应(状态码 + CORS 头 + JSON 体;204 无体)。 */
|
|
87
|
+
function fromTkResponse(res, tkRes) {
|
|
88
|
+
res.status(tkRes.status);
|
|
89
|
+
for (const [name, value] of Object.entries(tkRes.headers)) res.setHeader(name, value);
|
|
90
|
+
if (tkRes.status === 204) {
|
|
91
|
+
res.end();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
res.json(tkRes.body);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 创建 Vercel 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连,1.x 缓存语义)。
|
|
98
|
+
* @param options 注入项(database:Database 实现;mongoUri:连接串覆写)
|
|
99
|
+
* @returns 逐请求处理器 `(req, res) => Promise<void>`
|
|
100
|
+
*/
|
|
101
|
+
function createVercelFunc(options = {}) {
|
|
102
|
+
let database = options.database ?? null;
|
|
103
|
+
/** 懒建连(inject 优先;init 幂等) */
|
|
104
|
+
const getDatabase = async () => {
|
|
105
|
+
database ??= new MongoDatabase({ uri: options.mongoUri ?? process.env.MONGODB_URI ?? "" });
|
|
106
|
+
await database.init();
|
|
107
|
+
return database;
|
|
108
|
+
};
|
|
109
|
+
return async (req, res) => {
|
|
110
|
+
const request = toTkRequest(req);
|
|
111
|
+
fromTkResponse(res, await createHandler(scaffoldAdapters({
|
|
112
|
+
request: {
|
|
113
|
+
/** 事件即请求体(闭包透传,见 toTkRequest) */
|
|
114
|
+
toTkRequest: () => request },
|
|
115
|
+
response: {
|
|
116
|
+
/** TkResponse 恒等透传(平台写入见 fromTkResponse) */
|
|
117
|
+
fromTkResponse: (r) => r },
|
|
118
|
+
database: await getDatabase(),
|
|
119
|
+
capabilities: vercelCapabilities,
|
|
120
|
+
postSubmit: vercelPostSubmitDispatcher
|
|
121
|
+
}))(request));
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** 装配缓存(default handler 懒加载语义) */
|
|
125
|
+
let handlerFn = null;
|
|
126
|
+
/**
|
|
127
|
+
* Vercel Serverless Function 默认入口。
|
|
128
|
+
* @param req Vercel/Node 请求
|
|
129
|
+
* @param res Vercel/Node 响应
|
|
130
|
+
*/
|
|
131
|
+
async function vercelHandler(req, res) {
|
|
132
|
+
handlerFn ??= createVercelFunc();
|
|
133
|
+
await handlerFn(req, res);
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { createVercelFunc, vercelHandler as default, fromTkResponse, toTkRequest, vercelPostSubmitDispatcher };
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/dispatch.ts","../src/main.ts"],"sourcesContent":["/**\n * Vercel 的 POST_SUBMIT 派发实现(规范「后置副作用异步语义」)。\n *\n * **1.x 语义对齐**:COMMENT_SUBMIT 保存评论后,用 **HTTP 递归自调用**把\n * POST_SUBMIT 送到另一个函数实例执行——垃圾检测与邮件/IM 通知因此拥有\n * 独立的执行时间预算,既不会拖慢用户请求,也不会因为用户请求的超时上限\n * 而整条失败(1.x 实现见 `src/server/vercel/api/index.js` 的 `commentSubmit`)。\n *\n * **为什么放在适配器**:这是平台专有机制,common 不应感知。平台相关部分只有\n * 两处:\n * - 自身地址:`TWIKOO_SELF_URL`(显式覆写)→ `VERCEL_URL`(平台注入);\n * - 内部派发令牌:走 `x-twikoo-recursion` 请求头(1.x 同名,跨版本可互认)。\n *\n * 副作用链本身仍在 common 的 postSubmit 服务里。\n */\nimport {\n RECURSION_HEADER,\n getAxios,\n getRecursionToken,\n type PostSubmitDispatcher,\n} from \"@twikoojs/common\";\n\n/**\n * 派发等待上限(毫秒)。1.x 为 `Promise.race` 5 秒:超时只表示「本请求不再\n * 继续等」,被调用的实例仍在自己的预算内跑完副作用。\n */\nconst DISPATCH_TIMEOUT_MS = 5000;\n\n/**\n * 解析自身函数地址(显式覆写优先,其次平台注入的 VERCEL_URL)。\n * @returns 绝对地址;两者皆缺失时为空串\n */\nfunction resolveSelfUrl(): string {\n const explicit = process.env.TWIKOO_SELF_URL;\n if (explicit) return explicit;\n const vercelUrl = process.env.VERCEL_URL;\n return vercelUrl ? `https://${vercelUrl}` : \"\";\n}\n\n/** Vercel 的 POST_SUBMIT 派发实现(HTTP 递归自调用) */\nexport const vercelPostSubmitDispatcher: PostSubmitDispatcher = {\n /**\n * 自调用本函数执行 POST_SUBMIT(不等待副作用完成,只等有界竞速)。\n * @param comment 已入库的评论\n * @param ctx 当前请求上下文(用于取递归令牌与记录日志)\n */\n async dispatch(comment, ctx): Promise<void> {\n const url = resolveSelfUrl();\n if (!url) {\n ctx.logger.warn(\"POST_SUBMIT 派发跳过:未取到自身地址(TWIKOO_SELF_URL / VERCEL_URL)\");\n return;\n }\n const axios = await getAxios();\n await Promise.race([\n axios.post(\n url,\n { event: \"POST_SUBMIT\", comment },\n { headers: { [RECURSION_HEADER]: getRecursionToken(ctx.config) } },\n ),\n new Promise((resolve) => setTimeout(resolve, DISPATCH_TIMEOUT_MS)),\n ]);\n },\n};\n","/**\n * twikoo-vercel 主逻辑(Vercel 薄适配器,规范)。\n * 业务逻辑全部在 @twikoojs/common;平台核对:vercel.com/docs/functions(查阅 2026-09-17)。\n *\n * 后置副作用(垃圾检测 + 通知)经 {@link vercelPostSubmitDispatcher} 以\n * HTTP 递归自调用派发到独立执行单元,见 `./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 { vercelPostSubmitDispatcher } from \"./dispatch\";\n\n/** Vercel 平台能力:全能力(能力矩阵)*/\nconst vercelCapabilities = FULL_CAPABILITIES;\n\n/** Vercel 请求的最小结构面(Node IncomingMessage + Vercel 扩展) */\nexport interface VercelRequestLike {\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n query?: Record<string, string | string[] | undefined>;\n body?: unknown;\n url?: string;\n}\n\n/** Vercel 响应的最小结构面(Node ServerResponse) */\nexport interface VercelResponseLike {\n status(code: number): VercelResponseLike;\n setHeader(name: string, value: string): VercelResponseLike;\n json(body: unknown): VercelResponseLike;\n end(): VercelResponseLike;\n}\n\n/**\n * Vercel 请求 → 内部统一请求(headers 小写化;query String 化;x-real-ip/转发首跳取 IP)。\n * @param req Vercel/Node 请求\n * @returns 内部统一请求\n */\nexport function toTkRequest(req: VercelRequestLike): TkRequest {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers ?? {})) {\n if (value !== undefined) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n const query: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.query ?? {})) {\n query[key] = Array.isArray(value) ? String(value[0]) : String(value ?? \"\");\n }\n const forwarded = headers[\"x-forwarded-for\"];\n const ip = headers[\"x-real-ip\"] ?? (forwarded ? forwarded.split(\",\")[0].trim() : \"\") ?? \"\";\n const body = (req.body && typeof req.body === \"object\" ? req.body : {}) as TkRequest[\"body\"];\n return {\n method: String(req.method ?? \"POST\").toUpperCase(),\n path: String(req.url ?? \"/\"),\n query,\n body,\n headers,\n ip,\n raw: req,\n };\n}\n\n/** 内部统一响应 → Vercel 响应(状态码 + CORS 头 + JSON 体;204 无体)。 */\nexport function fromTkResponse(res: VercelResponseLike, tkRes: TkResponse): void {\n res.status(tkRes.status);\n for (const [name, value] of Object.entries(tkRes.headers)) {\n res.setHeader(name, value);\n }\n if (tkRes.status === 204) {\n res.end();\n return;\n }\n res.json(tkRes.body);\n}\n\n/**\n * 创建 Vercel 请求处理器(database 可注入;缺省按 MONGODB_URI 懒建连,1.x 缓存语义)。\n * @param options 注入项(database:Database 实现;mongoUri:连接串覆写)\n * @returns 逐请求处理器 `(req, res) => Promise<void>`\n */\nexport function createVercelFunc(\n options: { database?: Database; mongoUri?: string } = {},\n): (req: VercelRequestLike, res: VercelResponseLike) => Promise<void> {\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 (req, res) => {\n const request = toTkRequest(req);\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: vercelCapabilities,\n postSubmit: vercelPostSubmitDispatcher,\n }),\n );\n fromTkResponse(res, await handler(request));\n };\n}\n\n/** 装配缓存(default handler 懒加载语义) */\nlet handlerFn: ((req: VercelRequestLike, res: VercelResponseLike) => Promise<void>) | null = null;\n\n/**\n * Vercel Serverless Function 默认入口。\n * @param req Vercel/Node 请求\n * @param res Vercel/Node 响应\n */\nexport default async function vercelHandler(\n req: VercelRequestLike,\n res: VercelResponseLike,\n): Promise<void> {\n handlerFn ??= createVercelFunc();\n await handlerFn(req, res);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,sBAAsB;;;;;AAM5B,SAAS,iBAAyB;CAChC,MAAM,WAAW,QAAQ,IAAI;CAC7B,IAAI,UAAU,OAAO;CACrB,MAAM,YAAY,QAAQ,IAAI;CAC9B,OAAO,YAAY,WAAW,cAAc;AAC9C;;AAGA,MAAa,6BAAmD;;;;;;AAM9D,MAAM,SAAS,SAAS,KAAoB;CAC1C,MAAM,MAAM,eAAe;CAC3B,IAAI,CAAC,KAAK;EACR,IAAI,OAAO,KAAK,wDAAwD;EACxE;CACF;CACA,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,QAAQ,KAAK,CACjB,MAAM,KACJ,KACA;EAAE,OAAO;EAAe;CAAQ,GAChC,EAAE,SAAS,GAAG,mBAAmB,kBAAkB,IAAI,MAAM,EAAE,EAAE,CACnE,GACA,IAAI,SAAS,YAAY,WAAW,SAAS,mBAAmB,CAAC,CACnE,CAAC;AACH,EACF;;;;;;;;;;;AC3CA,MAAM,qBAAqB;;;;;;AAwB3B,SAAgB,YAAY,KAAmC;CAC7D,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,WAAW,CAAC,CAAC,GACzD,IAAI,UAAU,KAAA,GAAW,QAAQ,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;CAE1F,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,SAAS,CAAC,CAAC,GACvD,MAAM,OAAO,MAAM,QAAQ,KAAK,IAAI,OAAO,MAAM,EAAE,IAAI,OAAO,SAAS,EAAE;CAE3E,MAAM,YAAY,QAAQ;CAC1B,MAAM,KAAK,QAAQ,iBAAiB,YAAY,UAAU,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,OAAO;CACxF,MAAM,OAAQ,IAAI,QAAQ,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,CAAC;CACrE,OAAO;EACL,QAAQ,OAAO,IAAI,UAAU,MAAM,CAAC,CAAC,YAAY;EACjD,MAAM,OAAO,IAAI,OAAO,GAAG;EAC3B;EACA;EACA;EACA;EACA,KAAK;CACP;AACF;;AAGA,SAAgB,eAAe,KAAyB,OAAyB;CAC/E,IAAI,OAAO,MAAM,MAAM;CACvB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,GACtD,IAAI,UAAU,MAAM,KAAK;CAE3B,IAAI,MAAM,WAAW,KAAK;EACxB,IAAI,IAAI;EACR;CACF;CACA,IAAI,KAAK,MAAM,IAAI;AACrB;;;;;;AAOA,SAAgB,iBACd,UAAsD,CAAC,GACa;CACpE,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,KAAK,QAAQ;EACzB,MAAM,UAAU,YAAY,GAAG;EAgB/B,eAAe,KAAK,MAfJ,cACd,iBAAiB;GACf,SAAS;;AAEP,mBAAmB,QACrB;GACA,UAAU;;AAER,iBAAiB,MAAkB,EACrC;GACA,UAAU,MAAM,YAAY;GAC5B,cAAc;GACd,YAAY;EACd,CAAC,CAE6B,CAAC,CAAC,OAAO,CAAC;CAC5C;AACF;;AAGA,IAAI,YAAyF;;;;;;AAO7F,eAA8B,cAC5B,KACA,KACe;CACf,cAAc,iBAAiB;CAC/B,MAAM,UAAU,KAAK,GAAG;AAC1B"}
|
package/package.json
CHANGED
|
@@ -1,19 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twikoo-vercel",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "imaegoo <hello@imaegoo.com> (https://github.com/imaegoo)",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"description": "Twikoo comment system backend for Vercel",
|
|
6
5
|
"license": "MIT",
|
|
7
|
-
"main": "api/index.js",
|
|
8
6
|
"repository": {
|
|
9
7
|
"type": "git",
|
|
10
8
|
"url": "https://github.com/twikoojs/twikoo.git"
|
|
11
9
|
},
|
|
12
|
-
"
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
},
|
|
13
28
|
"dependencies": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
29
|
+
"@cap.js/server": "^4.0.5",
|
|
30
|
+
"@imaegoo/node-ip2region": "^2.1.1",
|
|
31
|
+
"@twikoojs/common": "workspace:*",
|
|
32
|
+
"@twikoojs/shared": "workspace:*",
|
|
33
|
+
"@xsai/generate-text": "^0.5.0",
|
|
34
|
+
"akismet-api": "^6.0.0",
|
|
35
|
+
"axios": "^1.20.0",
|
|
36
|
+
"bowser": "^2.14.1",
|
|
37
|
+
"dompurify": "^3.4.15",
|
|
38
|
+
"form-data": "^4.0.6",
|
|
39
|
+
"html-to-text": "^10.0.1",
|
|
40
|
+
"jsdom": "^30.1.0",
|
|
41
|
+
"marked": "^18.0.13",
|
|
42
|
+
"mongodb": "^7.6.0",
|
|
43
|
+
"nodemailer": "^10.0.10",
|
|
44
|
+
"pushoo": "workspace:^",
|
|
45
|
+
"tencentcloud-sdk-nodejs-tms": "^4.1.311",
|
|
46
|
+
"xml2js": "^0.6.2"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^26.6.1"
|
|
18
50
|
}
|
|
19
51
|
}
|