imean-service-engine 1.5.0 → 1.7.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/dist/mod.js CHANGED
@@ -6,6 +6,7 @@ import { serve } from '@hono/node-server';
6
6
  import { Etcd3 } from 'etcd3';
7
7
  import fs from 'fs-extra';
8
8
  import { Hono } from 'hono';
9
+ import { timing } from 'hono/timing';
9
10
  import { trace, SpanStatusCode } from '@opentelemetry/api';
10
11
  import winston, { format } from 'winston';
11
12
  import prettier from 'prettier';
@@ -17,6 +18,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
18
  import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
18
19
  import { streamSSE } from 'hono/streaming';
19
20
  import { ulid } from 'ulid';
21
+ import { html } from 'hono/html';
22
+ import { jsx, jsxs } from 'hono/jsx/jsx-runtime';
20
23
  export { default as dayjs } from 'dayjs';
21
24
 
22
25
  // mod.ts
@@ -586,6 +589,62 @@ var ActionHandler = class {
586
589
  function isAsyncIterable(obj) {
587
590
  return obj != null && typeof obj[Symbol.asyncIterator] === "function";
588
591
  }
592
+
593
+ // decorators/page.ts
594
+ var PAGE_METADATA = Symbol("page:metadata");
595
+ function Page(options) {
596
+ return function(_target, context) {
597
+ const methodName = context.name;
598
+ context.addInitializer(function() {
599
+ const prototype = this.constructor.prototype;
600
+ const existingMetadata = prototype[PAGE_METADATA] || {};
601
+ existingMetadata[methodName] = {
602
+ name: methodName,
603
+ description: options.description || "",
604
+ method: options.method,
605
+ path: options.path
606
+ };
607
+ prototype[PAGE_METADATA] = existingMetadata;
608
+ });
609
+ };
610
+ }
611
+ function getPageMetadata(target) {
612
+ return target.constructor.prototype[PAGE_METADATA] ?? {};
613
+ }
614
+ var tracer3 = trace.getTracer("page-handler");
615
+ var PageHandler = class {
616
+ constructor(moduleInstance, options, moduleName) {
617
+ this.moduleInstance = moduleInstance;
618
+ this.options = options;
619
+ this.moduleName = moduleName;
620
+ }
621
+ async handle(ctx) {
622
+ return await tracer3.startActiveSpan(
623
+ `handle ${this.moduleName}.${this.options.name}`,
624
+ async (span) => {
625
+ span.setAttribute("module", this.moduleName);
626
+ span.setAttribute("page", this.options.name);
627
+ span.setAttribute("path", this.options.path);
628
+ try {
629
+ const result = await this.moduleInstance[this.options.name].apply(
630
+ this.moduleInstance,
631
+ [ctx]
632
+ );
633
+ return ctx.html(result);
634
+ } catch (error) {
635
+ span.recordException(error);
636
+ span.setStatus({
637
+ code: SpanStatusCode.ERROR,
638
+ message: error.message
639
+ });
640
+ throw error;
641
+ } finally {
642
+ span.end();
643
+ }
644
+ }
645
+ );
646
+ }
647
+ };
589
648
  var WebSocketHandler = class {
590
649
  constructor(microservice, options) {
591
650
  this.microservice = microservice;
@@ -770,10 +829,12 @@ var Microservice = class {
770
829
  lease;
771
830
  scheduler;
772
831
  abortController;
773
- isShuttingDown = false;
774
832
  statisticsTimer;
775
833
  wsHandler;
776
834
  actionHandlers = /* @__PURE__ */ new Map();
835
+ pageHandlers = /* @__PURE__ */ new Map();
836
+ activeRequests = /* @__PURE__ */ new Map();
837
+ status = "running";
777
838
  modules = /* @__PURE__ */ new Map();
778
839
  fetch;
779
840
  options;
@@ -781,6 +842,7 @@ var Microservice = class {
781
842
  serviceId;
782
843
  constructor(options) {
783
844
  this.app = new Hono();
845
+ this.app.use(timing());
784
846
  this.nodeWebSocket = createNodeWebSocket({ app: this.app });
785
847
  this.serviceId = crypto.randomUUID();
786
848
  this.options = {
@@ -796,6 +858,10 @@ var Microservice = class {
796
858
  websocket: { enabled: false },
797
859
  cacheAdapter: new MemoryCacheAdapter(),
798
860
  plugins: [],
861
+ gracefulShutdown: {
862
+ timeout: 3e4,
863
+ cleanupHooks: []
864
+ },
799
865
  ...options
800
866
  };
801
867
  this.cache = this.options.cacheAdapter;
@@ -815,6 +881,11 @@ var Microservice = class {
815
881
  }
816
882
  await this.registerService(true);
817
883
  await this.initPlugins();
884
+ this.app.get(this.options.prefix, (ctx) => {
885
+ const name = this.options.name ?? "Microservice";
886
+ const version = this.options.version ?? "1.0.0";
887
+ return ctx.text(`${name} is ${this.status}. version: ${version}`);
888
+ });
818
889
  }
819
890
  async initModules() {
820
891
  for (const ModuleClass of this.options.modules) {
@@ -829,6 +900,7 @@ var Microservice = class {
829
900
  logger_default.info(`[ \u6CE8\u518C\u6A21\u5757 ] ${moduleName} ${metadata.options.description}`);
830
901
  this.modules.set(moduleName, moduleInstance);
831
902
  const actions = getActionMetadata(ModuleClass.prototype);
903
+ const pages = getPageMetadata(ModuleClass.prototype);
832
904
  for (const [actionName, actionMetadata] of Object.entries(actions)) {
833
905
  const handler = new ActionHandler(
834
906
  moduleInstance,
@@ -842,6 +914,18 @@ var Microservice = class {
842
914
  `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
843
915
  );
844
916
  }
917
+ for (const [_, page] of Object.entries(pages)) {
918
+ const handler = new PageHandler(
919
+ moduleInstance,
920
+ page,
921
+ moduleName
922
+ );
923
+ this.pageHandlers.set(`${moduleName}.${page.name}`, handler);
924
+ this.app[page.method](`${this.options.prefix}${page.path}`, (ctx) => handler.handle(ctx));
925
+ logger_default.info(
926
+ `[ \u6CE8\u518C\u9875\u9762 ] ${moduleName}.${page.name} ${page.method.toUpperCase()} ${page.path} ${page.description}`
927
+ );
928
+ }
845
929
  const schedules = getScheduleMetadata(ModuleClass.prototype);
846
930
  if (schedules && Object.keys(schedules).length > 0) {
847
931
  if (!this.scheduler && this.etcdClient) {
@@ -871,11 +955,6 @@ var Microservice = class {
871
955
  initRoutes() {
872
956
  const startTime = Date.now();
873
957
  const prefix = this.options.prefix || "/api";
874
- this.app.get(prefix, (ctx) => {
875
- const name = this.options.name ?? "Microservice";
876
- const version = this.options.version ?? "1.0.0";
877
- return ctx.text(`${name} is running. version: ${version}`);
878
- });
879
958
  this.app.get(`${prefix}/health`, (ctx) => {
880
959
  return ctx.json({
881
960
  status: "ok",
@@ -890,7 +969,9 @@ var Microservice = class {
890
969
  version: this.options.version,
891
970
  env: this.options.env,
892
971
  modules: this.getModules(),
893
- stats: Object.fromEntries(this.statsMap)
972
+ stats: Object.fromEntries(this.statsMap),
973
+ activeRequests: this.getActiveRequestCount(),
974
+ status: this.status
894
975
  });
895
976
  });
896
977
  this.app.get(`${prefix}/client.ts`, async (ctx) => {
@@ -1087,7 +1168,12 @@ var Microservice = class {
1087
1168
  process.on("SIGINT", () => {
1088
1169
  logger_default.info(`
1089
1170
  Received SIGINT signal`);
1090
- this.gracefulShutdown();
1171
+ this.shutdown();
1172
+ });
1173
+ process.on("SIGTERM", () => {
1174
+ logger_default.info(`
1175
+ Received SIGTERM signal`);
1176
+ this.shutdown();
1091
1177
  });
1092
1178
  process.on("unhandledrejection", (event) => {
1093
1179
  logger_default.error("Unhandled rejection:", event.reason);
@@ -1099,14 +1185,16 @@ Received SIGINT signal`);
1099
1185
  /**
1100
1186
  * 优雅停机
1101
1187
  */
1102
- async gracefulShutdown() {
1103
- if (this.isShuttingDown) return;
1104
- this.isShuttingDown = true;
1105
- logger_default.info("\nGraceful shutdown initiated...");
1188
+ async shutdown() {
1189
+ if (this.status === "shutting_down") return;
1190
+ this.status = "shutting_down";
1191
+ logger_default.info("\nshutdown initiated...");
1106
1192
  if (this.statisticsTimer) clearInterval(this.statisticsTimer);
1107
1193
  try {
1194
+ await this.waitForActiveRequests();
1195
+ await this.executeCleanupHooks();
1108
1196
  await this.stop();
1109
- logger_default.info("Graceful shutdown completed");
1197
+ logger_default.info("shutdown completed");
1110
1198
  } catch (error) {
1111
1199
  logger_default.error("Error during shutdown:", error);
1112
1200
  process.exit(1);
@@ -1114,6 +1202,57 @@ Received SIGINT signal`);
1114
1202
  process.exit(0);
1115
1203
  }
1116
1204
  }
1205
+ /**
1206
+ * 等待所有活跃请求完成
1207
+ */
1208
+ async waitForActiveRequests() {
1209
+ const timeout = this.options.gracefulShutdown?.timeout || 3e4;
1210
+ const startTime = Date.now();
1211
+ logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
1212
+ return new Promise((resolve) => {
1213
+ const checkInterval = setInterval(() => {
1214
+ const elapsed = Date.now() - startTime;
1215
+ if (this.activeRequests.size === 0) {
1216
+ clearInterval(checkInterval);
1217
+ logger_default.info("All active requests completed");
1218
+ resolve();
1219
+ } else if (elapsed >= timeout) {
1220
+ clearInterval(checkInterval);
1221
+ logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
1222
+ resolve();
1223
+ } else {
1224
+ logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
1225
+ }
1226
+ }, 1e3);
1227
+ });
1228
+ }
1229
+ /**
1230
+ * 执行清理hook
1231
+ */
1232
+ async executeCleanupHooks() {
1233
+ const hooks = this.options.gracefulShutdown?.cleanupHooks || [];
1234
+ if (hooks.length === 0) {
1235
+ logger_default.info("No cleanup hooks configured");
1236
+ return;
1237
+ }
1238
+ logger_default.info(`Executing ${hooks.length} cleanup hooks...`);
1239
+ for (const hook of hooks) {
1240
+ try {
1241
+ const timeout = hook.timeout || 5e3;
1242
+ logger_default.info(`Executing cleanup hook: ${hook.name}`);
1243
+ await Promise.race([
1244
+ Promise.resolve(hook.cleanup()),
1245
+ new Promise(
1246
+ (_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
1247
+ )
1248
+ ]);
1249
+ logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
1250
+ } catch (error) {
1251
+ logger_default.error(`Cleanup hook ${hook.name} failed:`, error);
1252
+ }
1253
+ }
1254
+ logger_default.info("All cleanup hooks completed");
1255
+ }
1117
1256
  initStatsEventManager() {
1118
1257
  this.statisticsTimer = setInterval(async () => {
1119
1258
  await this.updateStats();
@@ -1155,14 +1294,54 @@ Received SIGINT signal`);
1155
1294
  }
1156
1295
  return handler;
1157
1296
  }
1297
+ /**
1298
+ * 添加活跃请求跟踪
1299
+ */
1300
+ addActiveRequest(requestId, requestInfo) {
1301
+ this.activeRequests.set(requestId, requestInfo);
1302
+ }
1303
+ /**
1304
+ * 移除活跃请求跟踪
1305
+ */
1306
+ removeActiveRequest(requestId) {
1307
+ this.activeRequests.delete(requestId);
1308
+ }
1309
+ /**
1310
+ * 获取当前活跃请求数量
1311
+ */
1312
+ getActiveRequestCount() {
1313
+ return this.activeRequests.size;
1314
+ }
1315
+ /**
1316
+ * 获取当前活跃请求信息
1317
+ */
1318
+ getActiveRequests() {
1319
+ return Array.from(this.activeRequests.values());
1320
+ }
1158
1321
  handleRequest = async (ctx) => {
1159
1322
  const { moduleName, actionName } = ctx.req.param();
1160
1323
  const handler = this.getActionHandler(moduleName, actionName);
1324
+ const requestId = crypto.randomUUID();
1325
+ const startTime = Date.now();
1326
+ if (this.status === "shutting_down") {
1327
+ return ctx.json({
1328
+ success: false,
1329
+ error: "Service is shutting down"
1330
+ }, 503);
1331
+ }
1161
1332
  try {
1162
1333
  const paramsText = await ctx.req.text();
1334
+ this.addActiveRequest(requestId, {
1335
+ id: requestId,
1336
+ moduleName,
1337
+ actionName,
1338
+ startTime,
1339
+ params: paramsText
1340
+ });
1163
1341
  const result = await handler.handle(paramsText);
1164
1342
  if (handler.metadata.stream) {
1165
1343
  const encoder = new TextEncoder();
1344
+ const microservice = this;
1166
1345
  const stream = new ReadableStream({
1167
1346
  async start(controller) {
1168
1347
  try {
@@ -1186,6 +1365,8 @@ Received SIGINT signal`);
1186
1365
  encoder.encode(ejson4.stringify(response) + "\n")
1187
1366
  );
1188
1367
  controller.close();
1368
+ } finally {
1369
+ microservice.removeActiveRequest(requestId);
1189
1370
  }
1190
1371
  }
1191
1372
  });
@@ -1197,8 +1378,10 @@ Received SIGINT signal`);
1197
1378
  }
1198
1379
  });
1199
1380
  }
1381
+ this.removeActiveRequest(requestId);
1200
1382
  return ctx.text(ejson4.stringify({ success: true, data: result }));
1201
1383
  } catch (error) {
1384
+ this.removeActiveRequest(requestId);
1202
1385
  return ctx.json({ success: false, error: error.message });
1203
1386
  }
1204
1387
  };
@@ -1214,28 +1397,6 @@ Received SIGINT signal`);
1214
1397
  await this.waitingInitialization;
1215
1398
  }
1216
1399
  };
1217
-
1218
- // utils/checker.ts
1219
- async function startCheck(checkers, pass) {
1220
- logger_default.info("[ \u9884\u68C0\u5F00\u59CB ]");
1221
- for (const [index, checker] of checkers.entries()) {
1222
- const seq = index + 1;
1223
- logger_default.info(`${seq}. ${checker.name}`);
1224
- try {
1225
- if (checker.skip) {
1226
- logger_default.warn(`${seq}. ${checker.name} [\u8DF3\u8FC7]`);
1227
- continue;
1228
- }
1229
- await checker.check();
1230
- logger_default.info(`${seq}. ${checker.name} [\u6210\u529F]`);
1231
- } catch (error) {
1232
- logger_default.error(`${seq}. ${checker.name} [\u5931\u8D25]`);
1233
- throw error;
1234
- }
1235
- }
1236
- logger_default.info("[ \u9884\u68C0\u5B8C\u6210 ]");
1237
- if (pass) await pass();
1238
- }
1239
1400
  var HonoTransport = class {
1240
1401
  constructor(url, stream, closeStream) {
1241
1402
  this.url = url;
@@ -1351,5 +1512,198 @@ var ModelContextProtocolPlugin = class extends Plugin {
1351
1512
  );
1352
1513
  };
1353
1514
  };
1515
+ var DEFAULT_FAVICON = /* @__PURE__ */ jsx(
1516
+ "link",
1517
+ {
1518
+ rel: "icon",
1519
+ href: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'><defs><linearGradient id='nodeGradient' x1='0%' y1='0%' x2='100%' y2='100%'><stop offset='0%' stop-color='%233498db'/><stop offset='100%' stop-color='%232980b9'/></linearGradient><linearGradient id='centerNodeGradient' x1='0%' y1='0%' x2='100%' y2='100%'><stop offset='0%' stop-color='%232ecc71'/><stop offset='100%' stop-color='%2327ae60'/></linearGradient></defs><circle cx='50' cy='50' r='45' fill='%23f5f7fa'/><path d='M30,30 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><path d='M70,30 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><path d='M30,70 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><path d='M70,70 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><polygon points='30,15 45,25 45,45 30,55 15,45 15,25' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='70,15 85,25 85,45 70,55 55,45 55,25' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='30,45 45,55 45,75 30,85 15,75 15,55' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='70,45 85,55 85,75 70,85 55,75 55,55' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='50,30 65,40 65,60 50,70 35,60 35,40' fill='url(%23centerNodeGradient)' stroke='%2327ae60' stroke-width='2'/><circle cx='30' cy='30' r='3' fill='%23ffffff'/><circle cx='70' cy='30' r='3' fill='%23ffffff'/><circle cx='30' cy='70' r='3' fill='%23ffffff'/><circle cx='70' cy='70' r='3' fill='%23ffffff'/><circle cx='50' cy='50' r='4' fill='%23ffffff'/></svg>",
1520
+ type: "image/svg+xml"
1521
+ }
1522
+ );
1523
+ var BaseLayout = (props = {
1524
+ title: "Microservice Template"
1525
+ }) => html`<!DOCTYPE html>
1526
+ <html>
1527
+ <head>
1528
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1529
+ <title>${props.title}</title>
1530
+ ${props.heads}
1531
+ </head>
1532
+ <body>
1533
+ ${props.children}
1534
+ </body>
1535
+ </html>`;
1536
+ var HtmxLayout = (props = {
1537
+ title: "Microservice Template"
1538
+ }) => BaseLayout({
1539
+ title: props.title,
1540
+ heads: html`
1541
+ <script src="https://unpkg.com/htmx.org@latest"></script>
1542
+ <script src="https://unpkg.com/hyperscript.org@latest"></script>
1543
+ <script src="https://cdn.tailwindcss.com"></script>
1544
+ ${props.favicon || DEFAULT_FAVICON}
1545
+ `,
1546
+ children: props.children
1547
+ });
1548
+ var InfoCard = ({
1549
+ icon,
1550
+ iconColor,
1551
+ bgColor,
1552
+ label,
1553
+ value
1554
+ }) => /* @__PURE__ */ jsxs("div", { className: `${bgColor} p-4 rounded-lg`, children: [
1555
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center mb-2", children: [
1556
+ /* @__PURE__ */ jsx(
1557
+ "svg",
1558
+ {
1559
+ className: `w-5 h-5 ${iconColor} mr-2`,
1560
+ fill: "none",
1561
+ stroke: "currentColor",
1562
+ viewBox: "0 0 24 24",
1563
+ children: /* @__PURE__ */ jsx(
1564
+ "path",
1565
+ {
1566
+ strokeLinecap: "round",
1567
+ strokeLinejoin: "round",
1568
+ strokeWidth: 2,
1569
+ d: icon
1570
+ }
1571
+ )
1572
+ }
1573
+ ),
1574
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-600", children: label })
1575
+ ] }),
1576
+ /* @__PURE__ */ jsx("p", { className: `text-xl font-semibold text-gray-900`, children: value })
1577
+ ] });
1578
+ var getEnvironmentBadgeClass = (env) => {
1579
+ switch (env) {
1580
+ case "prod":
1581
+ return "bg-red-100 text-red-800";
1582
+ case "stg":
1583
+ return "bg-yellow-100 text-yellow-800";
1584
+ case "dev":
1585
+ default:
1586
+ return "bg-blue-100 text-blue-800";
1587
+ }
1588
+ };
1589
+ var ServiceInfoCards = ({
1590
+ serviceInfo
1591
+ }) => {
1592
+ const infoCards = [
1593
+ {
1594
+ icon: "M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2m-9 0h10m-10 0a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V6a2 2 0 00-2-2",
1595
+ iconColor: "text-blue-600",
1596
+ bgColor: "bg-blue-50",
1597
+ label: "\u670D\u52A1\u540D\u79F0",
1598
+ value: serviceInfo.name
1599
+ },
1600
+ {
1601
+ icon: "M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1",
1602
+ iconColor: "text-orange-600",
1603
+ bgColor: "bg-orange-50",
1604
+ label: "\u670D\u52A1\u8DEF\u5F84",
1605
+ value: serviceInfo.prefix || "/",
1606
+ isMonospace: true
1607
+ },
1608
+ {
1609
+ icon: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
1610
+ iconColor: "text-green-600",
1611
+ bgColor: "bg-green-50",
1612
+ label: "\u8FD0\u884C\u73AF\u5883",
1613
+ value: /* @__PURE__ */ jsx(
1614
+ "span",
1615
+ {
1616
+ className: `px-2 py-1 rounded-full text-sm ${getEnvironmentBadgeClass(serviceInfo.env ?? "dev")}`,
1617
+ children: serviceInfo.env ?? "dev"
1618
+ }
1619
+ )
1620
+ },
1621
+ {
1622
+ icon: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z",
1623
+ iconColor: "text-purple-600",
1624
+ bgColor: "bg-purple-50",
1625
+ label: "\u7248\u672C\u53F7",
1626
+ value: serviceInfo.version || "unknown"
1627
+ }
1628
+ ];
1629
+ return /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-md p-6 mb-8", children: [
1630
+ /* @__PURE__ */ jsxs("h2", { className: "text-2xl font-semibold text-gray-800 mb-6 flex items-center", children: [
1631
+ /* @__PURE__ */ jsx(
1632
+ "svg",
1633
+ {
1634
+ className: "w-6 h-6 mr-2 text-blue-600",
1635
+ fill: "none",
1636
+ stroke: "currentColor",
1637
+ viewBox: "0 0 24 24",
1638
+ children: /* @__PURE__ */ jsx(
1639
+ "path",
1640
+ {
1641
+ strokeLinecap: "round",
1642
+ strokeLinejoin: "round",
1643
+ strokeWidth: 2,
1644
+ d: "M13 10V3L4 14h7v7l9-11h-7z"
1645
+ }
1646
+ )
1647
+ }
1648
+ ),
1649
+ "\u670D\u52A1\u57FA\u672C\u4FE1\u606F"
1650
+ ] }),
1651
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6", children: infoCards.map((card, index) => /* @__PURE__ */ jsx(InfoCard, { ...card }, index)) })
1652
+ ] });
1653
+ };
1654
+ var ServiceStatusPage = ({
1655
+ serviceInfo
1656
+ }) => {
1657
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 py-8", children: /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto px-4", children: [
1658
+ /* @__PURE__ */ jsx("div", { className: "mb-8", children: /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold text-gray-900 mb-2", children: "Service Status" }) }),
1659
+ /* @__PURE__ */ jsx(ServiceInfoCards, { serviceInfo })
1660
+ ] }) });
1661
+ };
1662
+ var ServiceStatusPage_default = ServiceStatusPage;
1663
+
1664
+ // core/plugins/page/mod.ts
1665
+ var PageRenderPlugin = class extends Plugin {
1666
+ initialize = async (engine) => {
1667
+ const app = engine.getApp();
1668
+ app.get(`${engine.options.prefix}`, async (ctx) => {
1669
+ return ctx.html(HtmxLayout({
1670
+ title: engine.options.name,
1671
+ children: ServiceStatusPage_default({
1672
+ serviceInfo: {
1673
+ name: engine.options.name,
1674
+ prefix: engine.options.prefix,
1675
+ version: engine.options.version,
1676
+ env: engine.options.env,
1677
+ id: engine.serviceId,
1678
+ modules: engine.getModules(false)
1679
+ }
1680
+ })
1681
+ }));
1682
+ });
1683
+ logger_default.info(`PageRenderPlugin enabled`);
1684
+ };
1685
+ };
1686
+
1687
+ // utils/checker.ts
1688
+ async function startCheck(checkers, pass) {
1689
+ logger_default.info("[ \u9884\u68C0\u5F00\u59CB ]");
1690
+ for (const [index, checker] of checkers.entries()) {
1691
+ const seq = index + 1;
1692
+ logger_default.info(`${seq}. ${checker.name}`);
1693
+ try {
1694
+ if (checker.skip) {
1695
+ logger_default.warn(`${seq}. ${checker.name} [\u8DF3\u8FC7]`);
1696
+ continue;
1697
+ }
1698
+ await checker.check();
1699
+ logger_default.info(`${seq}. ${checker.name} [\u6210\u529F]`);
1700
+ } catch (error) {
1701
+ logger_default.error(`${seq}. ${checker.name} [\u5931\u8D25]`);
1702
+ throw error;
1703
+ }
1704
+ }
1705
+ logger_default.info("[ \u9884\u68C0\u5B8C\u6210 ]");
1706
+ if (pass) await pass();
1707
+ }
1354
1708
 
1355
- export { Action, CacheAdapter, MemoryCacheAdapter, Microservice, ModelContextProtocolPlugin, Module, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, logger_default as logger, startCheck };
1709
+ export { Action, BaseLayout, CacheAdapter, HtmxLayout, MemoryCacheAdapter, Microservice, ModelContextProtocolPlugin, Module, Page, PageRenderPlugin, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, ServiceInfoCards, ServiceStatusPage, logger_default as logger, startCheck };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imean-service-engine",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "microservice engine",
5
5
  "keywords": [
6
6
  "microservice",