tkserver 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/LICENSE +21 -0
- package/README.md +21 -20
- package/dist/index.d.mts +97 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +234 -0
- package/dist/index.mjs.map +1 -0
- package/dist/seed-BAmtKrez.js +337 -0
- package/dist/seed-BAmtKrez.js.map +1 -0
- package/dist/seed-C8kiwlWr.mjs +337 -0
- package/dist/seed-C8kiwlWr.mjs.map +1 -0
- package/dist/seed-DZgEijDW.js +338 -0
- package/dist/seed-DZgEijDW.js.map +1 -0
- package/dist/server.js +245 -0
- package/dist/server.js.map +1 -0
- package/package.json +44 -25
- package/index.js +0 -1224
- package/mongo.js +0 -1181
- package/server.js +0 -125
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,27 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tkserver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Twikoo 2.0 服务端适配器。业务逻辑在 `@twikoojs/common`,本包仅做平台入口与注入。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 部署(自托管 / Docker / pkg)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
6
8
|
npm i -g tkserver
|
|
9
|
+
tkserver # 默认监听 ::8080
|
|
7
10
|
```
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
Docker:`docker run -d -p 8080:8080 -v /data:/app/data imaegoo/twikoo`
|
|
13
|
+
数据目录:`TWIKOO_DATA`(缺省 ./data,LokiJS 文件库 db.json)
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
## 平台核对清单(查阅日期 2026-09-17)
|
|
16
|
+
|
|
17
|
+
- [x] HTTP server 与信号处理(SIGTERM/SIGINT 优雅退出,关闭期间 503)——nodejs.org/api
|
|
18
|
+
- [x] 容器内环境变量与数据目录挂载(TWIKOO_DATA)
|
|
19
|
+
- [x] 限流计数定时清理(TWIKOO_REQ_TIMES_CLEAR_TIME 缺省 10 分钟)
|
|
20
|
+
- [ ] Docker arm32v7 镜像实测(人工项)
|
|
21
|
+
- [ ] pkg/SEA 单文件运行时实测(人工项)
|
|
22
|
+
|
|
23
|
+
## 环境
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| `MONGODB_URI` | MongoDB 数据库连接字符串,不传则使用 lokijs | `null` |
|
|
20
|
-
| `MONGO_URL` | MongoDB 数据库连接字符串,不传则使用 lokijs | `null` |
|
|
21
|
-
| `TWIKOO_DATA` | lokijs 数据库存储路径 | `./data` |
|
|
22
|
-
| `TWIKOO_HOST` | 自定义监听的主机名或IP地址(例如 0.0.0.0 或 127.0.0.1),设置该值则会忽略 TWIKOO_LOCALHOST_ONLY,默认值为 null 但实际行为会回退到 `::` | `null` |
|
|
23
|
-
| `TWIKOO_PORT` | 端口号 | `8080` |
|
|
24
|
-
| `TWIKOO_THROTTLE` | IP 请求限流,当同一 IP 短时间内请求次数超过阈值将对该 IP 返回错误 | `250` |
|
|
25
|
-
| `TWIKOO_LOCALHOST_ONLY` | 为`true`时只监听本地请求,使得 nginx 等服务器反代之后不暴露原始端口 | `null` |
|
|
26
|
-
| `TWIKOO_LOG_LEVEL` | 日志级别,支持 `verbose` / `info` / `warn` / `error` | `info` |
|
|
27
|
-
| `TWIKOO_IP_HEADERS` | 在一些特殊情况下使用,如使用了`CloudFlare CDN` 它会将请求 IP 写到请求头的 `cf-connecting-ip` 字段上,为了能够正确的获取请求 IP 你可以写成 `['headers.cf-connecting-ip']` | `[]` |
|
|
25
|
+
- `TWIKOO_PORT`(8080)、`TWIKOO_HOST`(::)、`TWIKOO_LOCALHOST_ONLY`
|
|
26
|
+
- `MONGODB_URI`(可选,设置后改用 Mongo)
|
|
27
|
+
- `TWIKOO_DATA`(Loki 数据目录)、`TWIKOO_SHUTDOWN_TIMEOUT`
|
|
28
|
+
- `TWIKOO_SEED=1`(demo 数据)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Database, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
import { Server } from "node:http";
|
|
3
|
+
//#region src/main.d.ts
|
|
4
|
+
/** Node 请求的最小结构面(IncomingMessage;body 已由 server.ts 解析挂载) */
|
|
5
|
+
interface ServerRequestLike {
|
|
6
|
+
method?: string;
|
|
7
|
+
headers: Record<string, string | string[] | undefined>;
|
|
8
|
+
url?: string;
|
|
9
|
+
body?: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Node 响应的最小结构面(ServerResponse + 1.x status/json 垫片) */
|
|
12
|
+
interface ServerResponseLike {
|
|
13
|
+
statusCode?: number;
|
|
14
|
+
writableEnded?: boolean;
|
|
15
|
+
writeHead(code: number, headers: Record<string, string>): unknown;
|
|
16
|
+
end(body?: string): unknown;
|
|
17
|
+
status(code: number): ServerResponseLike;
|
|
18
|
+
json(body: unknown): ServerResponseLike;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Node 请求 → 内部统一请求(headers 小写化;x-real-ip/转发首跳取 IP)。
|
|
22
|
+
* @param req Node 请求
|
|
23
|
+
* @returns 内部统一请求
|
|
24
|
+
*/
|
|
25
|
+
export declare function toTkRequest(req: ServerRequestLike): TkRequest;
|
|
26
|
+
/**
|
|
27
|
+
* 内部统一响应 → Node 响应(204 无体;业务 JSON 透传 tkRes.status;
|
|
28
|
+
* headers 原样回写——CORS 由 pipeline 算好,业务分支在其后补 Content-Type;
|
|
29
|
+
* 垫片语义对齐 1.x)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function fromTkResponse(res: ServerResponseLike, tkRes: TkResponse): void;
|
|
32
|
+
/**
|
|
33
|
+
* 创建 tkserver 请求处理器(database 可注入;缺省按 MONGODB_URI/TWIKOO_DATA 选择)。
|
|
34
|
+
* @param options 注入项
|
|
35
|
+
* @returns 逐请求处理器
|
|
36
|
+
*/
|
|
37
|
+
export declare function createTkserverHandler(options?: {
|
|
38
|
+
database?: Database;
|
|
39
|
+
dataDir?: string;
|
|
40
|
+
mongoUri?: string;
|
|
41
|
+
}): (req: ServerRequestLike, res: ServerResponseLike) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* 优雅退出(1.x shutdown 语义:停限流清理定时器 → 关数据库)。
|
|
44
|
+
* @param options 清理项
|
|
45
|
+
*/
|
|
46
|
+
export declare function shutdown(options?: {
|
|
47
|
+
timer?: NodeJS.Timeout | null;
|
|
48
|
+
database?: Database | null;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/** requestTimes 清理周期(1.x TWIKOO_REQ_TIMES_CLEAR_TIME,缺省 10 分钟) */
|
|
51
|
+
export declare function getRequestTimesClearInterval(): number;
|
|
52
|
+
/** 启动限流计数定时清理(返回定时器供 shutdown 停用) */
|
|
53
|
+
export declare function startRequestTimesTimer(): NodeJS.Timeout;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/database.d.ts
|
|
56
|
+
/**
|
|
57
|
+
* 按环境选择数据库实现。
|
|
58
|
+
* @param options 数据目录 / 连接串覆盖项
|
|
59
|
+
* @returns 数据库实例(尚未 init)
|
|
60
|
+
*/
|
|
61
|
+
export declare function createTkserverDatabase(options?: {
|
|
62
|
+
dataDir?: string;
|
|
63
|
+
mongoUri?: string;
|
|
64
|
+
}): Database;
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/server.d.ts
|
|
67
|
+
/** 服务器实例与生命周期控制(createTkserverServer 产物) */
|
|
68
|
+
interface TkserverInstance {
|
|
69
|
+
/** HTTP 服务器(未监听;listen 由调用方执行) */
|
|
70
|
+
server: Server;
|
|
71
|
+
/** 已装配的数据库实例(供启动期 seed 复用)*/
|
|
72
|
+
database: Database;
|
|
73
|
+
/** 优雅关闭(关监听 → 排空连接 → 停定时器 → 关数据库) */
|
|
74
|
+
gracefulShutdown: () => Promise<void>;
|
|
75
|
+
/** 注册 SIGTERM/SIGINT 处理(TWIKOO_SHUTDOWN_TIMEOUT 兜底强杀) */
|
|
76
|
+
registerSignalHandlers: () => void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 创建 tkserver 服务器实例(不监听、不注册信号——由 bin 底部或测试驱动)。
|
|
80
|
+
* @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)
|
|
81
|
+
* @returns 服务器实例与生命周期控制
|
|
82
|
+
*/
|
|
83
|
+
export declare function createTkserverServer(options?: {
|
|
84
|
+
database?: Database;
|
|
85
|
+
}): TkserverInstance;
|
|
86
|
+
/**
|
|
87
|
+
* 启动 tkserver(bin `dist/server.js` 与 `twikoo-pkg` 的 SEA 产物共用入口):
|
|
88
|
+
* 可选 demo seed → 监听端口 → 注册信号处理。
|
|
89
|
+
* @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)
|
|
90
|
+
* @returns 已进入监听的服务器实例
|
|
91
|
+
*/
|
|
92
|
+
export declare function startTkserver(options?: {
|
|
93
|
+
database?: Database;
|
|
94
|
+
}): Promise<TkserverInstance>;
|
|
95
|
+
//#endregion
|
|
96
|
+
export type { TkserverInstance };
|
|
97
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Database, TkRequest, TkResponse } from "@twikoojs/common";
|
|
2
|
+
import { Server } from "node:http";
|
|
3
|
+
//#region src/main.d.ts
|
|
4
|
+
/** Node 请求的最小结构面(IncomingMessage;body 已由 server.ts 解析挂载) */
|
|
5
|
+
interface ServerRequestLike {
|
|
6
|
+
method?: string;
|
|
7
|
+
headers: Record<string, string | string[] | undefined>;
|
|
8
|
+
url?: string;
|
|
9
|
+
body?: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Node 响应的最小结构面(ServerResponse + 1.x status/json 垫片) */
|
|
12
|
+
interface ServerResponseLike {
|
|
13
|
+
statusCode?: number;
|
|
14
|
+
writableEnded?: boolean;
|
|
15
|
+
writeHead(code: number, headers: Record<string, string>): unknown;
|
|
16
|
+
end(body?: string): unknown;
|
|
17
|
+
status(code: number): ServerResponseLike;
|
|
18
|
+
json(body: unknown): ServerResponseLike;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Node 请求 → 内部统一请求(headers 小写化;x-real-ip/转发首跳取 IP)。
|
|
22
|
+
* @param req Node 请求
|
|
23
|
+
* @returns 内部统一请求
|
|
24
|
+
*/
|
|
25
|
+
export declare function toTkRequest(req: ServerRequestLike): TkRequest;
|
|
26
|
+
/**
|
|
27
|
+
* 内部统一响应 → Node 响应(204 无体;业务 JSON 透传 tkRes.status;
|
|
28
|
+
* headers 原样回写——CORS 由 pipeline 算好,业务分支在其后补 Content-Type;
|
|
29
|
+
* 垫片语义对齐 1.x)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function fromTkResponse(res: ServerResponseLike, tkRes: TkResponse): void;
|
|
32
|
+
/**
|
|
33
|
+
* 创建 tkserver 请求处理器(database 可注入;缺省按 MONGODB_URI/TWIKOO_DATA 选择)。
|
|
34
|
+
* @param options 注入项
|
|
35
|
+
* @returns 逐请求处理器
|
|
36
|
+
*/
|
|
37
|
+
export declare function createTkserverHandler(options?: {
|
|
38
|
+
database?: Database;
|
|
39
|
+
dataDir?: string;
|
|
40
|
+
mongoUri?: string;
|
|
41
|
+
}): (req: ServerRequestLike, res: ServerResponseLike) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* 优雅退出(1.x shutdown 语义:停限流清理定时器 → 关数据库)。
|
|
44
|
+
* @param options 清理项
|
|
45
|
+
*/
|
|
46
|
+
export declare function shutdown(options?: {
|
|
47
|
+
timer?: NodeJS.Timeout | null;
|
|
48
|
+
database?: Database | null;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/** requestTimes 清理周期(1.x TWIKOO_REQ_TIMES_CLEAR_TIME,缺省 10 分钟) */
|
|
51
|
+
export declare function getRequestTimesClearInterval(): number;
|
|
52
|
+
/** 启动限流计数定时清理(返回定时器供 shutdown 停用) */
|
|
53
|
+
export declare function startRequestTimesTimer(): NodeJS.Timeout;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/database.d.ts
|
|
56
|
+
/**
|
|
57
|
+
* 按环境选择数据库实现。
|
|
58
|
+
* @param options 数据目录 / 连接串覆盖项
|
|
59
|
+
* @returns 数据库实例(尚未 init)
|
|
60
|
+
*/
|
|
61
|
+
export declare function createTkserverDatabase(options?: {
|
|
62
|
+
dataDir?: string;
|
|
63
|
+
mongoUri?: string;
|
|
64
|
+
}): Database;
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/server.d.ts
|
|
67
|
+
/** 服务器实例与生命周期控制(createTkserverServer 产物) */
|
|
68
|
+
interface TkserverInstance {
|
|
69
|
+
/** HTTP 服务器(未监听;listen 由调用方执行) */
|
|
70
|
+
server: Server;
|
|
71
|
+
/** 已装配的数据库实例(供启动期 seed 复用)*/
|
|
72
|
+
database: Database;
|
|
73
|
+
/** 优雅关闭(关监听 → 排空连接 → 停定时器 → 关数据库) */
|
|
74
|
+
gracefulShutdown: () => Promise<void>;
|
|
75
|
+
/** 注册 SIGTERM/SIGINT 处理(TWIKOO_SHUTDOWN_TIMEOUT 兜底强杀) */
|
|
76
|
+
registerSignalHandlers: () => void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 创建 tkserver 服务器实例(不监听、不注册信号——由 bin 底部或测试驱动)。
|
|
80
|
+
* @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)
|
|
81
|
+
* @returns 服务器实例与生命周期控制
|
|
82
|
+
*/
|
|
83
|
+
export declare function createTkserverServer(options?: {
|
|
84
|
+
database?: Database;
|
|
85
|
+
}): TkserverInstance;
|
|
86
|
+
/**
|
|
87
|
+
* 启动 tkserver(bin `dist/server.js` 与 `twikoo-pkg` 的 SEA 产物共用入口):
|
|
88
|
+
* 可选 demo seed → 监听端口 → 注册信号处理。
|
|
89
|
+
* @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)
|
|
90
|
+
* @returns 已进入监听的服务器实例
|
|
91
|
+
*/
|
|
92
|
+
export declare function startTkserver(options?: {
|
|
93
|
+
database?: Database;
|
|
94
|
+
}): Promise<TkserverInstance>;
|
|
95
|
+
//#endregion
|
|
96
|
+
export type { TkserverInstance };
|
|
97
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _twikoojs_common = require("@twikoojs/common");
|
|
3
|
+
let node_http = require("node:http");
|
|
4
|
+
//#region src/database.ts
|
|
5
|
+
/**
|
|
6
|
+
* tkserver 数据库选择(1.x `server.js` 语义:`MONGODB_URI`/`MONGO_URL` → Mongo,
|
|
7
|
+
* 否则 Loki + `TWIKOO_DATA`)。
|
|
8
|
+
*
|
|
9
|
+
* 独立成文件的原因:启动路径需要**先**拿到数据库实例做 demo seed,
|
|
10
|
+
* 与逐请求惰性初始化共用同一份选择逻辑。
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* 按环境选择数据库实现。
|
|
14
|
+
* @param options 数据目录 / 连接串覆盖项
|
|
15
|
+
* @returns 数据库实例(尚未 init)
|
|
16
|
+
*/
|
|
17
|
+
function createTkserverDatabase(options = {}) {
|
|
18
|
+
const mongoUri = options.mongoUri ?? process.env.MONGODB_URI ?? process.env.MONGO_URL ?? "";
|
|
19
|
+
return mongoUri ? new _twikoojs_common.MongoDatabase({ uri: mongoUri }) : new _twikoojs_common.LokiDatabase({ dataDir: options.dataDir ?? process.env.TWIKOO_DATA ?? "./data" });
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/main.ts
|
|
23
|
+
/**
|
|
24
|
+
* tkserver 主逻辑(self-hosted 薄适配器)。
|
|
25
|
+
* 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo,否则 Loki(TWIKOO_DATA)。
|
|
26
|
+
* 平台核对:nodejs.org/api http/信号处理(查阅 2026-09-17)。
|
|
27
|
+
*/
|
|
28
|
+
/** tkserver 平台能力:全能力*/
|
|
29
|
+
const tkserverCapabilities = _twikoojs_common.FULL_CAPABILITIES;
|
|
30
|
+
/**
|
|
31
|
+
* Node 请求 → 内部统一请求(headers 小写化;x-real-ip/转发首跳取 IP)。
|
|
32
|
+
* @param req Node 请求
|
|
33
|
+
* @returns 内部统一请求
|
|
34
|
+
*/
|
|
35
|
+
function toTkRequest(req) {
|
|
36
|
+
const headers = {};
|
|
37
|
+
for (const [key, value] of Object.entries(req.headers ?? {})) if (value !== void 0) headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
|
|
38
|
+
const forwarded = headers["x-forwarded-for"];
|
|
39
|
+
const ip = headers["x-real-ip"] ?? (forwarded ? forwarded.split(",")[0].trim() : "") ?? "";
|
|
40
|
+
const body = req.body && typeof req.body === "object" ? req.body : {};
|
|
41
|
+
return {
|
|
42
|
+
method: String(req.method ?? "POST").toUpperCase(),
|
|
43
|
+
path: String(req.url ?? "/"),
|
|
44
|
+
query: {},
|
|
45
|
+
body,
|
|
46
|
+
headers,
|
|
47
|
+
ip,
|
|
48
|
+
raw: req
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 内部统一响应 → Node 响应(204 无体;业务 JSON 透传 tkRes.status;
|
|
53
|
+
* headers 原样回写——CORS 由 pipeline 算好,业务分支在其后补 Content-Type;
|
|
54
|
+
* 垫片语义对齐 1.x)。
|
|
55
|
+
*/
|
|
56
|
+
function fromTkResponse(res, tkRes) {
|
|
57
|
+
if (tkRes.status === 204) {
|
|
58
|
+
res.writeHead(204, tkRes.headers);
|
|
59
|
+
res.end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.writeHead(tkRes.status, {
|
|
63
|
+
...tkRes.headers,
|
|
64
|
+
"Content-Type": "application/json"
|
|
65
|
+
});
|
|
66
|
+
res.end(JSON.stringify(tkRes.body));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 创建 tkserver 请求处理器(database 可注入;缺省按 MONGODB_URI/TWIKOO_DATA 选择)。
|
|
70
|
+
* @param options 注入项
|
|
71
|
+
* @returns 逐请求处理器
|
|
72
|
+
*/
|
|
73
|
+
function createTkserverHandler(options = {}) {
|
|
74
|
+
let database = options.database ?? null;
|
|
75
|
+
/** 数据库选择(1.x server.js:MONGODB_URI → mongo,否则 loki) */
|
|
76
|
+
const getDatabase = async () => {
|
|
77
|
+
if (!database) database = createTkserverDatabase(options);
|
|
78
|
+
await database.init();
|
|
79
|
+
return database;
|
|
80
|
+
};
|
|
81
|
+
return async (req, res) => {
|
|
82
|
+
const request = toTkRequest(req);
|
|
83
|
+
fromTkResponse(res, await (0, _twikoojs_common.createHandler)((0, _twikoojs_common.scaffoldAdapters)({
|
|
84
|
+
request: {
|
|
85
|
+
/** 请求恒等透传(归一化见 toTkRequest) */
|
|
86
|
+
toTkRequest: () => request },
|
|
87
|
+
response: {
|
|
88
|
+
/** TkResponse 恒等透传(写入见 fromTkResponse) */
|
|
89
|
+
fromTkResponse: (r) => r },
|
|
90
|
+
database: await getDatabase(),
|
|
91
|
+
capabilities: tkserverCapabilities
|
|
92
|
+
}))(request));
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 优雅退出(1.x shutdown 语义:停限流清理定时器 → 关数据库)。
|
|
97
|
+
* @param options 清理项
|
|
98
|
+
*/
|
|
99
|
+
async function shutdown(options = {}) {
|
|
100
|
+
if (options.timer) clearInterval(options.timer);
|
|
101
|
+
await options.database?.close?.();
|
|
102
|
+
}
|
|
103
|
+
/** requestTimes 清理周期(1.x TWIKOO_REQ_TIMES_CLEAR_TIME,缺省 10 分钟) */
|
|
104
|
+
function getRequestTimesClearInterval() {
|
|
105
|
+
return parseInt(process.env.TWIKOO_REQ_TIMES_CLEAR_TIME ?? "", 10) || 6e5;
|
|
106
|
+
}
|
|
107
|
+
/** 启动限流计数定时清理(返回定时器供 shutdown 停用) */
|
|
108
|
+
function startRequestTimesTimer() {
|
|
109
|
+
return setInterval(() => (0, _twikoojs_common.resetRequestTimes)(), getRequestTimesClearInterval());
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/server.ts
|
|
113
|
+
/**
|
|
114
|
+
* tkserver bin 入口(HTTP 服务器引导;业务逻辑在 @twikoojs/common + main.ts)。
|
|
115
|
+
* 1.x server.js 语义对齐:JSON body 解析、socket 跟踪、退出信号优雅关闭、
|
|
116
|
+
* 关闭期间 503。TWIKOO_SKIP_BOOT=1 时仅导出工厂不启动(测试进程内装配)。
|
|
117
|
+
*/
|
|
118
|
+
/**
|
|
119
|
+
* 创建 tkserver 服务器实例(不监听、不注册信号——由 bin 底部或测试驱动)。
|
|
120
|
+
* @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)
|
|
121
|
+
* @returns 服务器实例与生命周期控制
|
|
122
|
+
*/
|
|
123
|
+
function createTkserverServer(options = {}) {
|
|
124
|
+
const database = options.database ?? createTkserverDatabase();
|
|
125
|
+
const handler = createTkserverHandler({ database });
|
|
126
|
+
const timer = startRequestTimesTimer();
|
|
127
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
128
|
+
let isShuttingDown = false;
|
|
129
|
+
const server = (0, node_http.createServer)((req, res) => {
|
|
130
|
+
if (isShuttingDown) {
|
|
131
|
+
res.writeHead(503, {
|
|
132
|
+
Connection: "close",
|
|
133
|
+
"Content-Type": "application/json"
|
|
134
|
+
});
|
|
135
|
+
res.end(JSON.stringify({
|
|
136
|
+
code: 503,
|
|
137
|
+
message: "Twikoo server is shutting down"
|
|
138
|
+
}));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
(async () => {
|
|
142
|
+
/** 聚合请求体(JSON 解析失败按空体处理,1.x 语义) */
|
|
143
|
+
const buffers = [];
|
|
144
|
+
for await (const chunk of req) buffers.push(chunk);
|
|
145
|
+
try {
|
|
146
|
+
req.body = JSON.parse(Buffer.concat(buffers).toString() || "{}");
|
|
147
|
+
} catch {
|
|
148
|
+
req.body = {};
|
|
149
|
+
}
|
|
150
|
+
/** status/json 垫片(1.x server.js 对齐) */
|
|
151
|
+
const shimmed = res;
|
|
152
|
+
shimmed.status = (code) => {
|
|
153
|
+
res.statusCode = code;
|
|
154
|
+
return shimmed;
|
|
155
|
+
};
|
|
156
|
+
shimmed.json = (json) => {
|
|
157
|
+
if (!res.writableEnded) {
|
|
158
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
159
|
+
res.end(JSON.stringify(json));
|
|
160
|
+
}
|
|
161
|
+
return shimmed;
|
|
162
|
+
};
|
|
163
|
+
await handler(req, shimmed);
|
|
164
|
+
})().catch(() => {
|
|
165
|
+
if (!res.writableEnded) {
|
|
166
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
167
|
+
res.end(JSON.stringify({
|
|
168
|
+
code: 1e3,
|
|
169
|
+
message: "internal error"
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
server.on("connection", (socket) => {
|
|
175
|
+
sockets.add(socket);
|
|
176
|
+
socket.on("close", () => sockets.delete(socket));
|
|
177
|
+
});
|
|
178
|
+
/** 优雅关闭(关监听 → 排空连接 → shutdown 资源) */
|
|
179
|
+
const gracefulShutdown = async () => {
|
|
180
|
+
if (isShuttingDown) return;
|
|
181
|
+
isShuttingDown = true;
|
|
182
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
183
|
+
for (const socket of sockets) socket.destroy();
|
|
184
|
+
await shutdown({ timer });
|
|
185
|
+
};
|
|
186
|
+
/** SIGTERM / SIGINT → 优雅退出(超时兜底强杀) */
|
|
187
|
+
const registerSignalHandlers = () => {
|
|
188
|
+
const timeout = parseInt(process.env.TWIKOO_SHUTDOWN_TIMEOUT ?? "", 10) || 5e3;
|
|
189
|
+
/** 信号回调:竞速优雅关闭与超时兜底 */
|
|
190
|
+
const onSignal = () => {
|
|
191
|
+
Promise.race([gracefulShutdown(), new Promise((resolve) => setTimeout(resolve, timeout))]).then(() => process.exit(0));
|
|
192
|
+
};
|
|
193
|
+
process.on("SIGTERM", onSignal);
|
|
194
|
+
process.on("SIGINT", onSignal);
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
server,
|
|
198
|
+
database,
|
|
199
|
+
gracefulShutdown,
|
|
200
|
+
registerSignalHandlers
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 启动 tkserver(bin `dist/server.js` 与 `twikoo-pkg` 的 SEA 产物共用入口):
|
|
205
|
+
* 可选 demo seed → 监听端口 → 注册信号处理。
|
|
206
|
+
* @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)
|
|
207
|
+
* @returns 已进入监听的服务器实例
|
|
208
|
+
*/
|
|
209
|
+
async function startTkserver(options = {}) {
|
|
210
|
+
const instance = createTkserverServer(options);
|
|
211
|
+
const { server, database, registerSignalHandlers } = instance;
|
|
212
|
+
const port = parseInt(process.env.TWIKOO_PORT ?? "", 10) || 8080;
|
|
213
|
+
const host = process.env.TWIKOO_HOST ?? (process.env.TWIKOO_LOCALHOST_ONLY === "true" ? "localhost" : "::");
|
|
214
|
+
if (process.env.TWIKOO_SEED === "1") try {
|
|
215
|
+
/** 动态 import:未开启 seed 时本模块不会被加载(生产不可触达第一道防线) */
|
|
216
|
+
const { seedDemoData } = await Promise.resolve().then(() => require("./seed-BAmtKrez.js"));
|
|
217
|
+
await seedDemoData({ database });
|
|
218
|
+
} catch (e) {
|
|
219
|
+
/** seed 属演示辅助:失败不阻断服务启动,但必须显式报错(不静默) */
|
|
220
|
+
console.error("[twikoo-seed] seed 失败,服务仍将启动:", e);
|
|
221
|
+
}
|
|
222
|
+
await new Promise((resolve) => {
|
|
223
|
+
server.listen(port, host, () => {
|
|
224
|
+
const actual = server.address().port;
|
|
225
|
+
console.log(`Twikoo server started on host ${host} port ${actual}`);
|
|
226
|
+
resolve();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
registerSignalHandlers();
|
|
230
|
+
return instance;
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
exports.createTkserverDatabase = createTkserverDatabase;
|
|
234
|
+
exports.createTkserverHandler = createTkserverHandler;
|
|
235
|
+
exports.createTkserverServer = createTkserverServer;
|
|
236
|
+
exports.fromTkResponse = fromTkResponse;
|
|
237
|
+
exports.getRequestTimesClearInterval = getRequestTimesClearInterval;
|
|
238
|
+
exports.shutdown = shutdown;
|
|
239
|
+
exports.startRequestTimesTimer = startRequestTimesTimer;
|
|
240
|
+
exports.startTkserver = startTkserver;
|
|
241
|
+
exports.toTkRequest = toTkRequest;
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["MongoDatabase","LokiDatabase","FULL_CAPABILITIES","createHandler","scaffoldAdapters","resetRequestTimes","createServer"],"sources":["../src/database.ts","../src/main.ts","../src/server.ts"],"sourcesContent":["/**\n * tkserver 数据库选择(1.x `server.js` 语义:`MONGODB_URI`/`MONGO_URL` → Mongo,\n * 否则 Loki + `TWIKOO_DATA`)。\n *\n * 独立成文件的原因:启动路径需要**先**拿到数据库实例做 demo seed,\n * 与逐请求惰性初始化共用同一份选择逻辑。\n */\nimport { LokiDatabase, MongoDatabase, type Database } from \"@twikoojs/common\";\n\n/**\n * 按环境选择数据库实现。\n * @param options 数据目录 / 连接串覆盖项\n * @returns 数据库实例(尚未 init)\n */\nexport function createTkserverDatabase(\n options: { dataDir?: string; mongoUri?: string } = {},\n): Database {\n const mongoUri = options.mongoUri ?? process.env.MONGODB_URI ?? process.env.MONGO_URL ?? \"\";\n return mongoUri\n ? new MongoDatabase({ uri: mongoUri })\n : new LokiDatabase({ dataDir: options.dataDir ?? process.env.TWIKOO_DATA ?? \"./data\" });\n}\n","/**\n * tkserver 主逻辑(self-hosted 薄适配器)。\n * 业务逻辑全部在 @twikoojs/common;数据库 MONGODB_URI→Mongo,否则 Loki(TWIKOO_DATA)。\n * 平台核对:nodejs.org/api http/信号处理(查阅 2026-09-17)。\n */\nimport {\n FULL_CAPABILITIES,\n createHandler,\n resetRequestTimes,\n scaffoldAdapters,\n type Database,\n type TkRequest,\n type TkResponse,\n} from \"@twikoojs/common\";\nimport { createTkserverDatabase } from \"./database\";\n\n/** tkserver 平台能力:全能力*/\nconst tkserverCapabilities = FULL_CAPABILITIES;\n\n/** Node 请求的最小结构面(IncomingMessage;body 已由 server.ts 解析挂载) */\nexport interface ServerRequestLike {\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n url?: string;\n body?: unknown;\n}\n\n/** Node 响应的最小结构面(ServerResponse + 1.x status/json 垫片) */\nexport interface ServerResponseLike {\n statusCode?: number;\n writableEnded?: boolean;\n writeHead(code: number, headers: Record<string, string>): unknown;\n end(body?: string): unknown;\n status(code: number): ServerResponseLike;\n json(body: unknown): ServerResponseLike;\n}\n\n/**\n * Node 请求 → 内部统一请求(headers 小写化;x-real-ip/转发首跳取 IP)。\n * @param req Node 请求\n * @returns 内部统一请求\n */\nexport function toTkRequest(req: ServerRequestLike): 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 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/**\n * 内部统一响应 → Node 响应(204 无体;业务 JSON 透传 tkRes.status;\n * headers 原样回写——CORS 由 pipeline 算好,业务分支在其后补 Content-Type;\n * 垫片语义对齐 1.x)。\n */\nexport function fromTkResponse(res: ServerResponseLike, tkRes: TkResponse): void {\n if (tkRes.status === 204) {\n res.writeHead(204, tkRes.headers);\n res.end();\n return;\n }\n res.writeHead(tkRes.status, { ...tkRes.headers, \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(tkRes.body));\n}\n\n/**\n * 创建 tkserver 请求处理器(database 可注入;缺省按 MONGODB_URI/TWIKOO_DATA 选择)。\n * @param options 注入项\n * @returns 逐请求处理器\n */\nexport function createTkserverHandler(\n options: { database?: Database; dataDir?: string; mongoUri?: string } = {},\n): (req: ServerRequestLike, res: ServerResponseLike) => Promise<void> {\n let database: Database | null = options.database ?? null;\n /** 数据库选择(1.x server.js:MONGODB_URI → mongo,否则 loki) */\n const getDatabase = async (): Promise<Database> => {\n if (!database) database = createTkserverDatabase(options);\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: tkserverCapabilities,\n // 后置副作用派发**刻意不传**:tkserver 是常驻进程,scaffoldAdapters 的\n // 默认实现即「进程内直调 postSubmit 服务且不 await」——1.x\n // `postSubmit(comment)` 语义(见 src/server/self-hosted/index.js)。\n }),\n );\n fromTkResponse(res, await handler(request));\n };\n}\n\n/**\n * 优雅退出(1.x shutdown 语义:停限流清理定时器 → 关数据库)。\n * @param options 清理项\n */\nexport async function shutdown(\n options: {\n timer?: NodeJS.Timeout | null;\n database?: Database | null;\n } = {},\n): Promise<void> {\n if (options.timer) clearInterval(options.timer);\n await options.database?.close?.();\n}\n\n/** requestTimes 清理周期(1.x TWIKOO_REQ_TIMES_CLEAR_TIME,缺省 10 分钟) */\nexport function getRequestTimesClearInterval(): number {\n return parseInt(process.env.TWIKOO_REQ_TIMES_CLEAR_TIME ?? \"\", 10) || 10 * 60 * 1000;\n}\n\n/** 启动限流计数定时清理(返回定时器供 shutdown 停用) */\nexport function startRequestTimesTimer(): NodeJS.Timeout {\n return setInterval(() => resetRequestTimes(), getRequestTimesClearInterval());\n}\n","/**\n * tkserver bin 入口(HTTP 服务器引导;业务逻辑在 @twikoojs/common + main.ts)。\n * 1.x server.js 语义对齐:JSON body 解析、socket 跟踪、退出信号优雅关闭、\n * 关闭期间 503。TWIKOO_SKIP_BOOT=1 时仅导出工厂不启动(测试进程内装配)。\n */\nimport { createServer, type Server } from \"node:http\";\nimport type { AddressInfo, Socket } from \"node:net\";\nimport {\n createTkserverHandler,\n getRequestTimesClearInterval,\n shutdown,\n startRequestTimesTimer,\n} from \"./main\";\nimport { createTkserverDatabase } from \"./database\";\nimport type { Database } from \"@twikoojs/common\";\n\n/** 垫片后的响应形态(Node 响应 + status/json) */\ntype ServerResponseShim = import(\"./main\").ServerResponseLike;\n\n/** 垫片中的请求形态(body 已解析挂载) */\ntype ServerRequestShim = import(\"./main\").ServerRequestLike & { body?: unknown };\n\n/** 服务器实例与生命周期控制(createTkserverServer 产物) */\nexport interface TkserverInstance {\n /** HTTP 服务器(未监听;listen 由调用方执行) */\n server: Server;\n /** 已装配的数据库实例(供启动期 seed 复用)*/\n database: Database;\n /** 优雅关闭(关监听 → 排空连接 → 停定时器 → 关数据库) */\n gracefulShutdown: () => Promise<void>;\n /** 注册 SIGTERM/SIGINT 处理(TWIKOO_SHUTDOWN_TIMEOUT 兜底强杀) */\n registerSignalHandlers: () => void;\n}\n\n/**\n * 创建 tkserver 服务器实例(不监听、不注册信号——由 bin 底部或测试驱动)。\n * @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)\n * @returns 服务器实例与生命周期控制\n */\nexport function createTkserverServer(options: { database?: Database } = {}): TkserverInstance {\n const database = options.database ?? createTkserverDatabase();\n const handler = createTkserverHandler({ database });\n const timer = startRequestTimesTimer();\n const sockets = new Set<Socket>();\n let isShuttingDown = false;\n\n const server = createServer((req, res) => {\n if (isShuttingDown) {\n res.writeHead(503, { Connection: \"close\", \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ code: 503, message: \"Twikoo server is shutting down\" }));\n return;\n }\n void (async () => {\n /** 聚合请求体(JSON 解析失败按空体处理,1.x 语义) */\n const buffers: Buffer[] = [];\n for await (const chunk of req) buffers.push(chunk as Buffer);\n try {\n (req as ServerRequestShim).body = JSON.parse(Buffer.concat(buffers).toString() || \"{}\");\n } catch {\n (req as ServerRequestShim).body = {};\n }\n /** status/json 垫片(1.x server.js 对齐) */\n const shimmed = res as unknown as ServerResponseShim;\n shimmed.status = (code: number) => {\n res.statusCode = code;\n return shimmed;\n };\n shimmed.json = (json: unknown) => {\n if (!res.writableEnded) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(json));\n }\n return shimmed;\n };\n await handler(req, shimmed);\n })().catch(() => {\n if (!res.writableEnded) {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ code: 1000, message: \"internal error\" }));\n }\n });\n });\n\n server.on(\"connection\", (socket) => {\n sockets.add(socket);\n socket.on(\"close\", () => sockets.delete(socket));\n });\n\n /** 优雅关闭(关监听 → 排空连接 → shutdown 资源) */\n const gracefulShutdown = async (): Promise<void> => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n await new Promise<void>((resolve) => server.close(() => resolve()));\n for (const socket of sockets) socket.destroy();\n await shutdown({ timer });\n };\n\n /** SIGTERM / SIGINT → 优雅退出(超时兜底强杀) */\n const registerSignalHandlers = (): void => {\n const timeout = parseInt(process.env.TWIKOO_SHUTDOWN_TIMEOUT ?? \"\", 10) || 5000;\n /** 信号回调:竞速优雅关闭与超时兜底 */\n const onSignal = (): void => {\n void Promise.race([\n gracefulShutdown(),\n new Promise((resolve) => setTimeout(resolve, timeout)),\n ]).then(() => process.exit(0));\n };\n process.on(\"SIGTERM\", onSignal);\n process.on(\"SIGINT\", onSignal);\n };\n\n return { server, database, gracefulShutdown, registerSignalHandlers };\n}\n\n/**\n * 启动 tkserver(bin `dist/server.js` 与 `twikoo-pkg` 的 SEA 产物共用入口):\n * 可选 demo seed → 监听端口 → 注册信号处理。\n * @param options 注入项(数据库可注入,缺省按 MONGODB_URI/TWIKOO_DATA 选择)\n * @returns 已进入监听的服务器实例\n */\nexport async function startTkserver(\n options: { database?: Database } = {},\n): Promise<TkserverInstance> {\n const instance = createTkserverServer(options);\n const { server, database, registerSignalHandlers } = instance;\n const port = parseInt(process.env.TWIKOO_PORT ?? \"\", 10) || 8080;\n const host =\n process.env.TWIKOO_HOST ?? (process.env.TWIKOO_LOCALHOST_ONLY === \"true\" ? \"localhost\" : \"::\");\n\n if (process.env.TWIKOO_SEED === \"1\") {\n try {\n /** 动态 import:未开启 seed 时本模块不会被加载(生产不可触达第一道防线) */\n const { seedDemoData } = await import(\"./seed\");\n await seedDemoData({ database });\n } catch (e) {\n /** seed 属演示辅助:失败不阻断服务启动,但必须显式报错(不静默) */\n console.error(\"[twikoo-seed] seed 失败,服务仍将启动:\", e);\n }\n }\n\n await new Promise<void>((resolve) => {\n server.listen(port, host, () => {\n const actual = (server.address() as AddressInfo).port;\n console.log(`Twikoo server started on host ${host} port ${actual}`);\n resolve();\n });\n });\n registerSignalHandlers();\n void getRequestTimesClearInterval;\n return instance;\n}\n"],"mappings":"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"}
|