@onebots/core 0.5.0 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +3 -3
  2. package/lib/__tests__/config-validator.test.d.ts +4 -0
  3. package/lib/__tests__/config-validator.test.js +152 -0
  4. package/lib/__tests__/di-container.test.d.ts +4 -0
  5. package/lib/__tests__/di-container.test.js +114 -0
  6. package/lib/__tests__/errors.test.d.ts +4 -0
  7. package/lib/__tests__/errors.test.js +111 -0
  8. package/lib/__tests__/integration.test.d.ts +5 -0
  9. package/lib/__tests__/integration.test.js +112 -0
  10. package/lib/__tests__/lifecycle.test.d.ts +4 -0
  11. package/lib/__tests__/lifecycle.test.js +163 -0
  12. package/lib/account.d.ts +4 -1
  13. package/lib/account.js +6 -3
  14. package/lib/adapter.d.ts +67 -1
  15. package/lib/adapter.js +31 -4
  16. package/lib/base-app.d.ts +30 -3
  17. package/lib/base-app.js +295 -142
  18. package/lib/circuit-breaker.d.ts +94 -0
  19. package/lib/circuit-breaker.js +206 -0
  20. package/lib/config-validator.d.ts +51 -0
  21. package/lib/config-validator.js +184 -0
  22. package/lib/connection-pool.d.ts +68 -0
  23. package/lib/connection-pool.js +202 -0
  24. package/lib/db.d.ts +2 -1
  25. package/lib/db.js +11 -2
  26. package/lib/di-container.d.ts +60 -0
  27. package/lib/di-container.js +103 -0
  28. package/lib/errors.d.ts +157 -0
  29. package/lib/errors.js +257 -0
  30. package/lib/index.d.ts +13 -4
  31. package/lib/index.js +17 -3
  32. package/lib/lifecycle.d.ts +75 -0
  33. package/lib/lifecycle.js +175 -0
  34. package/lib/logger.d.ts +76 -0
  35. package/lib/logger.js +156 -0
  36. package/lib/metrics.d.ts +80 -0
  37. package/lib/metrics.js +201 -0
  38. package/lib/middleware/index.d.ts +8 -0
  39. package/lib/middleware/index.js +8 -0
  40. package/lib/middleware/metrics-collector.d.ts +9 -0
  41. package/lib/middleware/metrics-collector.js +64 -0
  42. package/lib/middleware/rate-limit.d.ts +32 -0
  43. package/lib/middleware/rate-limit.js +149 -0
  44. package/lib/middleware/security-audit.d.ts +33 -0
  45. package/lib/middleware/security-audit.js +223 -0
  46. package/lib/middleware/token-manager.d.ts +73 -0
  47. package/lib/middleware/token-manager.js +186 -0
  48. package/lib/middleware/token-validator.d.ts +42 -0
  49. package/lib/middleware/token-validator.js +198 -0
  50. package/lib/protocol.d.ts +2 -3
  51. package/lib/protocol.js +4 -0
  52. package/lib/rate-limiter.d.ts +88 -0
  53. package/lib/rate-limiter.js +196 -0
  54. package/lib/registry.d.ts +27 -0
  55. package/lib/registry.js +40 -5
  56. package/lib/retry.d.ts +87 -0
  57. package/lib/retry.js +205 -0
  58. package/lib/router.d.ts +43 -6
  59. package/lib/router.js +139 -12
  60. package/lib/timestamp.d.ts +42 -0
  61. package/lib/timestamp.js +72 -0
  62. package/lib/types.d.ts +1 -0
  63. package/lib/types.js +2 -1
  64. package/lib/utils.d.ts +2 -1
  65. package/lib/utils.js +11 -19
  66. package/package.json +24 -9
