imean-service-engine 1.7.3 → 1.8.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.cjs CHANGED
@@ -14,10 +14,6 @@ var prettier = require('prettier');
14
14
  var crypto2 = require('crypto');
15
15
  var zlib = require('zlib');
16
16
  var nodeWs = require('@hono/node-ws');
17
- var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
18
- var types_js = require('@modelcontextprotocol/sdk/types.js');
19
- var streaming = require('hono/streaming');
20
- var ulid = require('ulid');
21
17
  var html = require('hono/html');
22
18
  var jsxRuntime = require('hono/jsx/jsx-runtime');
23
19
  var dayjs = require('dayjs');
@@ -92,8 +88,7 @@ function Action(options) {
92
88
  printError: options.printError ?? false,
93
89
  cache: options.cache,
94
90
  ttl: options.ttl,
95
- stream: options.stream,
96
- mcp: options.mcp
91
+ stream: options.stream
97
92
  };
98
93
  prototype[ACTION_METADATA] = existingMetadata;
99
94
  });
@@ -471,14 +466,14 @@ var ActionHandler = class {
471
466
  this.microservice = microservice;
472
467
  this.moduleName = moduleName;
473
468
  }
474
- async handle(req) {
469
+ async handle(req, ctx) {
475
470
  return await tracer2.startActiveSpan(
476
471
  `handle ${this.moduleName}.${this.actionName}`,
477
472
  async (span) => {
478
473
  span.setAttribute("module", this.moduleName);
479
474
  span.setAttribute("action", this.actionName);
480
475
  try {
481
- return await this._handle(req);
476
+ return await this._handle(req, ctx);
482
477
  } catch (error) {
483
478
  span.recordException(error);
484
479
  span.setStatus({
@@ -523,7 +518,7 @@ var ActionHandler = class {
523
518
  span.end();
524
519
  }
525
520
  }
526
- async _handle(req) {
521
+ async _handle(req, ctx) {
527
522
  const span = api.trace.getActiveSpan();
528
523
  const { args, requestHash } = await this._validate(req);
529
524
  const cacheHash = typeof this.metadata.cache === "function" ? this.metadata.cache(...args) : requestHash;
@@ -541,7 +536,7 @@ var ActionHandler = class {
541
536
  try {
542
537
  const result = await this.moduleInstance[this.actionName].apply(
543
538
  this.moduleInstance,
544
- args
539
+ args.concat(ctx)
545
540
  );
546
541
  if (this.metadata.stream) {
547
542
  span?.setAttribute("stream", true);
@@ -610,9 +605,10 @@ function Page(options) {
610
605
  existingMetadata[methodName] = {
611
606
  name: methodName,
612
607
  description: options.description || "",
613
- method: options.method,
608
+ method: options.method || "get",
614
609
  path: normalizePath(options.path),
615
- absolutePath: options.absolutePath ?? false
610
+ absolutePath: options.absolutePath ?? false,
611
+ middlewares: options.middlewares ?? []
616
612
  };
617
613
  prototype[PAGE_METADATA] = existingMetadata;
618
614
  });
@@ -667,8 +663,9 @@ var PageHandler = class {
667
663
  }
668
664
  };
669
665
  var WebSocketHandler = class {
670
- constructor(microservice, options) {
666
+ constructor(microservice, ctx, options) {
671
667
  this.microservice = microservice;
668
+ this.ctx = ctx;
672
669
  this.options = {
673
670
  timeout: options?.timeout || 3e4
674
671
  };
@@ -708,7 +705,7 @@ var WebSocketHandler = class {
708
705
  await this.sendMessage(ws, { type: "pong" });
709
706
  return;
710
707
  }
711
- const response = await this.handleRequest(ws, message);
708
+ const response = await this.handleRequest(ws, message, this.ctx);
712
709
  if (response) {
713
710
  await this.sendMessage(ws, response);
714
711
  }
@@ -735,7 +732,7 @@ var WebSocketHandler = class {
735
732
  onError(_error, ws) {
736
733
  this.onClose(ws);
737
734
  }
738
- async handleRequest(ws, message) {
735
+ async handleRequest(ws, message, ctx) {
739
736
  if (!message.id || !message.module || !message.action) {
740
737
  throw new Error("Invalid request message");
741
738
  }
@@ -771,7 +768,7 @@ var WebSocketHandler = class {
771
768
  message.action
772
769
  );
773
770
  const args = message.args ? Object.values(message.args) : [];
774
- const result = await handler.handle(args);
771
+ const result = await handler.handle(args, ctx);
775
772
  if (handler.metadata.stream) {
776
773
  try {
777
774
  for await (const value of result) {
@@ -932,20 +929,20 @@ var Microservice = class {
932
929
  );
933
930
  this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
934
931
  logger_default.info(
935
- `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
932
+ `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
936
933
  );
937
934
  }
938
935
  for (const [_, page] of Object.entries(pages)) {
939
- const handler = new PageHandler(
940
- moduleInstance,
941
- page,
942
- moduleName
943
- );
936
+ const handler = new PageHandler(moduleInstance, page, moduleName);
944
937
  this.pageHandlers.set(`${moduleName}.${page.name}`, handler);
945
938
  const paths = Array.isArray(page.path) ? page.path : [page.path];
946
939
  for (let path of paths) {
947
940
  path = page.absolutePath ? path : `${this.options.prefix}${path}`;
948
- this.app[page.method](path, (ctx) => handler.handle(ctx));
941
+ this.app[page.method](
942
+ path,
943
+ ...page.middlewares ?? [],
944
+ (ctx) => handler.handle(ctx)
945
+ );
949
946
  logger_default.info(
950
947
  `[ \u6CE8\u518C\u9875\u9762 ] ${moduleName}.${page.name} ${page.method.toUpperCase()} ${page.path} ${page.description}`
951
948
  );
@@ -1007,11 +1004,10 @@ var Microservice = class {
1007
1004
  });
1008
1005
  this.app.post(`${prefix}/:moduleName/:actionName`, this.handleRequest);
1009
1006
  if (this.options.websocket?.enabled) {
1010
- this.wsHandler = new WebSocketHandler(this);
1011
1007
  this.app.get(
1012
1008
  `${prefix}/ws`,
1013
- this.nodeWebSocket.upgradeWebSocket((_ctx) => {
1014
- const wsHandler = new WebSocketHandler(this, {
1009
+ this.nodeWebSocket.upgradeWebSocket((ctx) => {
1010
+ const wsHandler = new WebSocketHandler(this, ctx, {
1015
1011
  timeout: this.options.websocket?.timeout
1016
1012
  });
1017
1013
  return {
@@ -1233,7 +1229,9 @@ Received SIGTERM signal`);
1233
1229
  async waitForActiveRequests() {
1234
1230
  const timeout = this.options.gracefulShutdown?.timeout || 3e4;
1235
1231
  const startTime = Date.now();
1236
- logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
1232
+ logger_default.info(
1233
+ `Waiting for ${this.activeRequests.size} active requests to complete...`
1234
+ );
1237
1235
  return new Promise((resolve) => {
1238
1236
  const checkInterval = setInterval(() => {
1239
1237
  const elapsed = Date.now() - startTime;
@@ -1243,10 +1241,14 @@ Received SIGTERM signal`);
1243
1241
  resolve();
1244
1242
  } else if (elapsed >= timeout) {
1245
1243
  clearInterval(checkInterval);
1246
- logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
1244
+ logger_default.warn(
1245
+ `Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`
1246
+ );
1247
1247
  resolve();
1248
1248
  } else {
1249
- logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
1249
+ logger_default.info(
1250
+ `Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`
1251
+ );
1250
1252
  }
1251
1253
  }, 1e3);
1252
1254
  });
@@ -1268,7 +1270,10 @@ Received SIGTERM signal`);
1268
1270
  await Promise.race([
1269
1271
  Promise.resolve(hook.cleanup()),
1270
1272
  new Promise(
1271
- (_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
1273
+ (_, reject) => setTimeout(
1274
+ () => reject(new Error(`Cleanup hook ${hook.name} timeout`)),
1275
+ timeout
1276
+ )
1272
1277
  )
1273
1278
  ]);
1274
1279
  logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
@@ -1349,10 +1354,13 @@ Received SIGTERM signal`);
1349
1354
  const requestId = crypto.randomUUID();
1350
1355
  const startTime = Date.now();
1351
1356
  if (this.status === "shutting_down") {
1352
- return ctx.json({
1353
- success: false,
1354
- error: "Service is shutting down"
1355
- }, 503);
1357
+ return ctx.json(
1358
+ {
1359
+ success: false,
1360
+ error: "Service is shutting down"
1361
+ },
1362
+ 503
1363
+ );
1356
1364
  }
1357
1365
  try {
1358
1366
  const paramsText = await ctx.req.text();
@@ -1363,7 +1371,7 @@ Received SIGTERM signal`);
1363
1371
  startTime,
1364
1372
  params: paramsText
1365
1373
  });
1366
- const result = await handler.handle(paramsText);
1374
+ const result = await handler.handle(paramsText, ctx);
1367
1375
  if (handler.metadata.stream) {
1368
1376
  const encoder = new TextEncoder();
1369
1377
  const microservice = this;
@@ -1422,121 +1430,6 @@ Received SIGTERM signal`);
1422
1430
  await this.waitingInitialization;
1423
1431
  }
1424
1432
  };
1425
- var HonoTransport = class {
1426
- constructor(url, stream, closeStream) {
1427
- this.url = url;
1428
- this.stream = stream;
1429
- this.closeStream = closeStream;
1430
- this.sessionId = ulid.ulid();
1431
- }
1432
- sessionId;
1433
- onclose;
1434
- onerror;
1435
- onmessage;
1436
- async start() {
1437
- await this.stream.writeSSE({
1438
- event: "endpoint",
1439
- data: `${encodeURI(this.url)}?sessionId=${this.sessionId}`
1440
- });
1441
- this.stream.onAbort(() => {
1442
- this.close();
1443
- });
1444
- }
1445
- async handleMessage(message) {
1446
- let parsedMessage;
1447
- try {
1448
- parsedMessage = types_js.JSONRPCMessageSchema.parse(message);
1449
- } catch (error) {
1450
- this.onerror?.(error);
1451
- throw error;
1452
- }
1453
- this.onmessage?.(parsedMessage);
1454
- }
1455
- async send(message) {
1456
- await this.stream.writeln(
1457
- `event: message
1458
- data: ${JSON.stringify(message)}
1459
-
1460
- `
1461
- );
1462
- }
1463
- async close() {
1464
- this.onclose?.();
1465
- this.closeStream();
1466
- }
1467
- };
1468
- var ModelContextProtocolPlugin = class extends Plugin {
1469
- mcpServer;
1470
- transports = {};
1471
- registerMcpTools(engine) {
1472
- const modules = engine.getModules(true);
1473
- for (const module of Object.values(modules)) {
1474
- for (const action of Object.values(module.actions)) {
1475
- if (action.mcp?.type !== "tool") {
1476
- continue;
1477
- }
1478
- const args = {};
1479
- const argsIndex = {};
1480
- for (const [index, param] of (action.params ?? []).entries()) {
1481
- args[param.description] = param;
1482
- argsIndex[param.description] = index;
1483
- }
1484
- this.mcpServer.tool(
1485
- `${module.name}.${action.name}`,
1486
- action.description ?? "",
1487
- args,
1488
- async (params) => {
1489
- const argsList = [];
1490
- for (const [key, value] of Object.entries(params)) {
1491
- argsList[argsIndex[key]] = value;
1492
- }
1493
- const result = await engine.getActionHandler(module.name, action.name).handle(argsList);
1494
- return {
1495
- content: [{ type: "text", text: JSON.stringify(result) }]
1496
- };
1497
- }
1498
- );
1499
- }
1500
- }
1501
- }
1502
- initialize = async (engine) => {
1503
- const app = engine.getApp();
1504
- this.mcpServer = new mcp_js.McpServer({
1505
- name: engine.options.name,
1506
- version: engine.options.version
1507
- });
1508
- this.registerMcpTools(engine);
1509
- app.get(`${engine.options.prefix}/mcp_sse`, async (ctx) => {
1510
- return streaming.streamSSE(ctx, async (stream) => {
1511
- return new Promise(async (resolve) => {
1512
- const transport = new HonoTransport(
1513
- `${engine.options.prefix}/mcp_messages`,
1514
- stream,
1515
- () => {
1516
- delete this.transports[transport.sessionId];
1517
- resolve();
1518
- }
1519
- );
1520
- this.transports[transport.sessionId] = transport;
1521
- await this.mcpServer.connect(transport);
1522
- });
1523
- });
1524
- });
1525
- app.post(`${engine.options.prefix}/mcp_messages`, async (ctx) => {
1526
- const sessionId = ctx.req.query("sessionId");
1527
- if (!sessionId) {
1528
- return ctx.text("No transport found for sessionId", 400);
1529
- }
1530
- const transport = this.transports[sessionId];
1531
- const message = await ctx.req.json();
1532
- await transport.handleMessage(message);
1533
- return ctx.text("Accepted", 202);
1534
- });
1535
- logger_default.info(
1536
- `ModelContextProtocolPlugin endpoint: ${engine.options.prefix}/mcp_sse`
1537
- );
1538
- };
1539
- };
1540
1433
  var DEFAULT_FAVICON = /* @__PURE__ */ jsxRuntime.jsx(
1541
1434
  "link",
1542
1435
  {
@@ -1749,7 +1642,6 @@ exports.CacheAdapter = CacheAdapter;
1749
1642
  exports.HtmxLayout = HtmxLayout;
1750
1643
  exports.MemoryCacheAdapter = MemoryCacheAdapter;
1751
1644
  exports.Microservice = Microservice;
1752
- exports.ModelContextProtocolPlugin = ModelContextProtocolPlugin;
1753
1645
  exports.Module = Module;
1754
1646
  exports.Page = Page;
1755
1647
  exports.PageRenderPlugin = PageRenderPlugin;
package/dist/mod.d.cts CHANGED
@@ -3,7 +3,7 @@ export * from 'zod';
3
3
  import { Redis, Cluster } from 'ioredis';
4
4
  import { LRUCache } from 'lru-cache';
5
5
  import { Lease, Etcd3 } from 'etcd3';
6
- import { Hono } from 'hono';
6
+ import { MiddlewareHandler, Context, Hono } from 'hono';
7
7
  export { default as dayjs } from 'dayjs';
8
8
  import winston from 'winston';
9
9
  import * as hono_utils_html from 'hono/utils/html';
@@ -27,9 +27,6 @@ declare class MemoryCacheAdapter extends CacheAdapter {
27
27
  }
28
28
 
29
29
  type CacheFn = (...args: any[]) => any;
30
- type McpOptions = {
31
- type: "tool";
32
- };
33
30
  interface ActionOptions {
34
31
  params?: z.ZodType<any>[];
35
32
  returns?: z.ZodType<any>;
@@ -39,16 +36,17 @@ interface ActionOptions {
39
36
  cache?: boolean | CacheFn;
40
37
  ttl?: number;
41
38
  stream?: boolean;
42
- mcp?: McpOptions;
43
39
  }
44
40
  interface PageOptions {
45
- method: "get" | "post" | "put" | "delete" | "patch" | "options";
41
+ method?: "get" | "post" | "put" | "delete" | "patch" | "options";
42
+ middlewares?: MiddlewareHandler[];
46
43
  path: string | string[];
47
44
  absolutePath?: boolean;
48
45
  description?: string;
49
46
  }
50
47
  interface PageMetadata extends PageOptions {
51
48
  name: string;
49
+ method: "get" | "post" | "put" | "delete" | "patch" | "options";
52
50
  }
53
51
  interface ActionMetadata extends ActionOptions {
54
52
  name: string;
@@ -213,7 +211,7 @@ declare class ActionHandler {
213
211
  private microservice;
214
212
  private moduleName;
215
213
  constructor(moduleInstance: any, actionName: string, metadata: ActionMetadata, microservice: Microservice, moduleName: string);
216
- handle(req: string | any[]): Promise<any>;
214
+ handle(req: string | any[], ctx: Context): Promise<any>;
217
215
  private _validate;
218
216
  private _handle;
219
217
  }
@@ -316,13 +314,6 @@ declare class Microservice {
316
314
  init(): Promise<void>;
317
315
  }
318
316
 
319
- declare class ModelContextProtocolPlugin extends Plugin {
320
- private mcpServer;
321
- private transports;
322
- private registerMcpTools;
323
- initialize: (engine: Microservice) => Promise<void>;
324
- }
325
-
326
317
  declare const BaseLayout: (props?: {
327
318
  children?: any;
328
319
  title?: string;
@@ -381,4 +372,4 @@ declare function Schedule(options: ScheduleOptions): Function;
381
372
 
382
373
  declare const logger: winston.Logger;
383
374
 
384
- export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
375
+ export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
package/dist/mod.d.ts CHANGED
@@ -3,7 +3,7 @@ export * from 'zod';
3
3
  import { Redis, Cluster } from 'ioredis';
4
4
  import { LRUCache } from 'lru-cache';
5
5
  import { Lease, Etcd3 } from 'etcd3';
6
- import { Hono } from 'hono';
6
+ import { MiddlewareHandler, Context, Hono } from 'hono';
7
7
  export { default as dayjs } from 'dayjs';
8
8
  import winston from 'winston';
9
9
  import * as hono_utils_html from 'hono/utils/html';
@@ -27,9 +27,6 @@ declare class MemoryCacheAdapter extends CacheAdapter {
27
27
  }
28
28
 
29
29
  type CacheFn = (...args: any[]) => any;
30
- type McpOptions = {
31
- type: "tool";
32
- };
33
30
  interface ActionOptions {
34
31
  params?: z.ZodType<any>[];
35
32
  returns?: z.ZodType<any>;
@@ -39,16 +36,17 @@ interface ActionOptions {
39
36
  cache?: boolean | CacheFn;
40
37
  ttl?: number;
41
38
  stream?: boolean;
42
- mcp?: McpOptions;
43
39
  }
44
40
  interface PageOptions {
45
- method: "get" | "post" | "put" | "delete" | "patch" | "options";
41
+ method?: "get" | "post" | "put" | "delete" | "patch" | "options";
42
+ middlewares?: MiddlewareHandler[];
46
43
  path: string | string[];
47
44
  absolutePath?: boolean;
48
45
  description?: string;
49
46
  }
50
47
  interface PageMetadata extends PageOptions {
51
48
  name: string;
49
+ method: "get" | "post" | "put" | "delete" | "patch" | "options";
52
50
  }
53
51
  interface ActionMetadata extends ActionOptions {
54
52
  name: string;
@@ -213,7 +211,7 @@ declare class ActionHandler {
213
211
  private microservice;
214
212
  private moduleName;
215
213
  constructor(moduleInstance: any, actionName: string, metadata: ActionMetadata, microservice: Microservice, moduleName: string);
216
- handle(req: string | any[]): Promise<any>;
214
+ handle(req: string | any[], ctx: Context): Promise<any>;
217
215
  private _validate;
218
216
  private _handle;
219
217
  }
@@ -316,13 +314,6 @@ declare class Microservice {
316
314
  init(): Promise<void>;
317
315
  }
318
316
 
319
- declare class ModelContextProtocolPlugin extends Plugin {
320
- private mcpServer;
321
- private transports;
322
- private registerMcpTools;
323
- initialize: (engine: Microservice) => Promise<void>;
324
- }
325
-
326
317
  declare const BaseLayout: (props?: {
327
318
  children?: any;
328
319
  title?: string;
@@ -381,4 +372,4 @@ declare function Schedule(options: ScheduleOptions): Function;
381
372
 
382
373
  declare const logger: winston.Logger;
383
374
 
384
- export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
375
+ export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
package/dist/mod.js CHANGED
@@ -14,10 +14,6 @@ import crypto2 from 'node:crypto';
14
14
  import { brotliDecompress } from 'node:zlib';
15
15
  import { brotliCompress, constants } from 'zlib';
16
16
  import { createNodeWebSocket } from '@hono/node-ws';
17
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
- import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
19
- import { streamSSE } from 'hono/streaming';
20
- import { ulid } from 'ulid';
21
17
  import { html } from 'hono/html';
22
18
  import { jsx, jsxs } from 'hono/jsx/jsx-runtime';
23
19
  export { default as dayjs } from 'dayjs';
@@ -83,8 +79,7 @@ function Action(options) {
83
79
  printError: options.printError ?? false,
84
80
  cache: options.cache,
85
81
  ttl: options.ttl,
86
- stream: options.stream,
87
- mcp: options.mcp
82
+ stream: options.stream
88
83
  };
89
84
  prototype[ACTION_METADATA] = existingMetadata;
90
85
  });
@@ -462,14 +457,14 @@ var ActionHandler = class {
462
457
  this.microservice = microservice;
463
458
  this.moduleName = moduleName;
464
459
  }
465
- async handle(req) {
460
+ async handle(req, ctx) {
466
461
  return await tracer2.startActiveSpan(
467
462
  `handle ${this.moduleName}.${this.actionName}`,
468
463
  async (span) => {
469
464
  span.setAttribute("module", this.moduleName);
470
465
  span.setAttribute("action", this.actionName);
471
466
  try {
472
- return await this._handle(req);
467
+ return await this._handle(req, ctx);
473
468
  } catch (error) {
474
469
  span.recordException(error);
475
470
  span.setStatus({
@@ -514,7 +509,7 @@ var ActionHandler = class {
514
509
  span.end();
515
510
  }
516
511
  }
517
- async _handle(req) {
512
+ async _handle(req, ctx) {
518
513
  const span = trace.getActiveSpan();
519
514
  const { args, requestHash } = await this._validate(req);
520
515
  const cacheHash = typeof this.metadata.cache === "function" ? this.metadata.cache(...args) : requestHash;
@@ -532,7 +527,7 @@ var ActionHandler = class {
532
527
  try {
533
528
  const result = await this.moduleInstance[this.actionName].apply(
534
529
  this.moduleInstance,
535
- args
530
+ args.concat(ctx)
536
531
  );
537
532
  if (this.metadata.stream) {
538
533
  span?.setAttribute("stream", true);
@@ -601,9 +596,10 @@ function Page(options) {
601
596
  existingMetadata[methodName] = {
602
597
  name: methodName,
603
598
  description: options.description || "",
604
- method: options.method,
599
+ method: options.method || "get",
605
600
  path: normalizePath(options.path),
606
- absolutePath: options.absolutePath ?? false
601
+ absolutePath: options.absolutePath ?? false,
602
+ middlewares: options.middlewares ?? []
607
603
  };
608
604
  prototype[PAGE_METADATA] = existingMetadata;
609
605
  });
@@ -658,8 +654,9 @@ var PageHandler = class {
658
654
  }
659
655
  };
660
656
  var WebSocketHandler = class {
661
- constructor(microservice, options) {
657
+ constructor(microservice, ctx, options) {
662
658
  this.microservice = microservice;
659
+ this.ctx = ctx;
663
660
  this.options = {
664
661
  timeout: options?.timeout || 3e4
665
662
  };
@@ -699,7 +696,7 @@ var WebSocketHandler = class {
699
696
  await this.sendMessage(ws, { type: "pong" });
700
697
  return;
701
698
  }
702
- const response = await this.handleRequest(ws, message);
699
+ const response = await this.handleRequest(ws, message, this.ctx);
703
700
  if (response) {
704
701
  await this.sendMessage(ws, response);
705
702
  }
@@ -726,7 +723,7 @@ var WebSocketHandler = class {
726
723
  onError(_error, ws) {
727
724
  this.onClose(ws);
728
725
  }
729
- async handleRequest(ws, message) {
726
+ async handleRequest(ws, message, ctx) {
730
727
  if (!message.id || !message.module || !message.action) {
731
728
  throw new Error("Invalid request message");
732
729
  }
@@ -762,7 +759,7 @@ var WebSocketHandler = class {
762
759
  message.action
763
760
  );
764
761
  const args = message.args ? Object.values(message.args) : [];
765
- const result = await handler.handle(args);
762
+ const result = await handler.handle(args, ctx);
766
763
  if (handler.metadata.stream) {
767
764
  try {
768
765
  for await (const value of result) {
@@ -923,20 +920,20 @@ var Microservice = class {
923
920
  );
924
921
  this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
925
922
  logger_default.info(
926
- `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
923
+ `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
927
924
  );
928
925
  }
929
926
  for (const [_, page] of Object.entries(pages)) {
930
- const handler = new PageHandler(
931
- moduleInstance,
932
- page,
933
- moduleName
934
- );
927
+ const handler = new PageHandler(moduleInstance, page, moduleName);
935
928
  this.pageHandlers.set(`${moduleName}.${page.name}`, handler);
936
929
  const paths = Array.isArray(page.path) ? page.path : [page.path];
937
930
  for (let path of paths) {
938
931
  path = page.absolutePath ? path : `${this.options.prefix}${path}`;
939
- this.app[page.method](path, (ctx) => handler.handle(ctx));
932
+ this.app[page.method](
933
+ path,
934
+ ...page.middlewares ?? [],
935
+ (ctx) => handler.handle(ctx)
936
+ );
940
937
  logger_default.info(
941
938
  `[ \u6CE8\u518C\u9875\u9762 ] ${moduleName}.${page.name} ${page.method.toUpperCase()} ${page.path} ${page.description}`
942
939
  );
@@ -998,11 +995,10 @@ var Microservice = class {
998
995
  });
999
996
  this.app.post(`${prefix}/:moduleName/:actionName`, this.handleRequest);
1000
997
  if (this.options.websocket?.enabled) {
1001
- this.wsHandler = new WebSocketHandler(this);
1002
998
  this.app.get(
1003
999
  `${prefix}/ws`,
1004
- this.nodeWebSocket.upgradeWebSocket((_ctx) => {
1005
- const wsHandler = new WebSocketHandler(this, {
1000
+ this.nodeWebSocket.upgradeWebSocket((ctx) => {
1001
+ const wsHandler = new WebSocketHandler(this, ctx, {
1006
1002
  timeout: this.options.websocket?.timeout
1007
1003
  });
1008
1004
  return {
@@ -1224,7 +1220,9 @@ Received SIGTERM signal`);
1224
1220
  async waitForActiveRequests() {
1225
1221
  const timeout = this.options.gracefulShutdown?.timeout || 3e4;
1226
1222
  const startTime = Date.now();
1227
- logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
1223
+ logger_default.info(
1224
+ `Waiting for ${this.activeRequests.size} active requests to complete...`
1225
+ );
1228
1226
  return new Promise((resolve) => {
1229
1227
  const checkInterval = setInterval(() => {
1230
1228
  const elapsed = Date.now() - startTime;
@@ -1234,10 +1232,14 @@ Received SIGTERM signal`);
1234
1232
  resolve();
1235
1233
  } else if (elapsed >= timeout) {
1236
1234
  clearInterval(checkInterval);
1237
- logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
1235
+ logger_default.warn(
1236
+ `Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`
1237
+ );
1238
1238
  resolve();
1239
1239
  } else {
1240
- logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
1240
+ logger_default.info(
1241
+ `Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`
1242
+ );
1241
1243
  }
1242
1244
  }, 1e3);
1243
1245
  });
@@ -1259,7 +1261,10 @@ Received SIGTERM signal`);
1259
1261
  await Promise.race([
1260
1262
  Promise.resolve(hook.cleanup()),
1261
1263
  new Promise(
1262
- (_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
1264
+ (_, reject) => setTimeout(
1265
+ () => reject(new Error(`Cleanup hook ${hook.name} timeout`)),
1266
+ timeout
1267
+ )
1263
1268
  )
1264
1269
  ]);
1265
1270
  logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
@@ -1340,10 +1345,13 @@ Received SIGTERM signal`);
1340
1345
  const requestId = crypto.randomUUID();
1341
1346
  const startTime = Date.now();
1342
1347
  if (this.status === "shutting_down") {
1343
- return ctx.json({
1344
- success: false,
1345
- error: "Service is shutting down"
1346
- }, 503);
1348
+ return ctx.json(
1349
+ {
1350
+ success: false,
1351
+ error: "Service is shutting down"
1352
+ },
1353
+ 503
1354
+ );
1347
1355
  }
1348
1356
  try {
1349
1357
  const paramsText = await ctx.req.text();
@@ -1354,7 +1362,7 @@ Received SIGTERM signal`);
1354
1362
  startTime,
1355
1363
  params: paramsText
1356
1364
  });
1357
- const result = await handler.handle(paramsText);
1365
+ const result = await handler.handle(paramsText, ctx);
1358
1366
  if (handler.metadata.stream) {
1359
1367
  const encoder = new TextEncoder();
1360
1368
  const microservice = this;
@@ -1413,121 +1421,6 @@ Received SIGTERM signal`);
1413
1421
  await this.waitingInitialization;
1414
1422
  }
1415
1423
  };
1416
- var HonoTransport = class {
1417
- constructor(url, stream, closeStream) {
1418
- this.url = url;
1419
- this.stream = stream;
1420
- this.closeStream = closeStream;
1421
- this.sessionId = ulid();
1422
- }
1423
- sessionId;
1424
- onclose;
1425
- onerror;
1426
- onmessage;
1427
- async start() {
1428
- await this.stream.writeSSE({
1429
- event: "endpoint",
1430
- data: `${encodeURI(this.url)}?sessionId=${this.sessionId}`
1431
- });
1432
- this.stream.onAbort(() => {
1433
- this.close();
1434
- });
1435
- }
1436
- async handleMessage(message) {
1437
- let parsedMessage;
1438
- try {
1439
- parsedMessage = JSONRPCMessageSchema.parse(message);
1440
- } catch (error) {
1441
- this.onerror?.(error);
1442
- throw error;
1443
- }
1444
- this.onmessage?.(parsedMessage);
1445
- }
1446
- async send(message) {
1447
- await this.stream.writeln(
1448
- `event: message
1449
- data: ${JSON.stringify(message)}
1450
-
1451
- `
1452
- );
1453
- }
1454
- async close() {
1455
- this.onclose?.();
1456
- this.closeStream();
1457
- }
1458
- };
1459
- var ModelContextProtocolPlugin = class extends Plugin {
1460
- mcpServer;
1461
- transports = {};
1462
- registerMcpTools(engine) {
1463
- const modules = engine.getModules(true);
1464
- for (const module of Object.values(modules)) {
1465
- for (const action of Object.values(module.actions)) {
1466
- if (action.mcp?.type !== "tool") {
1467
- continue;
1468
- }
1469
- const args = {};
1470
- const argsIndex = {};
1471
- for (const [index, param] of (action.params ?? []).entries()) {
1472
- args[param.description] = param;
1473
- argsIndex[param.description] = index;
1474
- }
1475
- this.mcpServer.tool(
1476
- `${module.name}.${action.name}`,
1477
- action.description ?? "",
1478
- args,
1479
- async (params) => {
1480
- const argsList = [];
1481
- for (const [key, value] of Object.entries(params)) {
1482
- argsList[argsIndex[key]] = value;
1483
- }
1484
- const result = await engine.getActionHandler(module.name, action.name).handle(argsList);
1485
- return {
1486
- content: [{ type: "text", text: JSON.stringify(result) }]
1487
- };
1488
- }
1489
- );
1490
- }
1491
- }
1492
- }
1493
- initialize = async (engine) => {
1494
- const app = engine.getApp();
1495
- this.mcpServer = new McpServer({
1496
- name: engine.options.name,
1497
- version: engine.options.version
1498
- });
1499
- this.registerMcpTools(engine);
1500
- app.get(`${engine.options.prefix}/mcp_sse`, async (ctx) => {
1501
- return streamSSE(ctx, async (stream) => {
1502
- return new Promise(async (resolve) => {
1503
- const transport = new HonoTransport(
1504
- `${engine.options.prefix}/mcp_messages`,
1505
- stream,
1506
- () => {
1507
- delete this.transports[transport.sessionId];
1508
- resolve();
1509
- }
1510
- );
1511
- this.transports[transport.sessionId] = transport;
1512
- await this.mcpServer.connect(transport);
1513
- });
1514
- });
1515
- });
1516
- app.post(`${engine.options.prefix}/mcp_messages`, async (ctx) => {
1517
- const sessionId = ctx.req.query("sessionId");
1518
- if (!sessionId) {
1519
- return ctx.text("No transport found for sessionId", 400);
1520
- }
1521
- const transport = this.transports[sessionId];
1522
- const message = await ctx.req.json();
1523
- await transport.handleMessage(message);
1524
- return ctx.text("Accepted", 202);
1525
- });
1526
- logger_default.info(
1527
- `ModelContextProtocolPlugin endpoint: ${engine.options.prefix}/mcp_sse`
1528
- );
1529
- };
1530
- };
1531
1424
  var DEFAULT_FAVICON = /* @__PURE__ */ jsx(
1532
1425
  "link",
1533
1426
  {
@@ -1730,4 +1623,4 @@ async function startCheck(checkers, pass) {
1730
1623
  if (pass) await pass();
1731
1624
  }
1732
1625
 
1733
- export { Action, BaseLayout, CacheAdapter, HtmxLayout, MemoryCacheAdapter, Microservice, ModelContextProtocolPlugin, Module, Page, PageRenderPlugin, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, ServiceInfoCards, ServiceStatusPage, logger_default as logger, startCheck };
1626
+ export { Action, BaseLayout, CacheAdapter, HtmxLayout, MemoryCacheAdapter, Microservice, Module, Page, PageRenderPlugin, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, ServiceInfoCards, ServiceStatusPage, logger_default as logger, startCheck };
package/package.json CHANGED
@@ -1,87 +1,86 @@
1
- {
2
- "name": "imean-service-engine",
3
- "version": "1.7.3",
4
- "description": "microservice engine",
5
- "keywords": [
6
- "microservice",
7
- "websocket",
8
- "http",
9
- "node"
10
- ],
11
- "author": "imean",
12
- "type": "module",
13
- "license": "MIT",
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://git.imean.tech/imean/imean-microservice-framework.git"
17
- },
18
- "main": "dist/mod.js",
19
- "module": "dist/mod.js",
20
- "types": "dist/mod.d.ts",
21
- "exports": {
22
- ".": {
23
- "types": "./dist/mod.d.ts",
24
- "import": "./dist/mod.js",
25
- "require": "./dist/mod.cjs"
26
- }
27
- },
28
- "files": [
29
- "dist",
30
- "README.md",
31
- "LICENSE"
32
- ],
33
- "scripts": {
34
- "dev": "tsx watch dev/index.ts",
35
- "build": "tsup",
36
- "test": "vitest run",
37
- "prepublishOnly": "npm run build && npm run test"
38
- },
39
- "dependencies": {
40
- "@hono/node-server": "^1.13.7",
41
- "@hono/node-ws": "^1.0.6",
42
- "@modelcontextprotocol/sdk": "^1.8.0",
43
- "dayjs": "^1.11.13",
44
- "ejson": "^2.2.3",
45
- "etcd3": "^1.1.2",
46
- "fs-extra": "^11.3.0",
47
- "hono": "^4.6.17",
48
- "lru-cache": "^11.0.2",
49
- "prettier": "^3.4.2",
50
- "ulid": "^3.0.0",
51
- "winston": "^3.17.0",
52
- "zod": "^3.24.1"
53
- },
54
- "peerDependencies": {
55
- "@opentelemetry/api": "^1.x",
56
- "ioredis": "^5.6.0"
57
- },
58
- "devDependencies": {
59
- "@opentelemetry/auto-instrumentations-node": "^0.55.3",
60
- "@opentelemetry/exporter-logs-otlp-proto": "^0.57.1",
61
- "@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
62
- "@opentelemetry/exporter-trace-otlp-proto": "^0.57.1",
63
- "@opentelemetry/instrumentation-winston": "^0.44.0",
64
- "@opentelemetry/sdk-logs": "^0.57.1",
65
- "@opentelemetry/sdk-metrics": "^1.30.1",
66
- "@opentelemetry/sdk-node": "^0.57.1",
67
- "@opentelemetry/sdk-trace-node": "^1.30.1",
68
- "@opentelemetry/winston-transport": "^0.10.0",
69
- "@types/ejson": "^2.2.2",
70
- "@types/fs-extra": "^11.0.4",
71
- "@types/ioredis-mock": "^8.2.5",
72
- "@types/node": "^20.0.0",
73
- "@vitest/coverage-v8": "^3.0.4",
74
- "imean-service-client": "^1.5.0",
75
- "ioredis-mock": "^8.9.0",
76
- "opentelemetry-instrumentation-fetch-node": "^1.2.3",
77
- "tslib": "^2.8.1",
78
- "tsup": "^8.0.1",
79
- "tsx": "^4.19.2",
80
- "typescript": "^5.3.3",
81
- "vite-tsconfig-paths": "^5.1.4",
82
- "vitest": "^3.0.3"
83
- },
84
- "engines": {
85
- "node": ">=20"
86
- }
87
- }
1
+ {
2
+ "name": "imean-service-engine",
3
+ "version": "1.8.0",
4
+ "description": "microservice engine",
5
+ "keywords": [
6
+ "microservice",
7
+ "websocket",
8
+ "http",
9
+ "node"
10
+ ],
11
+ "author": "imean",
12
+ "type": "module",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://git.imean.tech/imean/imean-microservice-framework.git"
17
+ },
18
+ "main": "dist/mod.js",
19
+ "module": "dist/mod.js",
20
+ "types": "dist/mod.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/mod.d.ts",
24
+ "import": "./dist/mod.js",
25
+ "require": "./dist/mod.cjs"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "dev": "tsx watch dev/index.ts",
35
+ "build": "tsup",
36
+ "test": "vitest run",
37
+ "prepublishOnly": "npm run build && npm run test"
38
+ },
39
+ "dependencies": {
40
+ "@hono/node-server": "^1.13.7",
41
+ "@hono/node-ws": "^1.0.6",
42
+ "dayjs": "^1.11.13",
43
+ "ejson": "^2.2.3",
44
+ "etcd3": "^1.1.2",
45
+ "fs-extra": "^11.3.0",
46
+ "hono": "^4.6.17",
47
+ "lru-cache": "^11.0.2",
48
+ "prettier": "^3.4.2",
49
+ "ulid": "^3.0.0",
50
+ "winston": "^3.17.0",
51
+ "zod": "^3.24.1"
52
+ },
53
+ "peerDependencies": {
54
+ "@opentelemetry/api": "^1.x",
55
+ "ioredis": "^5.6.0"
56
+ },
57
+ "devDependencies": {
58
+ "@opentelemetry/auto-instrumentations-node": "^0.55.3",
59
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.57.1",
60
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
61
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.57.1",
62
+ "@opentelemetry/instrumentation-winston": "^0.44.0",
63
+ "@opentelemetry/sdk-logs": "^0.57.1",
64
+ "@opentelemetry/sdk-metrics": "^1.30.1",
65
+ "@opentelemetry/sdk-node": "^0.57.1",
66
+ "@opentelemetry/sdk-trace-node": "^1.30.1",
67
+ "@opentelemetry/winston-transport": "^0.10.0",
68
+ "@types/ejson": "^2.2.2",
69
+ "@types/fs-extra": "^11.0.4",
70
+ "@types/ioredis-mock": "^8.2.5",
71
+ "@types/node": "^20.0.0",
72
+ "@vitest/coverage-v8": "^3.0.4",
73
+ "imean-service-client": "^1.5.0",
74
+ "ioredis-mock": "^8.9.0",
75
+ "opentelemetry-instrumentation-fetch-node": "^1.2.3",
76
+ "tslib": "^2.8.1",
77
+ "tsup": "^8.0.1",
78
+ "tsx": "^4.19.2",
79
+ "typescript": "^5.3.3",
80
+ "vite-tsconfig-paths": "^5.1.4",
81
+ "vitest": "^3.0.3"
82
+ },
83
+ "engines": {
84
+ "node": ">=20"
85
+ }
86
+ }