imean-service-engine 1.4.2 → 1.6.0
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 +582 -582
- package/dist/mod.cjs +124 -11
- package/dist/mod.d.cts +55 -4
- package/dist/mod.d.ts +55 -4
- package/dist/mod.js +124 -11
- package/package.json +87 -86
package/dist/mod.cjs
CHANGED
@@ -292,8 +292,6 @@ async function formatCode(code) {
|
|
292
292
|
return code;
|
293
293
|
}
|
294
294
|
}
|
295
|
-
|
296
|
-
// core/generator.ts
|
297
295
|
function getZodTypeString(schema, defaultOptional = false) {
|
298
296
|
function processType(type) {
|
299
297
|
const def = type._def;
|
@@ -303,6 +301,9 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
303
301
|
if (def.typeName === "ZodOptional") {
|
304
302
|
return processType(def.innerType);
|
305
303
|
}
|
304
|
+
if (def.typeName === "ZodEffects" && def.schema?._def.typeName !== "ZodAny") {
|
305
|
+
return processType(def.schema);
|
306
|
+
}
|
306
307
|
switch (def.typeName) {
|
307
308
|
case "ZodString": {
|
308
309
|
return "string";
|
@@ -310,6 +311,9 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
310
311
|
case "ZodNumber": {
|
311
312
|
return "number";
|
312
313
|
}
|
314
|
+
case "ZodBigInt": {
|
315
|
+
return "bigint";
|
316
|
+
}
|
313
317
|
case "ZodBoolean": {
|
314
318
|
return "boolean";
|
315
319
|
}
|
@@ -775,10 +779,11 @@ var Microservice = class {
|
|
775
779
|
lease;
|
776
780
|
scheduler;
|
777
781
|
abortController;
|
778
|
-
isShuttingDown = false;
|
779
782
|
statisticsTimer;
|
780
783
|
wsHandler;
|
781
784
|
actionHandlers = /* @__PURE__ */ new Map();
|
785
|
+
activeRequests = /* @__PURE__ */ new Map();
|
786
|
+
status = "running";
|
782
787
|
modules = /* @__PURE__ */ new Map();
|
783
788
|
fetch;
|
784
789
|
options;
|
@@ -801,6 +806,10 @@ var Microservice = class {
|
|
801
806
|
websocket: { enabled: false },
|
802
807
|
cacheAdapter: new MemoryCacheAdapter(),
|
803
808
|
plugins: [],
|
809
|
+
gracefulShutdown: {
|
810
|
+
timeout: 3e4,
|
811
|
+
cleanupHooks: []
|
812
|
+
},
|
804
813
|
...options
|
805
814
|
};
|
806
815
|
this.cache = this.options.cacheAdapter;
|
@@ -879,7 +888,7 @@ var Microservice = class {
|
|
879
888
|
this.app.get(prefix, (ctx) => {
|
880
889
|
const name = this.options.name ?? "Microservice";
|
881
890
|
const version = this.options.version ?? "1.0.0";
|
882
|
-
return ctx.text(`${name} is
|
891
|
+
return ctx.text(`${name} is ${this.status}. version: ${version}`);
|
883
892
|
});
|
884
893
|
this.app.get(`${prefix}/health`, (ctx) => {
|
885
894
|
return ctx.json({
|
@@ -895,7 +904,9 @@ var Microservice = class {
|
|
895
904
|
version: this.options.version,
|
896
905
|
env: this.options.env,
|
897
906
|
modules: this.getModules(),
|
898
|
-
stats: Object.fromEntries(this.statsMap)
|
907
|
+
stats: Object.fromEntries(this.statsMap),
|
908
|
+
activeRequests: this.getActiveRequestCount(),
|
909
|
+
status: this.status
|
899
910
|
});
|
900
911
|
});
|
901
912
|
this.app.get(`${prefix}/client.ts`, async (ctx) => {
|
@@ -1092,7 +1103,12 @@ var Microservice = class {
|
|
1092
1103
|
process.on("SIGINT", () => {
|
1093
1104
|
logger_default.info(`
|
1094
1105
|
Received SIGINT signal`);
|
1095
|
-
this.
|
1106
|
+
this.shutdown();
|
1107
|
+
});
|
1108
|
+
process.on("SIGTERM", () => {
|
1109
|
+
logger_default.info(`
|
1110
|
+
Received SIGTERM signal`);
|
1111
|
+
this.shutdown();
|
1096
1112
|
});
|
1097
1113
|
process.on("unhandledrejection", (event) => {
|
1098
1114
|
logger_default.error("Unhandled rejection:", event.reason);
|
@@ -1104,14 +1120,16 @@ Received SIGINT signal`);
|
|
1104
1120
|
/**
|
1105
1121
|
* 优雅停机
|
1106
1122
|
*/
|
1107
|
-
async
|
1108
|
-
if (this.
|
1109
|
-
this.
|
1110
|
-
logger_default.info("\
|
1123
|
+
async shutdown() {
|
1124
|
+
if (this.status === "shutting_down") return;
|
1125
|
+
this.status = "shutting_down";
|
1126
|
+
logger_default.info("\nshutdown initiated...");
|
1111
1127
|
if (this.statisticsTimer) clearInterval(this.statisticsTimer);
|
1112
1128
|
try {
|
1129
|
+
await this.waitForActiveRequests();
|
1130
|
+
await this.executeCleanupHooks();
|
1113
1131
|
await this.stop();
|
1114
|
-
logger_default.info("
|
1132
|
+
logger_default.info("shutdown completed");
|
1115
1133
|
} catch (error) {
|
1116
1134
|
logger_default.error("Error during shutdown:", error);
|
1117
1135
|
process.exit(1);
|
@@ -1119,6 +1137,57 @@ Received SIGINT signal`);
|
|
1119
1137
|
process.exit(0);
|
1120
1138
|
}
|
1121
1139
|
}
|
1140
|
+
/**
|
1141
|
+
* 等待所有活跃请求完成
|
1142
|
+
*/
|
1143
|
+
async waitForActiveRequests() {
|
1144
|
+
const timeout = this.options.gracefulShutdown?.timeout || 3e4;
|
1145
|
+
const startTime = Date.now();
|
1146
|
+
logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
|
1147
|
+
return new Promise((resolve) => {
|
1148
|
+
const checkInterval = setInterval(() => {
|
1149
|
+
const elapsed = Date.now() - startTime;
|
1150
|
+
if (this.activeRequests.size === 0) {
|
1151
|
+
clearInterval(checkInterval);
|
1152
|
+
logger_default.info("All active requests completed");
|
1153
|
+
resolve();
|
1154
|
+
} else if (elapsed >= timeout) {
|
1155
|
+
clearInterval(checkInterval);
|
1156
|
+
logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
|
1157
|
+
resolve();
|
1158
|
+
} else {
|
1159
|
+
logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
|
1160
|
+
}
|
1161
|
+
}, 1e3);
|
1162
|
+
});
|
1163
|
+
}
|
1164
|
+
/**
|
1165
|
+
* 执行清理hook
|
1166
|
+
*/
|
1167
|
+
async executeCleanupHooks() {
|
1168
|
+
const hooks = this.options.gracefulShutdown?.cleanupHooks || [];
|
1169
|
+
if (hooks.length === 0) {
|
1170
|
+
logger_default.info("No cleanup hooks configured");
|
1171
|
+
return;
|
1172
|
+
}
|
1173
|
+
logger_default.info(`Executing ${hooks.length} cleanup hooks...`);
|
1174
|
+
for (const hook of hooks) {
|
1175
|
+
try {
|
1176
|
+
const timeout = hook.timeout || 5e3;
|
1177
|
+
logger_default.info(`Executing cleanup hook: ${hook.name}`);
|
1178
|
+
await Promise.race([
|
1179
|
+
Promise.resolve(hook.cleanup()),
|
1180
|
+
new Promise(
|
1181
|
+
(_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
|
1182
|
+
)
|
1183
|
+
]);
|
1184
|
+
logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
|
1185
|
+
} catch (error) {
|
1186
|
+
logger_default.error(`Cleanup hook ${hook.name} failed:`, error);
|
1187
|
+
}
|
1188
|
+
}
|
1189
|
+
logger_default.info("All cleanup hooks completed");
|
1190
|
+
}
|
1122
1191
|
initStatsEventManager() {
|
1123
1192
|
this.statisticsTimer = setInterval(async () => {
|
1124
1193
|
await this.updateStats();
|
@@ -1160,14 +1229,54 @@ Received SIGINT signal`);
|
|
1160
1229
|
}
|
1161
1230
|
return handler;
|
1162
1231
|
}
|
1232
|
+
/**
|
1233
|
+
* 添加活跃请求跟踪
|
1234
|
+
*/
|
1235
|
+
addActiveRequest(requestId, requestInfo) {
|
1236
|
+
this.activeRequests.set(requestId, requestInfo);
|
1237
|
+
}
|
1238
|
+
/**
|
1239
|
+
* 移除活跃请求跟踪
|
1240
|
+
*/
|
1241
|
+
removeActiveRequest(requestId) {
|
1242
|
+
this.activeRequests.delete(requestId);
|
1243
|
+
}
|
1244
|
+
/**
|
1245
|
+
* 获取当前活跃请求数量
|
1246
|
+
*/
|
1247
|
+
getActiveRequestCount() {
|
1248
|
+
return this.activeRequests.size;
|
1249
|
+
}
|
1250
|
+
/**
|
1251
|
+
* 获取当前活跃请求信息
|
1252
|
+
*/
|
1253
|
+
getActiveRequests() {
|
1254
|
+
return Array.from(this.activeRequests.values());
|
1255
|
+
}
|
1163
1256
|
handleRequest = async (ctx) => {
|
1164
1257
|
const { moduleName, actionName } = ctx.req.param();
|
1165
1258
|
const handler = this.getActionHandler(moduleName, actionName);
|
1259
|
+
const requestId = crypto.randomUUID();
|
1260
|
+
const startTime = Date.now();
|
1261
|
+
if (this.status === "shutting_down") {
|
1262
|
+
return ctx.json({
|
1263
|
+
success: false,
|
1264
|
+
error: "Service is shutting down"
|
1265
|
+
}, 503);
|
1266
|
+
}
|
1166
1267
|
try {
|
1167
1268
|
const paramsText = await ctx.req.text();
|
1269
|
+
this.addActiveRequest(requestId, {
|
1270
|
+
id: requestId,
|
1271
|
+
moduleName,
|
1272
|
+
actionName,
|
1273
|
+
startTime,
|
1274
|
+
params: paramsText
|
1275
|
+
});
|
1168
1276
|
const result = await handler.handle(paramsText);
|
1169
1277
|
if (handler.metadata.stream) {
|
1170
1278
|
const encoder = new TextEncoder();
|
1279
|
+
const microservice = this;
|
1171
1280
|
const stream = new ReadableStream({
|
1172
1281
|
async start(controller) {
|
1173
1282
|
try {
|
@@ -1191,6 +1300,8 @@ Received SIGINT signal`);
|
|
1191
1300
|
encoder.encode(ejson4__default.default.stringify(response) + "\n")
|
1192
1301
|
);
|
1193
1302
|
controller.close();
|
1303
|
+
} finally {
|
1304
|
+
microservice.removeActiveRequest(requestId);
|
1194
1305
|
}
|
1195
1306
|
}
|
1196
1307
|
});
|
@@ -1202,8 +1313,10 @@ Received SIGINT signal`);
|
|
1202
1313
|
}
|
1203
1314
|
});
|
1204
1315
|
}
|
1316
|
+
this.removeActiveRequest(requestId);
|
1205
1317
|
return ctx.text(ejson4__default.default.stringify({ success: true, data: result }));
|
1206
1318
|
} catch (error) {
|
1319
|
+
this.removeActiveRequest(requestId);
|
1207
1320
|
return ctx.json({ success: false, error: error.message });
|
1208
1321
|
}
|
1209
1322
|
};
|
package/dist/mod.d.cts
CHANGED
@@ -155,6 +155,32 @@ interface MicroserviceOptions {
|
|
155
155
|
};
|
156
156
|
cacheAdapter?: CacheAdapter;
|
157
157
|
plugins?: Plugin[];
|
158
|
+
gracefulShutdown?: {
|
159
|
+
/** 优雅停机超时时间(毫秒),默认 30 秒 */
|
160
|
+
timeout?: number;
|
161
|
+
/** 清理hook列表 */
|
162
|
+
cleanupHooks?: CleanupHook[];
|
163
|
+
};
|
164
|
+
}
|
165
|
+
interface CleanupHook {
|
166
|
+
/** hook名称 */
|
167
|
+
name: string;
|
168
|
+
/** 清理函数 */
|
169
|
+
cleanup: () => Promise<void> | void;
|
170
|
+
/** 超时时间(毫秒),默认 5 秒 */
|
171
|
+
timeout?: number;
|
172
|
+
}
|
173
|
+
interface RequestInfo {
|
174
|
+
/** 请求ID */
|
175
|
+
id: string;
|
176
|
+
/** 模块名称 */
|
177
|
+
moduleName: string;
|
178
|
+
/** 动作名称 */
|
179
|
+
actionName: string;
|
180
|
+
/** 开始时间 */
|
181
|
+
startTime: number;
|
182
|
+
/** 请求参数 */
|
183
|
+
params?: any;
|
158
184
|
}
|
159
185
|
interface ModuleInfo extends ModuleOptions {
|
160
186
|
actions: Record<string, ActionMetadata>;
|
@@ -197,10 +223,11 @@ declare class Microservice {
|
|
197
223
|
private lease?;
|
198
224
|
private scheduler?;
|
199
225
|
private abortController?;
|
200
|
-
private isShuttingDown;
|
201
226
|
private statisticsTimer?;
|
202
227
|
private wsHandler?;
|
203
228
|
private actionHandlers;
|
229
|
+
private activeRequests;
|
230
|
+
private status;
|
204
231
|
modules: Map<string, ModuleInfo>;
|
205
232
|
readonly fetch: typeof fetch;
|
206
233
|
options: Required<MicroserviceOptions>;
|
@@ -236,7 +263,7 @@ declare class Microservice {
|
|
236
263
|
/**
|
237
264
|
* 停止服务
|
238
265
|
*/
|
239
|
-
stop
|
266
|
+
private stop;
|
240
267
|
/**
|
241
268
|
* 初始化停机处理
|
242
269
|
*/
|
@@ -244,10 +271,34 @@ declare class Microservice {
|
|
244
271
|
/**
|
245
272
|
* 优雅停机
|
246
273
|
*/
|
247
|
-
|
274
|
+
shutdown(): Promise<void>;
|
275
|
+
/**
|
276
|
+
* 等待所有活跃请求完成
|
277
|
+
*/
|
278
|
+
private waitForActiveRequests;
|
279
|
+
/**
|
280
|
+
* 执行清理hook
|
281
|
+
*/
|
282
|
+
private executeCleanupHooks;
|
248
283
|
private initStatsEventManager;
|
249
284
|
private updateStats;
|
250
285
|
getActionHandler(moduleName: string, actionName: string): ActionHandler;
|
286
|
+
/**
|
287
|
+
* 添加活跃请求跟踪
|
288
|
+
*/
|
289
|
+
private addActiveRequest;
|
290
|
+
/**
|
291
|
+
* 移除活跃请求跟踪
|
292
|
+
*/
|
293
|
+
private removeActiveRequest;
|
294
|
+
/**
|
295
|
+
* 获取当前活跃请求数量
|
296
|
+
*/
|
297
|
+
getActiveRequestCount(): number;
|
298
|
+
/**
|
299
|
+
* 获取当前活跃请求信息
|
300
|
+
*/
|
301
|
+
getActiveRequests(): RequestInfo[];
|
251
302
|
private handleRequest;
|
252
303
|
private initPlugins;
|
253
304
|
init(): Promise<void>;
|
@@ -286,4 +337,4 @@ declare function Schedule(options: ScheduleOptions): Function;
|
|
286
337
|
|
287
338
|
declare const logger: winston.Logger;
|
288
339
|
|
289
|
-
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
340
|
+
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
package/dist/mod.d.ts
CHANGED
@@ -155,6 +155,32 @@ interface MicroserviceOptions {
|
|
155
155
|
};
|
156
156
|
cacheAdapter?: CacheAdapter;
|
157
157
|
plugins?: Plugin[];
|
158
|
+
gracefulShutdown?: {
|
159
|
+
/** 优雅停机超时时间(毫秒),默认 30 秒 */
|
160
|
+
timeout?: number;
|
161
|
+
/** 清理hook列表 */
|
162
|
+
cleanupHooks?: CleanupHook[];
|
163
|
+
};
|
164
|
+
}
|
165
|
+
interface CleanupHook {
|
166
|
+
/** hook名称 */
|
167
|
+
name: string;
|
168
|
+
/** 清理函数 */
|
169
|
+
cleanup: () => Promise<void> | void;
|
170
|
+
/** 超时时间(毫秒),默认 5 秒 */
|
171
|
+
timeout?: number;
|
172
|
+
}
|
173
|
+
interface RequestInfo {
|
174
|
+
/** 请求ID */
|
175
|
+
id: string;
|
176
|
+
/** 模块名称 */
|
177
|
+
moduleName: string;
|
178
|
+
/** 动作名称 */
|
179
|
+
actionName: string;
|
180
|
+
/** 开始时间 */
|
181
|
+
startTime: number;
|
182
|
+
/** 请求参数 */
|
183
|
+
params?: any;
|
158
184
|
}
|
159
185
|
interface ModuleInfo extends ModuleOptions {
|
160
186
|
actions: Record<string, ActionMetadata>;
|
@@ -197,10 +223,11 @@ declare class Microservice {
|
|
197
223
|
private lease?;
|
198
224
|
private scheduler?;
|
199
225
|
private abortController?;
|
200
|
-
private isShuttingDown;
|
201
226
|
private statisticsTimer?;
|
202
227
|
private wsHandler?;
|
203
228
|
private actionHandlers;
|
229
|
+
private activeRequests;
|
230
|
+
private status;
|
204
231
|
modules: Map<string, ModuleInfo>;
|
205
232
|
readonly fetch: typeof fetch;
|
206
233
|
options: Required<MicroserviceOptions>;
|
@@ -236,7 +263,7 @@ declare class Microservice {
|
|
236
263
|
/**
|
237
264
|
* 停止服务
|
238
265
|
*/
|
239
|
-
stop
|
266
|
+
private stop;
|
240
267
|
/**
|
241
268
|
* 初始化停机处理
|
242
269
|
*/
|
@@ -244,10 +271,34 @@ declare class Microservice {
|
|
244
271
|
/**
|
245
272
|
* 优雅停机
|
246
273
|
*/
|
247
|
-
|
274
|
+
shutdown(): Promise<void>;
|
275
|
+
/**
|
276
|
+
* 等待所有活跃请求完成
|
277
|
+
*/
|
278
|
+
private waitForActiveRequests;
|
279
|
+
/**
|
280
|
+
* 执行清理hook
|
281
|
+
*/
|
282
|
+
private executeCleanupHooks;
|
248
283
|
private initStatsEventManager;
|
249
284
|
private updateStats;
|
250
285
|
getActionHandler(moduleName: string, actionName: string): ActionHandler;
|
286
|
+
/**
|
287
|
+
* 添加活跃请求跟踪
|
288
|
+
*/
|
289
|
+
private addActiveRequest;
|
290
|
+
/**
|
291
|
+
* 移除活跃请求跟踪
|
292
|
+
*/
|
293
|
+
private removeActiveRequest;
|
294
|
+
/**
|
295
|
+
* 获取当前活跃请求数量
|
296
|
+
*/
|
297
|
+
getActiveRequestCount(): number;
|
298
|
+
/**
|
299
|
+
* 获取当前活跃请求信息
|
300
|
+
*/
|
301
|
+
getActiveRequests(): RequestInfo[];
|
251
302
|
private handleRequest;
|
252
303
|
private initPlugins;
|
253
304
|
init(): Promise<void>;
|
@@ -286,4 +337,4 @@ declare function Schedule(options: ScheduleOptions): Function;
|
|
286
337
|
|
287
338
|
declare const logger: winston.Logger;
|
288
339
|
|
289
|
-
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
340
|
+
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
package/dist/mod.js
CHANGED
@@ -283,8 +283,6 @@ async function formatCode(code) {
|
|
283
283
|
return code;
|
284
284
|
}
|
285
285
|
}
|
286
|
-
|
287
|
-
// core/generator.ts
|
288
286
|
function getZodTypeString(schema, defaultOptional = false) {
|
289
287
|
function processType(type) {
|
290
288
|
const def = type._def;
|
@@ -294,6 +292,9 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
294
292
|
if (def.typeName === "ZodOptional") {
|
295
293
|
return processType(def.innerType);
|
296
294
|
}
|
295
|
+
if (def.typeName === "ZodEffects" && def.schema?._def.typeName !== "ZodAny") {
|
296
|
+
return processType(def.schema);
|
297
|
+
}
|
297
298
|
switch (def.typeName) {
|
298
299
|
case "ZodString": {
|
299
300
|
return "string";
|
@@ -301,6 +302,9 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
301
302
|
case "ZodNumber": {
|
302
303
|
return "number";
|
303
304
|
}
|
305
|
+
case "ZodBigInt": {
|
306
|
+
return "bigint";
|
307
|
+
}
|
304
308
|
case "ZodBoolean": {
|
305
309
|
return "boolean";
|
306
310
|
}
|
@@ -766,10 +770,11 @@ var Microservice = class {
|
|
766
770
|
lease;
|
767
771
|
scheduler;
|
768
772
|
abortController;
|
769
|
-
isShuttingDown = false;
|
770
773
|
statisticsTimer;
|
771
774
|
wsHandler;
|
772
775
|
actionHandlers = /* @__PURE__ */ new Map();
|
776
|
+
activeRequests = /* @__PURE__ */ new Map();
|
777
|
+
status = "running";
|
773
778
|
modules = /* @__PURE__ */ new Map();
|
774
779
|
fetch;
|
775
780
|
options;
|
@@ -792,6 +797,10 @@ var Microservice = class {
|
|
792
797
|
websocket: { enabled: false },
|
793
798
|
cacheAdapter: new MemoryCacheAdapter(),
|
794
799
|
plugins: [],
|
800
|
+
gracefulShutdown: {
|
801
|
+
timeout: 3e4,
|
802
|
+
cleanupHooks: []
|
803
|
+
},
|
795
804
|
...options
|
796
805
|
};
|
797
806
|
this.cache = this.options.cacheAdapter;
|
@@ -870,7 +879,7 @@ var Microservice = class {
|
|
870
879
|
this.app.get(prefix, (ctx) => {
|
871
880
|
const name = this.options.name ?? "Microservice";
|
872
881
|
const version = this.options.version ?? "1.0.0";
|
873
|
-
return ctx.text(`${name} is
|
882
|
+
return ctx.text(`${name} is ${this.status}. version: ${version}`);
|
874
883
|
});
|
875
884
|
this.app.get(`${prefix}/health`, (ctx) => {
|
876
885
|
return ctx.json({
|
@@ -886,7 +895,9 @@ var Microservice = class {
|
|
886
895
|
version: this.options.version,
|
887
896
|
env: this.options.env,
|
888
897
|
modules: this.getModules(),
|
889
|
-
stats: Object.fromEntries(this.statsMap)
|
898
|
+
stats: Object.fromEntries(this.statsMap),
|
899
|
+
activeRequests: this.getActiveRequestCount(),
|
900
|
+
status: this.status
|
890
901
|
});
|
891
902
|
});
|
892
903
|
this.app.get(`${prefix}/client.ts`, async (ctx) => {
|
@@ -1083,7 +1094,12 @@ var Microservice = class {
|
|
1083
1094
|
process.on("SIGINT", () => {
|
1084
1095
|
logger_default.info(`
|
1085
1096
|
Received SIGINT signal`);
|
1086
|
-
this.
|
1097
|
+
this.shutdown();
|
1098
|
+
});
|
1099
|
+
process.on("SIGTERM", () => {
|
1100
|
+
logger_default.info(`
|
1101
|
+
Received SIGTERM signal`);
|
1102
|
+
this.shutdown();
|
1087
1103
|
});
|
1088
1104
|
process.on("unhandledrejection", (event) => {
|
1089
1105
|
logger_default.error("Unhandled rejection:", event.reason);
|
@@ -1095,14 +1111,16 @@ Received SIGINT signal`);
|
|
1095
1111
|
/**
|
1096
1112
|
* 优雅停机
|
1097
1113
|
*/
|
1098
|
-
async
|
1099
|
-
if (this.
|
1100
|
-
this.
|
1101
|
-
logger_default.info("\
|
1114
|
+
async shutdown() {
|
1115
|
+
if (this.status === "shutting_down") return;
|
1116
|
+
this.status = "shutting_down";
|
1117
|
+
logger_default.info("\nshutdown initiated...");
|
1102
1118
|
if (this.statisticsTimer) clearInterval(this.statisticsTimer);
|
1103
1119
|
try {
|
1120
|
+
await this.waitForActiveRequests();
|
1121
|
+
await this.executeCleanupHooks();
|
1104
1122
|
await this.stop();
|
1105
|
-
logger_default.info("
|
1123
|
+
logger_default.info("shutdown completed");
|
1106
1124
|
} catch (error) {
|
1107
1125
|
logger_default.error("Error during shutdown:", error);
|
1108
1126
|
process.exit(1);
|
@@ -1110,6 +1128,57 @@ Received SIGINT signal`);
|
|
1110
1128
|
process.exit(0);
|
1111
1129
|
}
|
1112
1130
|
}
|
1131
|
+
/**
|
1132
|
+
* 等待所有活跃请求完成
|
1133
|
+
*/
|
1134
|
+
async waitForActiveRequests() {
|
1135
|
+
const timeout = this.options.gracefulShutdown?.timeout || 3e4;
|
1136
|
+
const startTime = Date.now();
|
1137
|
+
logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
|
1138
|
+
return new Promise((resolve) => {
|
1139
|
+
const checkInterval = setInterval(() => {
|
1140
|
+
const elapsed = Date.now() - startTime;
|
1141
|
+
if (this.activeRequests.size === 0) {
|
1142
|
+
clearInterval(checkInterval);
|
1143
|
+
logger_default.info("All active requests completed");
|
1144
|
+
resolve();
|
1145
|
+
} else if (elapsed >= timeout) {
|
1146
|
+
clearInterval(checkInterval);
|
1147
|
+
logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
|
1148
|
+
resolve();
|
1149
|
+
} else {
|
1150
|
+
logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
|
1151
|
+
}
|
1152
|
+
}, 1e3);
|
1153
|
+
});
|
1154
|
+
}
|
1155
|
+
/**
|
1156
|
+
* 执行清理hook
|
1157
|
+
*/
|
1158
|
+
async executeCleanupHooks() {
|
1159
|
+
const hooks = this.options.gracefulShutdown?.cleanupHooks || [];
|
1160
|
+
if (hooks.length === 0) {
|
1161
|
+
logger_default.info("No cleanup hooks configured");
|
1162
|
+
return;
|
1163
|
+
}
|
1164
|
+
logger_default.info(`Executing ${hooks.length} cleanup hooks...`);
|
1165
|
+
for (const hook of hooks) {
|
1166
|
+
try {
|
1167
|
+
const timeout = hook.timeout || 5e3;
|
1168
|
+
logger_default.info(`Executing cleanup hook: ${hook.name}`);
|
1169
|
+
await Promise.race([
|
1170
|
+
Promise.resolve(hook.cleanup()),
|
1171
|
+
new Promise(
|
1172
|
+
(_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
|
1173
|
+
)
|
1174
|
+
]);
|
1175
|
+
logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
|
1176
|
+
} catch (error) {
|
1177
|
+
logger_default.error(`Cleanup hook ${hook.name} failed:`, error);
|
1178
|
+
}
|
1179
|
+
}
|
1180
|
+
logger_default.info("All cleanup hooks completed");
|
1181
|
+
}
|
1113
1182
|
initStatsEventManager() {
|
1114
1183
|
this.statisticsTimer = setInterval(async () => {
|
1115
1184
|
await this.updateStats();
|
@@ -1151,14 +1220,54 @@ Received SIGINT signal`);
|
|
1151
1220
|
}
|
1152
1221
|
return handler;
|
1153
1222
|
}
|
1223
|
+
/**
|
1224
|
+
* 添加活跃请求跟踪
|
1225
|
+
*/
|
1226
|
+
addActiveRequest(requestId, requestInfo) {
|
1227
|
+
this.activeRequests.set(requestId, requestInfo);
|
1228
|
+
}
|
1229
|
+
/**
|
1230
|
+
* 移除活跃请求跟踪
|
1231
|
+
*/
|
1232
|
+
removeActiveRequest(requestId) {
|
1233
|
+
this.activeRequests.delete(requestId);
|
1234
|
+
}
|
1235
|
+
/**
|
1236
|
+
* 获取当前活跃请求数量
|
1237
|
+
*/
|
1238
|
+
getActiveRequestCount() {
|
1239
|
+
return this.activeRequests.size;
|
1240
|
+
}
|
1241
|
+
/**
|
1242
|
+
* 获取当前活跃请求信息
|
1243
|
+
*/
|
1244
|
+
getActiveRequests() {
|
1245
|
+
return Array.from(this.activeRequests.values());
|
1246
|
+
}
|
1154
1247
|
handleRequest = async (ctx) => {
|
1155
1248
|
const { moduleName, actionName } = ctx.req.param();
|
1156
1249
|
const handler = this.getActionHandler(moduleName, actionName);
|
1250
|
+
const requestId = crypto.randomUUID();
|
1251
|
+
const startTime = Date.now();
|
1252
|
+
if (this.status === "shutting_down") {
|
1253
|
+
return ctx.json({
|
1254
|
+
success: false,
|
1255
|
+
error: "Service is shutting down"
|
1256
|
+
}, 503);
|
1257
|
+
}
|
1157
1258
|
try {
|
1158
1259
|
const paramsText = await ctx.req.text();
|
1260
|
+
this.addActiveRequest(requestId, {
|
1261
|
+
id: requestId,
|
1262
|
+
moduleName,
|
1263
|
+
actionName,
|
1264
|
+
startTime,
|
1265
|
+
params: paramsText
|
1266
|
+
});
|
1159
1267
|
const result = await handler.handle(paramsText);
|
1160
1268
|
if (handler.metadata.stream) {
|
1161
1269
|
const encoder = new TextEncoder();
|
1270
|
+
const microservice = this;
|
1162
1271
|
const stream = new ReadableStream({
|
1163
1272
|
async start(controller) {
|
1164
1273
|
try {
|
@@ -1182,6 +1291,8 @@ Received SIGINT signal`);
|
|
1182
1291
|
encoder.encode(ejson4.stringify(response) + "\n")
|
1183
1292
|
);
|
1184
1293
|
controller.close();
|
1294
|
+
} finally {
|
1295
|
+
microservice.removeActiveRequest(requestId);
|
1185
1296
|
}
|
1186
1297
|
}
|
1187
1298
|
});
|
@@ -1193,8 +1304,10 @@ Received SIGINT signal`);
|
|
1193
1304
|
}
|
1194
1305
|
});
|
1195
1306
|
}
|
1307
|
+
this.removeActiveRequest(requestId);
|
1196
1308
|
return ctx.text(ejson4.stringify({ success: true, data: result }));
|
1197
1309
|
} catch (error) {
|
1310
|
+
this.removeActiveRequest(requestId);
|
1198
1311
|
return ctx.json({ success: false, error: error.message });
|
1199
1312
|
}
|
1200
1313
|
};
|