package/lib/retry.d.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * 重试策略和网络请求重试机制
3
+ * 提供统一的重试逻辑,支持指数退避
4
+ */
5
+ export interface RetryOptions {
6
+ /** 最大重试次数 */
7
+ maxRetries?: number;
8
+ /** 初始延迟时间(毫秒) */
9
+ initialDelay?: number;
10
+ /** 最大延迟时间(毫秒) */
11
+ maxDelay?: number;
12
+ /** 退避倍数 */
13
+ backoffMultiplier?: number;
14
+ /** 是否添加随机抖动 */
15
+ jitter?: boolean;
16
+ /** 判断是否应该重试的函数 */
17
+ shouldRetry?: (error: Error, attempt: number) => boolean;
18
+ /** 重试前的回调 */
19
+ onRetry?: (error: Error, attempt: number, delay: number) => void;
20
+ }
21
+ /**
22
+ * 带重试的异步函数执行器
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const result = await retry(
27
+ * () => fetch('https://api.example.com/data'),
28
+ * { maxRetries: 3, initialDelay: 1000 }
29
+ * );
30
+ * ```
31
+ */
32
+ export declare function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
33
+ /**
34
+ * 创建一个带重试功能的函数包装器
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const fetchWithRetry = withRetry(fetch, { maxRetries: 3 });
39
+ * const response = await fetchWithRetry('https://api.example.com/data');
40
+ * ```
41
+ */
42
+ export declare function withRetry<T extends (...args: any[]) => Promise<any>>(fn: T, options?: RetryOptions): T;
43
+ /**
44
+ * 重试策略预设
45
+ */
46
+ export declare const RetryPresets: {
47
+ /** 快速重试:3次,1秒起始 */
48
+ fast: RetryOptions;
49
+ /** 标准重试:5次,2秒起始 */
50
+ standard: RetryOptions;
51
+ /** 持久重试:10次,5秒起始 */
52
+ persistent: RetryOptions;
53
+ /** WebSocket 重连:无限重试 */
54
+ websocket: RetryOptions;
55
+ };
56
+ /**
57
+ * 连接管理器 - 用于 WebSocket 等长连接的重连管理
58
+ */
59
+ export declare class ConnectionManager {
60
+ private connect;
61
+ private reconnectAttempt;
62
+ private reconnectTimer;
63
+ private isConnecting;
64
+ private options;
65
+ constructor(connect: () => Promise<void>, options?: RetryOptions);
66
+ /**
67
+ * 开始连接
68
+ */
69
+ start(): Promise<void>;
70
+ /**
71
+ * 停止连接和重连
72
+ */
73
+ stop(): void;
74
+ /**
75
+ * 触发重连
76
+ */
77
+ scheduleReconnect(error?: Error): void;
78
+ /**
79
+ * 重置重连计数(连接成功时调用)
80
+ */
81
+ resetAttempts(): void;
82
+ /**
83
+ * 获取当前重连次数
84
+ */
85
+ getAttempts(): number;
86
+ private tryConnect;
87
+ }
package/lib/retry.js ADDED
@@ -0,0 +1,205 @@
1
+ /**
2
+ * 重试策略和网络请求重试机制
3
+ * 提供统一的重试逻辑,支持指数退避
4
+ */
5
+ const DEFAULT_OPTIONS = {
6
+ maxRetries: 3,
7
+ initialDelay: 1000,
8
+ maxDelay: 30000,
9
+ backoffMultiplier: 2,
10
+ jitter: true,
11
+ shouldRetry: (error) => {
12
+ // 默认对网络错误和超时错误进行重试
13
+ const message = error.message.toLowerCase();
14
+ return (message.includes('timeout') ||
15
+ message.includes('network') ||
16
+ message.includes('econnreset') ||
17
+ message.includes('econnrefused') ||
18
+ message.includes('socket') ||
19
+ message.includes('fetch failed'));
20
+ },
21
+ onRetry: () => { },
22
+ };
23
+ /**
24
+ * 计算下一次重试的延迟时间
25
+ */
26
+ function calculateDelay(attempt, options) {
27
+ let delay = options.initialDelay * Math.pow(options.backoffMultiplier, attempt - 1);
28
+ // 添加随机抖动(±25%)
29
+ if (options.jitter) {
30
+ const jitterFactor = 0.75 + Math.random() * 0.5;
31
+ delay *= jitterFactor;
32
+ }
33
+ return Math.min(delay, options.maxDelay);
34
+ }
35
+ /**
36
+ * 等待指定时间
37
+ */
38
+ function sleep(ms) {
39
+ return new Promise(resolve => setTimeout(resolve, ms));
40
+ }
41
+ /**
42
+ * 带重试的异步函数执行器
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const result = await retry(
47
+ * () => fetch('https://api.example.com/data'),
48
+ * { maxRetries: 3, initialDelay: 1000 }
49
+ * );
50
+ * ```
51
+ */
52
+ export async function retry(fn, options = {}) {
53
+ const opts = { ...DEFAULT_OPTIONS, ...options };
54
+ let lastError;
55
+ for (let attempt = 1; attempt <= opts.maxRetries + 1; attempt++) {
56
+ try {
57
+ return await fn();
58
+ }
59
+ catch (error) {
60
+ lastError = error instanceof Error ? error : new Error(String(error));
61
+ // 检查是否还有重试机会
62
+ if (attempt > opts.maxRetries) {
63
+ break;
64
+ }
65
+ // 检查是否应该重试
66
+ if (!opts.shouldRetry(lastError, attempt)) {
67
+ break;
68
+ }
69
+ // 计算延迟并等待
70
+ const delay = calculateDelay(attempt, opts);
71
+ opts.onRetry(lastError, attempt, delay);
72
+ await sleep(delay);
73
+ }
74
+ }
75
+ throw lastError;
76
+ }
77
+ /**
78
+ * 创建一个带重试功能的函数包装器
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const fetchWithRetry = withRetry(fetch, { maxRetries: 3 });
83
+ * const response = await fetchWithRetry('https://api.example.com/data');
84
+ * ```
85
+ */
86
+ export function withRetry(fn, options = {}) {
87
+ return ((...args) => {
88
+ return retry(() => fn(...args), options);
89
+ });
90
+ }
91
+ /**
92
+ * 重试策略预设
93
+ */
94
+ export const RetryPresets = {
95
+ /** 快速重试:3次,1秒起始 */
96
+ fast: {
97
+ maxRetries: 3,
98
+ initialDelay: 1000,
99
+ maxDelay: 5000,
100
+ backoffMultiplier: 1.5,
101
+ },
102
+ /** 标准重试:5次,2秒起始 */
103
+ standard: {
104
+ maxRetries: 5,
105
+ initialDelay: 2000,
106
+ maxDelay: 30000,
107
+ backoffMultiplier: 2,
108
+ },
109
+ /** 持久重试:10次,5秒起始 */
110
+ persistent: {
111
+ maxRetries: 10,
112
+ initialDelay: 5000,
113
+ maxDelay: 60000,
114
+ backoffMultiplier: 2,
115
+ },
116
+ /** WebSocket 重连:无限重试 */
117
+ websocket: {
118
+ maxRetries: Infinity,
119
+ initialDelay: 1000,
120
+ maxDelay: 30000,
121
+ backoffMultiplier: 2,
122
+ jitter: true,
123
+ },
124
+ };
125
+ /**
126
+ * 连接管理器 - 用于 WebSocket 等长连接的重连管理
127
+ */
128
+ export class ConnectionManager {
129
+ connect;
130
+ reconnectAttempt = 0;
131
+ reconnectTimer = null;
132
+ isConnecting = false;
133
+ options;
134
+ constructor(connect, options = RetryPresets.websocket) {
135
+ this.connect = connect;
136
+ this.options = { ...DEFAULT_OPTIONS, ...options };
137
+ }
138
+ /**
139
+ * 开始连接
140
+ */
141
+ async start() {
142
+ this.stop();
143
+ this.reconnectAttempt = 0;
144
+ await this.tryConnect();
145
+ }
146
+ /**
147
+ * 停止连接和重连
148
+ */
149
+ stop() {
150
+ if (this.reconnectTimer) {
151
+ clearTimeout(this.reconnectTimer);
152
+ this.reconnectTimer = null;
153
+ }
154
+ this.isConnecting = false;
155
+ }
156
+ /**
157
+ * 触发重连
158
+ */
159
+ scheduleReconnect(error) {
160
+ if (this.isConnecting)
161
+ return;
162
+ this.reconnectAttempt++;
163
+ if (this.reconnectAttempt > this.options.maxRetries) {
164
+ console.error('[ConnectionManager] 达到最大重试次数,停止重连');
165
+ return;
166
+ }
167
+ const delay = calculateDelay(this.reconnectAttempt, this.options);
168
+ console.log(`[ConnectionManager] 将在 ${Math.round(delay / 1000)}s 后进行第 ${this.reconnectAttempt} 次重连`);
169
+ if (error) {
170
+ this.options.onRetry(error, this.reconnectAttempt, delay);
171
+ }
172
+ this.reconnectTimer = setTimeout(() => {
173
+ this.tryConnect();
174
+ }, delay);
175
+ }
176
+ /**
177
+ * 重置重连计数(连接成功时调用)
178
+ */
179
+ resetAttempts() {
180
+ this.reconnectAttempt = 0;
181
+ }
182
+ /**
183
+ * 获取当前重连次数
184
+ */
185
+ getAttempts() {
186
+ return this.reconnectAttempt;
187
+ }
188
+ async tryConnect() {
189
+ if (this.isConnecting)
190
+ return;
191
+ this.isConnecting = true;
192
+ try {
193
+ await this.connect();
194
+ this.resetAttempts();
195
+ }
196
+ catch (error) {
197
+ const err = error instanceof Error ? error : new Error(String(error));
198
+ console.error('[ConnectionManager] 连接失败:', err.message);
199
+ this.scheduleReconnect(err);
200
+ }
201
+ finally {
202
+ this.isConnecting = false;
203
+ }
204
+ }
205
+ }
package/lib/router.d.ts CHANGED
@@ -1,16 +1,53 @@
1
1
  import KoaRouter from "@koa/router";
2
2
  import { WebSocketServer, WebSocket, ServerOptions } from "ws";
3
3
  import { IncomingMessage, Server } from "http";
4
- export declare class WsServer<T extends typeof WebSocket.WebSocket = typeof WebSocket.WebSocket, U extends typeof IncomingMessage = typeof IncomingMessage> extends WebSocketServer<T, U> {
5
- constructor(options?: WsServer.Options<T, U>);
4
+ import type { RouterContext as KoaRouterContext } from "@koa/router";
5
+ import type { Request } from 'koa';
6
+ export type RouterContext = KoaRouterContext & {
7
+ request: Request & {
8
+ body: any;
9
+ };
10
+ };
11
+ export type { Next } from "koa";
12
+ export declare class WsServer<T extends typeof WebSocket = typeof WebSocket, U extends typeof IncomingMessage = typeof IncomingMessage> extends WebSocketServer<T, U> {
13
+ readonly path: string;
14
+ constructor(options: WsServer.Options<T, U>);
6
15
  }
7
16
  export declare namespace WsServer {
8
- interface Options<T extends typeof WebSocket.WebSocket = typeof WebSocket.WebSocket, U extends typeof IncomingMessage = typeof IncomingMessage> extends ServerOptions<T, U> {
17
+ interface Options<T extends typeof WebSocket = typeof WebSocket, U extends typeof IncomingMessage = typeof IncomingMessage> extends ServerOptions<T, U> {
9
18
  path: string;
10
19
  }
11
20
  }
12
21
  export declare class Router extends KoaRouter {
13
- wsStack: WsServer[];
14
- constructor(server: Server, options?: KoaRouter.RouterOptions);
15
- ws(path: string): WsServer<typeof import("ws"), typeof IncomingMessage>;
22
+ private wsMap;
23
+ private upgradeHandler?;
24
+ private readonly server;
25
+ constructor(server: Server, options?: ConstructorParameters<typeof KoaRouter>[0]);
26
+ /**
27
+ * 设置 WebSocket upgrade 处理器
28
+ */
29
+ private setupUpgradeHandler;
30
+ /**
31
+ * 创建并注册一个新的 WebSocket 服务器
32
+ * @param path WebSocket 路径(客户端请求的完整路径,不受 router prefix 影响)
33
+ * @returns WebSocket 服务器实例
34
+ */
35
+ ws(path: string): WsServer;
36
+ /**
37
+ * 移除 WebSocket 服务器
38
+ * @param path WebSocket 路径(客户端请求的完整路径)
39
+ */
40
+ removeWs(path: string): boolean;
41
+ /**
42
+ * 清理所有 WebSocket 服务器和事件监听器
43
+ */
44
+ cleanup(): void;
45
+ /**
46
+ * 异步清理(返回 Promise)
47
+ */
48
+ cleanupAsync(): Promise<void>;
49
+ /**
50
+ * 获取所有已注册的 WebSocket 路径
51
+ */
52
+ getWsPaths(): string[];
16
53
  }
package/lib/router.js CHANGED
@@ -1,28 +1,155 @@
1
1
  import KoaRouter from "@koa/router";
2
2
  import { WebSocketServer } from "ws";
3
3
  export class WsServer extends WebSocketServer {
4
+ path = '';
4
5
  constructor(options) {
5
6
  super(options);
7
+ // 设置 path(基类可能不会自动设置,因为使用了 noServer: true)
6
8
  this.path = options.path;
7
9
  }
8
10
  }
9
11
  export class Router extends KoaRouter {
10
- wsStack = [];
12
+ wsMap = new Map();
13
+ upgradeHandler;
14
+ server;
11
15
  constructor(server, options) {
12
16
  super(options);
13
- server.on("upgrade", (request, socket, head) => {
14
- const { pathname } = new URL(request.url, `wss://localhost`);
15
- const wsServer = this.wsStack.find(wss => wss.path === pathname);
16
- if (!wsServer)
17
- return socket.destroy();
18
- wsServer.handleUpgrade(request, socket, head, function done(ws) {
19
- wsServer.emit("connection", ws, request);
20
- });
21
- });
17
+ this.server = server;
18
+ this.setupUpgradeHandler();
19
+ }
20
+ /**
21
+ * 设置 WebSocket upgrade 处理器
22
+ */
23
+ setupUpgradeHandler() {
24
+ this.upgradeHandler = (request, socket, head) => {
25
+ try {
26
+ const url = new URL(request.url || "/", `http://localhost`);
27
+ const pathname = url.pathname;
28
+ // WebSocket upgrade 请求的路径是客户端直接请求的完整路径
29
+ // 不受 Koa router prefix 影响,所以直接使用 pathname 进行匹配
30
+ const wsServer = this.wsMap.get(pathname);
31
+ if (!wsServer) {
32
+ // 没有找到匹配的 WebSocket 服务器,优雅地关闭连接
33
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
34
+ socket.destroy();
35
+ return;
36
+ }
37
+ // 处理 WebSocket 升级
38
+ wsServer.handleUpgrade(request, socket, head, (ws) => {
39
+ wsServer.emit("connection", ws, request);
40
+ });
41
+ }
42
+ catch (error) {
43
+ // 处理 URL 解析错误
44
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
45
+ socket.destroy();
46
+ }
47
+ };
48
+ this.server.on("upgrade", this.upgradeHandler);
22
49
  }
50
+ /**
51
+ * 创建并注册一个新的 WebSocket 服务器
52
+ * @param path WebSocket 路径(客户端请求的完整路径,不受 router prefix 影响)
53
+ * @returns WebSocket 服务器实例
54
+ */
23
55
  ws(path) {
24
- const wsServer = new WsServer({ noServer: true, path });
25
- this.wsStack.push(wsServer);
56
+ // 规范化路径:确保以 / 开头
57
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
58
+ // WebSocket 路径不受 Router prefix 影响
59
+ // 因为 WebSocket upgrade 是直接处理 HTTP upgrade 请求的,使用原始 pathname
60
+ // 所以直接使用传入的路径,不添加 prefix
61
+ const fullPath = normalizedPath;
62
+ // 检查路径是否已存在
63
+ if (this.wsMap.has(fullPath)) {
64
+ throw new Error(`WebSocket server already exists at path: ${fullPath}`);
65
+ }
66
+ // 创建 WebSocket 服务器
67
+ const wsServer = new WsServer({
68
+ noServer: true,
69
+ path: fullPath
70
+ });
71
+ // 存储完整路径(客户端请求的路径,不受 prefix 影响)
72
+ this.wsMap.set(fullPath, wsServer);
26
73
  return wsServer;
27
74
  }
75
+ /**
76
+ * 移除 WebSocket 服务器
77
+ * @param path WebSocket 路径(客户端请求的完整路径)
78
+ */
79
+ removeWs(path) {
80
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
81
+ // 尝试匹配完整路径或相对路径
82
+ let fullPath;
83
+ if (this.opts.prefix && !normalizedPath.startsWith(this.opts.prefix)) {
84
+ fullPath = `${this.opts.prefix}${normalizedPath}`;
85
+ }
86
+ else {
87
+ fullPath = normalizedPath;
88
+ }
89
+ const wsServer = this.wsMap.get(fullPath) || this.wsMap.get(normalizedPath);
90
+ if (wsServer) {
91
+ // 关闭所有连接
92
+ wsServer.close();
93
+ this.wsMap.delete(fullPath);
94
+ if (fullPath !== normalizedPath && this.wsMap.has(normalizedPath)) {
95
+ this.wsMap.delete(normalizedPath);
96
+ }
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ /**
102
+ * 清理所有 WebSocket 服务器和事件监听器
103
+ */
104
+ cleanup() {
105
+ // 关闭所有 WebSocket 服务器
106
+ for (const wsServer of this.wsMap.values()) {
107
+ try {
108
+ wsServer.close();
109
+ }
110
+ catch (error) {
111
+ // 忽略关闭错误
112
+ }
113
+ }
114
+ this.wsMap.clear();
115
+ // 移除 upgrade 事件监听器
116
+ if (this.upgradeHandler) {
117
+ this.server.removeListener("upgrade", this.upgradeHandler);
118
+ this.upgradeHandler = undefined;
119
+ }
120
+ }
121
+ /**
122
+ * 异步清理(返回 Promise)
123
+ */
124
+ async cleanupAsync() {
125
+ return new Promise((resolve) => {
126
+ // 关闭所有 WebSocket 服务器
127
+ const closePromises = [];
128
+ for (const wsServer of this.wsMap.values()) {
129
+ closePromises.push(new Promise((resolve) => {
130
+ try {
131
+ wsServer.close(() => resolve());
132
+ }
133
+ catch {
134
+ resolve();
135
+ }
136
+ }));
137
+ }
138
+ Promise.all(closePromises).then(() => {
139
+ this.wsMap.clear();
140
+ // 移除 upgrade 事件监听器
141
+ if (this.upgradeHandler) {
142
+ this.server.removeListener("upgrade", this.upgradeHandler);
143
+ this.upgradeHandler = undefined;
144
+ }
145
+ resolve();
146
+ });
147
+ });
148
+ }
149
+ /**
150
+ * 获取所有已注册的 WebSocket 路径
151
+ */
152
+ getWsPaths() {
153
+ return Array.from(this.wsMap.keys());
154
+ }
28
155
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 统一时间戳工具:各平台有的返回 Unix **秒**、有的返回 **毫秒**,CommonEvent 等统一用 **毫秒**。
3
+ */
4
+ /** 启发式阈值:大于等于该值视为已是毫秒(约对应 2001-09-09 之后的毫秒时间戳) */
5
+ export declare const UNIX_TIMESTAMP_MS_MIN = 10000000000;
6
+ /**
7
+ * 平台明确给出 **Unix 秒**(如微信公众号 CreateTime、飞书 create_time)时,转为事件用的 **毫秒**。
8
+ *
9
+ * @param seconds - 秒级时间戳(number / 数字字符串)
10
+ * @param options.fallbackMs - 空值、非有限数或 ≤0 时使用的毫秒时间,默认 `Date.now()`
11
+ */
12
+ export declare function unixSecondsToEventMs(seconds: unknown, options?: {
13
+ fallbackMs?: number;
14
+ }): number;
15
+ /**
16
+ * 平台明确给出 **Unix 毫秒** 时,校验并取整;无效时使用 fallback。
17
+ */
18
+ export declare function unixMillisToEventMs(ms: unknown, options?: {
19
+ fallbackMs?: number;
20
+ }): number;
21
+ /**
22
+ * 不确定单位时使用:**大于等于 {@link UNIX_TIMESTAMP_MS_MIN} 视为毫秒**,否则视为秒。
23
+ * 适用于部分文档不清或中间层混传的场景。
24
+ */
25
+ export declare function coerceUnixToEventMs(value: unknown, options?: {
26
+ fallbackMs?: number;
27
+ }): number;
28
+ /**
29
+ * 需要 **Unix 秒** 的字段(如部分协议里的 message `time`)时使用。
30
+ *
31
+ * @param seconds - 秒级时间戳
32
+ * @param options.fallbackSec - 无效时的秒级时间,默认当前 `Date.now()/1000` 取整
33
+ */
34
+ export declare function toUnixSeconds(seconds: unknown, options?: {
35
+ fallbackSec?: number;
36
+ }): number;
37
+ /**
38
+ * 平台返回 **ISO 8601 字符串**或其它 `Date` 可解析格式(如 QQ 开放消息 `timestamp`)时,转为事件用的 **毫秒**。
39
+ */
40
+ export declare function dateLikeToEventMs(value: unknown, options?: {
41
+ fallbackMs?: number;
42
+ }): number;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * 统一时间戳工具:各平台有的返回 Unix **秒**、有的返回 **毫秒**,CommonEvent 等统一用 **毫秒**。
3
+ */
4
+ /** 启发式阈值:大于等于该值视为已是毫秒(约对应 2001-09-09 之后的毫秒时间戳) */
5
+ export const UNIX_TIMESTAMP_MS_MIN = 10_000_000_000;
6
+ /**
7
+ * 平台明确给出 **Unix 秒**(如微信公众号 CreateTime、飞书 create_time)时,转为事件用的 **毫秒**。
8
+ *
9
+ * @param seconds - 秒级时间戳(number / 数字字符串)
10
+ * @param options.fallbackMs - 空值、非有限数或 ≤0 时使用的毫秒时间,默认 `Date.now()`
11
+ */
12
+ export function unixSecondsToEventMs(seconds, options) {
13
+ const fallback = options?.fallbackMs ?? Date.now();
14
+ if (seconds == null || seconds === "")
15
+ return fallback;
16
+ const n = typeof seconds === "string" ? parseFloat(seconds) : Number(seconds);
17
+ if (!Number.isFinite(n) || n <= 0)
18
+ return fallback;
19
+ return Math.floor(n * 1000);
20
+ }
21
+ /**
22
+ * 平台明确给出 **Unix 毫秒** 时,校验并取整;无效时使用 fallback。
23
+ */
24
+ export function unixMillisToEventMs(ms, options) {
25
+ const fallback = options?.fallbackMs ?? Date.now();
26
+ if (ms == null || ms === "")
27
+ return fallback;
28
+ const n = typeof ms === "string" ? parseFloat(ms) : Number(ms);
29
+ if (!Number.isFinite(n) || n <= 0)
30
+ return fallback;
31
+ return Math.floor(n);
32
+ }
33
+ /**
34
+ * 不确定单位时使用:**大于等于 {@link UNIX_TIMESTAMP_MS_MIN} 视为毫秒**,否则视为秒。
35
+ * 适用于部分文档不清或中间层混传的场景。
36
+ */
37
+ export function coerceUnixToEventMs(value, options) {
38
+ const fallback = options?.fallbackMs ?? Date.now();
39
+ if (value == null || value === "")
40
+ return fallback;
41
+ const n = typeof value === "string" ? parseFloat(value) : Number(value);
42
+ if (!Number.isFinite(n) || n <= 0)
43
+ return fallback;
44
+ if (n >= UNIX_TIMESTAMP_MS_MIN)
45
+ return Math.floor(n);
46
+ return Math.floor(n * 1000);
47
+ }
48
+ /**
49
+ * 需要 **Unix 秒** 的字段(如部分协议里的 message `time`)时使用。
50
+ *
51
+ * @param seconds - 秒级时间戳
52
+ * @param options.fallbackSec - 无效时的秒级时间,默认当前 `Date.now()/1000` 取整
53
+ */
54
+ export function toUnixSeconds(seconds, options) {
55
+ const fallback = options?.fallbackSec ?? Math.floor(Date.now() / 1000);
56
+ if (seconds == null || seconds === "")
57
+ return fallback;
58
+ const n = typeof seconds === "string" ? parseFloat(seconds) : Number(seconds);
59
+ if (!Number.isFinite(n) || n <= 0)
60
+ return fallback;
61
+ return Math.floor(n);
62
+ }
63
+ /**
64
+ * 平台返回 **ISO 8601 字符串**或其它 `Date` 可解析格式(如 QQ 开放消息 `timestamp`)时,转为事件用的 **毫秒**。
65
+ */
66
+ export function dateLikeToEventMs(value, options) {
67
+ const fallback = options?.fallbackMs ?? Date.now();
68
+ if (value == null || value === "")
69
+ return fallback;
70
+ const t = new Date(value).getTime();
71
+ return Number.isFinite(t) ? t : fallback;
72
+ }
package/lib/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import 'koa-body';
1
2
  export type Dict<T = any, K extends string | symbol = string> = Record<K, T>;
2
3
  export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "mark" | "off";
3
4
  export type Dispose = () => any;
package/lib/types.js CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ // 导入 koa-body 的类型定义,它会自动扩展 Request 接口
2
+ import 'koa-body';
package/lib/utils.d.ts CHANGED
@@ -11,8 +11,9 @@ export declare function omit<T, K extends keyof T>(source: T, keys?: Iterable<K>
11
11
  * 将驼峰命名替换为下划线分割命名
12
12
  * @param name
13
13
  * @returns
14
- * @todo 是否应该改名 ToUnderLine()?
15
14
  */
15
+ export declare function toUnderLine(name: string): string;
16
+ /** @deprecated Use {@link toUnderLine} instead. */
16
17
  export declare function toLine<T extends string>(name: T): string;
17
18
  export interface Class<T = any> {
18
19
  new (...args: any[]): T;