imean-service-engine 2.0.0 → 2.0.2

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 (72) hide show
  1. package/dist/index.d.mts +77 -52
  2. package/dist/index.d.ts +77 -52
  3. package/dist/index.js +2078 -1945
  4. package/dist/index.mjs +2076 -1944
  5. package/package.json +9 -2
  6. package/.vscode/settings.json +0 -8
  7. package/src/core/checker.ts +0 -33
  8. package/src/core/decorators.test.ts +0 -96
  9. package/src/core/decorators.ts +0 -68
  10. package/src/core/engine.test.ts +0 -218
  11. package/src/core/engine.ts +0 -635
  12. package/src/core/errors.ts +0 -28
  13. package/src/core/factory.test.ts +0 -73
  14. package/src/core/factory.ts +0 -92
  15. package/src/core/logger.ts +0 -65
  16. package/src/core/testing.ts +0 -73
  17. package/src/core/types.ts +0 -191
  18. package/src/index.ts +0 -49
  19. package/src/metadata/README.md +0 -422
  20. package/src/metadata/metadata.test.ts +0 -369
  21. package/src/metadata/metadata.ts +0 -512
  22. package/src/plugins/action/action-plugin.test.ts +0 -660
  23. package/src/plugins/action/decorator.ts +0 -14
  24. package/src/plugins/action/index.ts +0 -4
  25. package/src/plugins/action/plugin.ts +0 -349
  26. package/src/plugins/action/types.ts +0 -49
  27. package/src/plugins/action/utils.test.ts +0 -196
  28. package/src/plugins/action/utils.ts +0 -111
  29. package/src/plugins/cache/adapter.test.ts +0 -689
  30. package/src/plugins/cache/adapter.ts +0 -324
  31. package/src/plugins/cache/cache-plugin.test.ts +0 -269
  32. package/src/plugins/cache/decorator.ts +0 -26
  33. package/src/plugins/cache/index.ts +0 -20
  34. package/src/plugins/cache/plugin.ts +0 -299
  35. package/src/plugins/cache/types.ts +0 -69
  36. package/src/plugins/client-code/client-code-plugin.test.ts +0 -511
  37. package/src/plugins/client-code/format.ts +0 -9
  38. package/src/plugins/client-code/generator.test.ts +0 -52
  39. package/src/plugins/client-code/generator.ts +0 -263
  40. package/src/plugins/client-code/index.ts +0 -15
  41. package/src/plugins/client-code/plugin.ts +0 -158
  42. package/src/plugins/client-code/types.ts +0 -52
  43. package/src/plugins/client-code/utils.ts +0 -164
  44. package/src/plugins/graceful-shutdown/graceful-shutdown-plugin.test.ts +0 -401
  45. package/src/plugins/graceful-shutdown/index.ts +0 -3
  46. package/src/plugins/graceful-shutdown/plugin.ts +0 -279
  47. package/src/plugins/graceful-shutdown/types.ts +0 -17
  48. package/src/plugins/rate-limit/rate-limit-plugin.example.ts +0 -171
  49. package/src/plugins/route/components/Layout.tsx +0 -42
  50. package/src/plugins/route/components/ServiceStatusPage.tsx +0 -141
  51. package/src/plugins/route/decorator.ts +0 -50
  52. package/src/plugins/route/index.ts +0 -16
  53. package/src/plugins/route/plugin.ts +0 -218
  54. package/src/plugins/route/route-plugin.test.ts +0 -759
  55. package/src/plugins/route/types.ts +0 -72
  56. package/src/plugins/schedule/README.md +0 -309
  57. package/src/plugins/schedule/decorator.ts +0 -25
  58. package/src/plugins/schedule/index.ts +0 -12
  59. package/src/plugins/schedule/mock-etcd.ts +0 -145
  60. package/src/plugins/schedule/plugin.ts +0 -164
  61. package/src/plugins/schedule/schedule-plugin.test.ts +0 -312
  62. package/src/plugins/schedule/scheduler.ts +0 -164
  63. package/src/plugins/schedule/types.ts +0 -94
  64. package/src/plugins/schedule/utils.test.ts +0 -163
  65. package/src/plugins/schedule/utils.ts +0 -41
  66. package/tests/integration/client.test.ts +0 -203
  67. package/tests/integration/dev-service.ts +0 -301
  68. package/tests/integration/generated/client.ts +0 -123
  69. package/tests/integration/start-service.ts +0 -21
  70. package/tsconfig.json +0 -27
  71. package/tsup.config.ts +0 -16
  72. package/vitest.config.ts +0 -19
package/dist/index.mjs CHANGED
@@ -1,10 +1,6 @@
1
- import { serve } from '@hono/node-server';
2
- import { Hono } from 'hono';
3
- export { Context, MiddlewareHandler } from 'hono';
4
- import * as net from 'net';
5
1
  import winston, { format } from 'winston';
6
2
  import { html } from 'hono/html';
7
- import { jsx } from 'hono/jsx/jsx-runtime';
3
+ import { jsx, jsxs } from 'hono/jsx/jsx-runtime';
8
4
  import * as ejson from 'ejson';
9
5
  import { z } from 'zod';
10
6
  export { z } from 'zod';
@@ -12,6 +8,10 @@ import { createHash } from 'crypto';
12
8
  import { promises } from 'fs';
13
9
  import { dirname } from 'path';
14
10
  import prettier from 'prettier';
11
+ import { serve } from '@hono/node-server';
12
+ import { Hono } from 'hono';
13
+ export { Context, MiddlewareHandler } from 'hono';
14
+ import * as net from 'net';
15
15
 
16
16
  var __create = Object.create;
17
17
  var __defProp = Object.defineProperty;
@@ -1643,6 +1643,17 @@ var require_src = __commonJS({
1643
1643
  }
1644
1644
  });
1645
1645
 
1646
+ // src/core/types.ts
1647
+ var PluginPriority = /* @__PURE__ */ ((PluginPriority2) => {
1648
+ PluginPriority2[PluginPriority2["SYSTEM"] = 50] = "SYSTEM";
1649
+ PluginPriority2[PluginPriority2["SECURITY"] = 100] = "SECURITY";
1650
+ PluginPriority2[PluginPriority2["LOGGING"] = 200] = "LOGGING";
1651
+ PluginPriority2[PluginPriority2["BUSINESS"] = 300] = "BUSINESS";
1652
+ PluginPriority2[PluginPriority2["PERFORMANCE"] = 400] = "PERFORMANCE";
1653
+ PluginPriority2[PluginPriority2["ROUTE"] = 1e3] = "ROUTE";
1654
+ return PluginPriority2;
1655
+ })(PluginPriority || {});
1656
+
1646
1657
  // src/metadata/metadata.ts
1647
1658
  var DECORATED_KEYS_KEY = /* @__PURE__ */ Symbol.for("imean:decoratedKeys");
1648
1659
  var keyToClassesMap = /* @__PURE__ */ new Map();
@@ -1857,2234 +1868,2355 @@ var logger = winston.createLogger({
1857
1868
  });
1858
1869
  var logger_default = logger;
1859
1870
 
1860
- // src/core/engine.ts
1861
- var Microservice = class {
1862
- // 存储已注册的模块
1863
- modules = [];
1864
- // 存储已注册的插件(按注册顺序)
1865
- plugins = [];
1866
- // 插件注册表(以name为键,用于覆盖逻辑)
1867
- pluginRegistry = /* @__PURE__ */ new Map();
1868
- // 模块实例缓存(单例管理)
1869
- moduleInstances = /* @__PURE__ */ new Map();
1870
- // 引擎配置(冻结,只读)
1871
- options;
1872
- // 是否已启动
1873
- started = false;
1874
- // 模块元数据键(用于通过双向访问查找模块)
1875
- moduleMetadataKey;
1876
- // 实际使用的端口(启动后设置)
1877
- actualPort = null;
1878
- // Hono 实例(由引擎统一管理)
1879
- hono = new Hono();
1880
- // HTTP 服务器实例
1881
- server = null;
1882
- constructor(options, moduleMetadataKey) {
1883
- this.options = Object.freeze({
1884
- hostname: "0.0.0.0",
1885
- ...options
1886
- });
1887
- this.moduleMetadataKey = moduleMetadataKey;
1871
+ // src/core/checker.ts
1872
+ async function startCheck(checkers, pass) {
1873
+ logger_default.info("[ \u9884\u68C0\u5F00\u59CB ]");
1874
+ for (const [index, checker] of checkers.entries()) {
1875
+ const seq = index + 1;
1876
+ logger_default.info(`${seq}. ${checker.name}`);
1877
+ try {
1878
+ if (checker.skip) {
1879
+ logger_default.warn(`${seq}. ${checker.name} [\u8DF3\u8FC7]`);
1880
+ continue;
1881
+ }
1882
+ await checker.check();
1883
+ logger_default.info(`${seq}. ${checker.name} [\u6210\u529F]`);
1884
+ } catch (error) {
1885
+ logger_default.error(`${seq}. ${checker.name} [\u5931\u8D25]`);
1886
+ throw error;
1887
+ }
1888
+ }
1889
+ logger_default.info("[ \u9884\u68C0\u5B8C\u6210 ]");
1890
+ if (pass) await pass();
1891
+ }
1892
+ var DEFAULT_FAVICON = /* @__PURE__ */ jsx(
1893
+ "link",
1894
+ {
1895
+ rel: "icon",
1896
+ 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>",
1897
+ type: "image/svg+xml"
1898
+ }
1899
+ );
1900
+ var BaseLayout = (props = {
1901
+ title: "Microservice Template"
1902
+ }) => html`<!DOCTYPE html>
1903
+ <html>
1904
+ <head>
1905
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1906
+ <title>${props.title}</title>
1907
+ ${props.heads}
1908
+ </head>
1909
+ <body>
1910
+ ${props.children}
1911
+ </body>
1912
+ </html>`;
1913
+ var HtmxLayout = (props = {
1914
+ title: "Microservice Template"
1915
+ }) => BaseLayout({
1916
+ title: props.title,
1917
+ heads: html`
1918
+ <script src="https://unpkg.com/htmx.org@latest"></script>
1919
+ <script src="https://unpkg.com/hyperscript.org@latest"></script>
1920
+ <script src="https://cdn.tailwindcss.com"></script>
1921
+ ${props.favicon || DEFAULT_FAVICON}
1922
+ `,
1923
+ children: props.children
1924
+ });
1925
+ var InfoCard = ({
1926
+ icon,
1927
+ iconColor,
1928
+ bgColor,
1929
+ label,
1930
+ value
1931
+ }) => /* @__PURE__ */ jsxs("div", { className: `${bgColor} p-4 rounded-lg`, children: [
1932
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center mb-2", children: [
1933
+ /* @__PURE__ */ jsx(
1934
+ "svg",
1935
+ {
1936
+ className: `w-5 h-5 ${iconColor} mr-2`,
1937
+ fill: "none",
1938
+ stroke: "currentColor",
1939
+ viewBox: "0 0 24 24",
1940
+ children: /* @__PURE__ */ jsx(
1941
+ "path",
1942
+ {
1943
+ strokeLinecap: "round",
1944
+ strokeLinejoin: "round",
1945
+ strokeWidth: 2,
1946
+ d: icon
1947
+ }
1948
+ )
1949
+ }
1950
+ ),
1951
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-600", children: label })
1952
+ ] }),
1953
+ /* @__PURE__ */ jsx("p", { className: `text-xl font-semibold text-gray-900`, children: value })
1954
+ ] });
1955
+ var getEnvironmentBadgeClass = (env) => {
1956
+ const normalizedEnv = env.toLowerCase();
1957
+ switch (normalizedEnv) {
1958
+ case "production":
1959
+ case "prod":
1960
+ return "bg-red-100 text-red-800";
1961
+ case "staging":
1962
+ case "stg":
1963
+ return "bg-yellow-100 text-yellow-800";
1964
+ case "development":
1965
+ case "dev":
1966
+ default:
1967
+ return "bg-blue-100 text-blue-800";
1888
1968
  }
1969
+ };
1970
+ var getStatusBadgeClass = (status) => {
1971
+ switch (status.toLowerCase()) {
1972
+ case "running":
1973
+ return "bg-green-100 text-green-800";
1974
+ case "stopped":
1975
+ return "bg-gray-100 text-gray-800";
1976
+ case "error":
1977
+ return "bg-red-100 text-red-800";
1978
+ default:
1979
+ return "bg-blue-100 text-blue-800";
1980
+ }
1981
+ };
1982
+ var ServiceInfoCards = ({
1983
+ serviceInfo
1984
+ }) => {
1985
+ const env = serviceInfo.env || typeof process !== "undefined" && process.env?.NODE_ENV || "dev";
1986
+ const status = serviceInfo.status || "running";
1987
+ const infoCards = [
1988
+ {
1989
+ 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",
1990
+ iconColor: "text-blue-600",
1991
+ bgColor: "bg-blue-50",
1992
+ label: "\u670D\u52A1\u540D\u79F0",
1993
+ value: serviceInfo.name
1994
+ },
1995
+ {
1996
+ 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",
1997
+ iconColor: "text-orange-600",
1998
+ bgColor: "bg-orange-50",
1999
+ label: "\u670D\u52A1\u8DEF\u5F84",
2000
+ value: serviceInfo.prefix || "/"
2001
+ },
2002
+ {
2003
+ icon: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
2004
+ iconColor: "text-green-600",
2005
+ bgColor: "bg-green-50",
2006
+ label: "\u8FD0\u884C\u73AF\u5883",
2007
+ value: /* @__PURE__ */ jsx(
2008
+ "span",
2009
+ {
2010
+ className: `px-2 py-1 rounded-full text-sm ${getEnvironmentBadgeClass(env)}`,
2011
+ children: env
2012
+ }
2013
+ )
2014
+ },
2015
+ {
2016
+ 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",
2017
+ iconColor: "text-purple-600",
2018
+ bgColor: "bg-purple-50",
2019
+ label: "\u7248\u672C\u53F7",
2020
+ value: serviceInfo.version || "unknown"
2021
+ },
2022
+ ...serviceInfo.port ? [
2023
+ {
2024
+ icon: "M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01",
2025
+ iconColor: "text-indigo-600",
2026
+ bgColor: "bg-indigo-50",
2027
+ label: "\u7AEF\u53E3",
2028
+ value: serviceInfo.port
2029
+ }
2030
+ ] : [],
2031
+ {
2032
+ icon: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
2033
+ iconColor: "text-emerald-600",
2034
+ bgColor: "bg-emerald-50",
2035
+ label: "\u8FD0\u884C\u72B6\u6001",
2036
+ value: /* @__PURE__ */ jsx(
2037
+ "span",
2038
+ {
2039
+ className: `px-2 py-1 rounded-full text-sm ${getStatusBadgeClass(status)}`,
2040
+ children: status
2041
+ }
2042
+ )
2043
+ }
2044
+ ];
2045
+ return /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-md p-6 mb-8", children: [
2046
+ /* @__PURE__ */ jsxs("h2", { className: "text-2xl font-semibold text-gray-800 mb-6 flex items-center", children: [
2047
+ /* @__PURE__ */ jsx(
2048
+ "svg",
2049
+ {
2050
+ className: "w-6 h-6 mr-2 text-blue-600",
2051
+ fill: "none",
2052
+ stroke: "currentColor",
2053
+ viewBox: "0 0 24 24",
2054
+ children: /* @__PURE__ */ jsx(
2055
+ "path",
2056
+ {
2057
+ strokeLinecap: "round",
2058
+ strokeLinejoin: "round",
2059
+ strokeWidth: 2,
2060
+ d: "M13 10V3L4 14h7v7l9-11h-7z"
2061
+ }
2062
+ )
2063
+ }
2064
+ ),
2065
+ "\u670D\u52A1\u57FA\u672C\u4FE1\u606F"
2066
+ ] }),
2067
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: infoCards.map((card, index) => /* @__PURE__ */ jsx(InfoCard, { ...card }, index)) })
2068
+ ] });
2069
+ };
2070
+ var ServiceStatusPage = ({
2071
+ serviceInfo
2072
+ }) => {
2073
+ 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: [
2074
+ /* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
2075
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold text-gray-900 mb-2", children: "Service Status" }),
2076
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600", children: "\u67E5\u770B\u670D\u52A1\u8FD0\u884C\u72B6\u6001\u548C\u57FA\u672C\u4FE1\u606F" })
2077
+ ] }),
2078
+ /* @__PURE__ */ jsx(ServiceInfoCards, { serviceInfo })
2079
+ ] }) });
2080
+ };
2081
+
2082
+ // src/plugins/route/decorator.ts
2083
+ function Route(options) {
2084
+ return Handler({
2085
+ type: "route",
2086
+ options
2087
+ });
2088
+ }
2089
+ function Page(options) {
2090
+ const pageOptions = {
2091
+ method: options.method || "GET",
2092
+ ...options
2093
+ };
2094
+ return Route(pageOptions);
2095
+ }
2096
+
2097
+ // src/plugins/route/plugin.ts
2098
+ var RoutePlugin = class {
2099
+ name = "route-plugin";
2100
+ priority = 1e3 /* ROUTE */;
2101
+ engine;
2102
+ globalPrefix;
2103
+ globalMiddlewares;
1889
2104
  /**
1890
- * 获取 Hono 实例(供插件使用)
2105
+ * 构造函数
2106
+ * @param options 插件配置选项
1891
2107
  */
1892
- getHono() {
1893
- return this.hono;
2108
+ constructor(options) {
2109
+ this.globalPrefix = options?.prefix || "";
2110
+ this.globalMiddlewares = options?.globalMiddlewares || [];
1894
2111
  }
1895
2112
  /**
1896
- * 内部注册插件方法(供构造函数和子类使用)
1897
- * @protected
2113
+ * 声明Module配置Schema(用于类型推导+运行时校验)
1898
2114
  */
1899
- registerPlugin(plugin) {
1900
- if (!plugin.name || plugin.name.trim() === "") {
1901
- throw new PluginNameRequiredError();
1902
- }
1903
- const existingPlugin = this.pluginRegistry.get(plugin.name);
1904
- if (existingPlugin) {
1905
- const oldIndex = this.plugins.indexOf(existingPlugin);
1906
- if (oldIndex >= 0) {
1907
- this.plugins.splice(oldIndex, 1);
2115
+ getModuleOptionsSchema() {
2116
+ return {
2117
+ _type: {},
2118
+ validate: (options) => {
2119
+ if (options.routePrefix !== void 0 && typeof options.routePrefix !== "string") {
2120
+ return "routePrefix must be a string";
2121
+ }
2122
+ if (options.routePrefix && !options.routePrefix.startsWith("/")) {
2123
+ return `routePrefix must start with '/'`;
2124
+ }
2125
+ if (options.routeMiddlewares !== void 0 && !Array.isArray(options.routeMiddlewares)) {
2126
+ return "routeMiddlewares must be an array";
2127
+ }
2128
+ return true;
1908
2129
  }
1909
- logger_default.info(`Override plugin: ${plugin.name}`);
1910
- }
1911
- this.pluginRegistry.set(plugin.name, plugin);
1912
- this.plugins.push(plugin);
2130
+ };
1913
2131
  }
1914
2132
  /**
1915
- * 加载并注册所有模块(通过唯一的 key 查找)
1916
- * 在引擎启动时调用,使用双向访问机制查找所有被装饰的类
1917
- *
1918
- * 注意:
1919
- * - 每个引擎实例使用唯一的 moduleMetadataKey,实现隔离
1920
- * - 模块类是静态的,可以被多个引擎实例共享
1921
- * - 每个引擎实例会创建独立的模块实例,互不影响
2133
+ * 引擎初始化钩子:获取Hono实例
1922
2134
  */
1923
- loadModules() {
1924
- this.modules = [];
1925
- const moduleClasses = getClassesByKey(this.moduleMetadataKey);
1926
- for (const moduleClass of moduleClasses) {
1927
- const metadata = getClassMetadata(moduleClass, this.moduleMetadataKey);
1928
- const moduleName = metadata.name || moduleClass.name;
1929
- const existingModule = this.modules.find((m) => m.name === moduleName);
1930
- if (existingModule) {
1931
- throw new DuplicateModuleError(moduleName);
1932
- }
1933
- const moduleMetadata = {
1934
- name: moduleName,
1935
- clazz: moduleClass,
1936
- options: metadata.options || {}
1937
- };
1938
- this.modules.push(moduleMetadata);
1939
- }
2135
+ onInit(engine) {
2136
+ this.engine = engine;
2137
+ logger_default.info("RoutePlugin initialized");
1940
2138
  }
1941
2139
  /**
1942
- * 加载Handler元数据(平铺结构)
1943
- * 每个装饰器都是独立的HandlerMetadata条目
1944
- * 为每个方法创建包装链管理器,提供简单的 wrap API
2140
+ * Handler加载钩子:解析type="route"的Handler元数据,注册HTTP路由
1945
2141
  */
1946
- loadHandlerMetadata() {
1947
- const handlers = [];
1948
- const wrapperChains = /* @__PURE__ */ new Map();
1949
- const originalMethods = /* @__PURE__ */ new Map();
1950
- for (const module of this.modules) {
1951
- if (!this.moduleInstances.has(module.clazz)) {
1952
- this.get(module.clazz);
2142
+ onHandlerLoad(handlers) {
2143
+ const routeHandlers = handlers.filter(
2144
+ (handler) => handler.type === "route"
2145
+ );
2146
+ logger_default.info(`Found ${routeHandlers.length} route handler(s)`);
2147
+ for (const handler of routeHandlers) {
2148
+ const routeOptions = handler.options;
2149
+ const methodName = handler.methodName;
2150
+ const moduleClass = handler.module;
2151
+ const moduleInstance = this.engine.get(moduleClass);
2152
+ if (!moduleInstance) {
2153
+ logger_default.warn(
2154
+ `Module instance not found for ${moduleClass.name}, skipping route registration`
2155
+ );
2156
+ continue;
1953
2157
  }
1954
- const allMetadata = getAllHandlerMetadata(module.clazz);
1955
- for (const [methodName, metadataList] of allMetadata.entries()) {
1956
- const method = module.clazz.prototype[methodName];
1957
- const methodNameStr = String(methodName);
1958
- const methodKey = `${module.clazz.name}.${methodNameStr}`;
1959
- if (!originalMethods.has(methodKey)) {
1960
- originalMethods.set(methodKey, method);
1961
- wrapperChains.set(methodKey, []);
1962
- }
1963
- for (const meta of metadataList) {
1964
- const chain = wrapperChains.get(methodKey);
1965
- handlers.push({
1966
- ...meta,
1967
- method: originalMethods.get(methodKey),
1968
- methodName: methodNameStr,
1969
- module: module.clazz,
1970
- // 提供简单的 wrap API:插件只需要调用这个方法
1971
- wrap: (wrapper) => {
1972
- chain.push(wrapper);
1973
- }
1974
- });
2158
+ const moduleMetadata = this.engine.getModules().find((m) => m.clazz === moduleClass);
2159
+ const moduleOptions = moduleMetadata?.options || {};
2160
+ const routePrefix = moduleOptions.routePrefix || "";
2161
+ const paths = Array.isArray(routeOptions.path) ? routeOptions.path : [routeOptions.path];
2162
+ const fullPaths = paths.map((p) => this.globalPrefix + routePrefix + p);
2163
+ const routeHandler = async (ctx) => {
2164
+ const method = moduleInstance[methodName];
2165
+ if (typeof method !== "function") {
2166
+ return ctx.json({ error: "Handler method not found" }, 500);
2167
+ }
2168
+ try {
2169
+ const result = await method.call(moduleInstance, ctx);
2170
+ if (result instanceof Response) {
2171
+ return result;
2172
+ }
2173
+ if (typeof result === "string" || typeof result === "object" && result !== null && "type" in result) {
2174
+ return result;
2175
+ }
2176
+ return ctx.json(result);
2177
+ } catch (error) {
2178
+ logger_default.error(
2179
+ `Error in route handler ${moduleClass.name}.${methodName}`,
2180
+ error
2181
+ );
2182
+ return ctx.json(
2183
+ {
2184
+ error: "Internal server error",
2185
+ message: error instanceof Error ? error.message : String(error)
2186
+ },
2187
+ 500
2188
+ );
2189
+ }
2190
+ };
2191
+ const hono = this.engine.getHono();
2192
+ const methods = routeOptions.method ? Array.isArray(routeOptions.method) ? routeOptions.method : [routeOptions.method] : ["GET"];
2193
+ for (const fullPath of fullPaths) {
2194
+ let route = hono;
2195
+ if (this.globalMiddlewares.length > 0) {
2196
+ route = route.use(fullPath, ...this.globalMiddlewares);
2197
+ }
2198
+ if (moduleOptions.routeMiddlewares && moduleOptions.routeMiddlewares.length > 0) {
2199
+ route = route.use(fullPath, ...moduleOptions.routeMiddlewares);
2200
+ }
2201
+ if (routeOptions.middlewares && routeOptions.middlewares.length > 0) {
2202
+ route = route.use(fullPath, ...routeOptions.middlewares);
2203
+ }
2204
+ for (const method of methods) {
2205
+ const methodLower = method.toLowerCase();
2206
+ if (methodLower === "get" || methodLower === "post" || methodLower === "put" || methodLower === "delete" || methodLower === "patch" || methodLower === "head" || methodLower === "options") {
2207
+ route[methodLower](fullPath, routeHandler);
2208
+ const description = routeOptions.description ? ` (${routeOptions.description})` : "";
2209
+ logger_default.info(
2210
+ `Registered route: ${method.toUpperCase()} ${fullPath} -> ${moduleClass.name}.${methodName}${description}`
2211
+ );
2212
+ } else {
2213
+ logger_default.warn(
2214
+ `Unsupported HTTP method: ${method}, skipping route ${fullPath}`
2215
+ );
2216
+ }
1975
2217
  }
1976
2218
  }
1977
2219
  }
1978
- this.__wrapperChains__ = wrapperChains;
1979
- this.__originalMethods__ = originalMethods;
1980
- return handlers;
1981
2220
  }
1982
- /**
1983
- * 应用所有包装链到原型
1984
- * 在所有插件执行完 onHandlerLoad 后调用
1985
- */
1986
- applyWrapperChains() {
1987
- const wrapperChains = this.__wrapperChains__;
1988
- const originalMethods = this.__originalMethods__;
1989
- if (!wrapperChains || !originalMethods) return;
1990
- for (const [methodKey, chain] of wrapperChains.entries()) {
1991
- if (chain.length === 0) continue;
1992
- const [moduleName, methodName] = methodKey.split(".");
1993
- const module = this.modules.find((m) => m.clazz.name === moduleName);
1994
- if (!module) continue;
1995
- const originalMethod = originalMethods.get(methodKey);
1996
- const prototype = module.clazz.prototype;
1997
- let wrappedMethod = originalMethod;
1998
- for (let i = chain.length - 1; i >= 0; i--) {
1999
- const wrapper = chain[i];
2000
- const next = wrappedMethod;
2001
- wrappedMethod = async function(...args) {
2002
- return wrapper(() => next.apply(this, args), this, ...args);
2003
- };
2004
- }
2005
- prototype[methodName] = wrappedMethod;
2006
- }
2221
+ };
2222
+ function buildParamsSchema(schemas) {
2223
+ const shape = {};
2224
+ for (let i = 0; i < schemas.length; i++) {
2225
+ shape[String(i)] = schemas[i];
2007
2226
  }
2008
- /**
2009
- * 检查端口是否可用
2010
- */
2011
- async isPortAvailable(port, hostname) {
2012
- return new Promise((resolve) => {
2013
- const server = net.createServer();
2014
- server.unref();
2015
- server.listen(port, hostname, () => {
2016
- server.once("close", () => resolve(true));
2017
- server.close();
2018
- });
2019
- server.on("error", () => resolve(false));
2020
- });
2227
+ return z.object(shape);
2228
+ }
2229
+ function parseAndValidateParams(body, schemas) {
2230
+ if (!body || typeof body !== "object") {
2231
+ body = {};
2021
2232
  }
2022
- /**
2023
- * 寻找一个随机的可用端口
2024
- */
2025
- getRandomPort(hostname) {
2026
- return new Promise((resolve, reject) => {
2027
- const server = net.createServer();
2028
- server.unref();
2029
- server.on("error", reject);
2030
- server.listen(0, hostname, () => {
2031
- const port = server.address().port;
2032
- server.close((err) => {
2033
- if (err) {
2034
- return reject(err);
2035
- }
2036
- resolve(port);
2037
- });
2038
- });
2039
- });
2233
+ if (schemas.length === 0) {
2234
+ return { success: true, data: [] };
2040
2235
  }
2041
- /**
2042
- * 启动引擎
2043
- * @param requestedPort 启动端口(可选,默认0,表示随机端口)
2044
- * @returns 实际使用的端口号
2045
- */
2046
- async start(requestedPort = 0) {
2047
- if (this.started) {
2048
- throw new Error("Engine is already started");
2049
- }
2050
- try {
2051
- const { port, hostname } = await this.determinePort(requestedPort);
2052
- this.initializeModulesAndPlugins();
2053
- this.processHandlers();
2054
- await this.executePluginStartHooks();
2055
- this.startHttpServer(port, hostname);
2056
- this.started = true;
2057
- logger_default.info(
2058
- `${this.options.name} v${this.options.version} started successfully on port ${this.actualPort}`
2059
- );
2060
- return this.actualPort;
2061
- } catch (error) {
2062
- logger_default.error("Failed to start engine", error);
2063
- throw error;
2064
- }
2236
+ const paramsSchema = buildParamsSchema(schemas);
2237
+ const validation = paramsSchema.safeParse(body);
2238
+ if (!validation.success) {
2239
+ return {
2240
+ success: false,
2241
+ error: validation.error
2242
+ };
2243
+ }
2244
+ const validatedData = [];
2245
+ for (let i = 0; i < schemas.length; i++) {
2246
+ validatedData[i] = validation.data[String(i)];
2065
2247
  }
2248
+ return {
2249
+ success: true,
2250
+ data: validatedData
2251
+ };
2252
+ }
2253
+ function buildActionPath(enginePrefix, moduleName, handlerName) {
2254
+ const normalizedPrefix = enginePrefix.replace(/\/+$/, "");
2255
+ return `/${normalizedPrefix}/${moduleName}/${handlerName}`.replace(/\/+/g, "/").replace(/^\/\//, "/");
2256
+ }
2257
+
2258
+ // src/plugins/action/plugin.ts
2259
+ function isAsyncIterable(obj) {
2260
+ return obj != null && typeof obj[Symbol.asyncIterator] === "function";
2261
+ }
2262
+ var ActionPlugin = class {
2263
+ name = "action-plugin";
2264
+ priority = 1e3 /* ROUTE */;
2265
+ // 路由插件优先级最低,必须最后执行
2266
+ engine;
2066
2267
  /**
2067
- * 确定实际使用的端口
2268
+ * 声明Module配置Schema(用于类型推导+运行时校验)
2068
2269
  */
2069
- async determinePort(requestedPort) {
2070
- const hostname = this.options.hostname || "0.0.0.0";
2071
- if (requestedPort !== 0) {
2072
- const portAvailable = await this.isPortAvailable(requestedPort, hostname);
2073
- if (portAvailable) {
2074
- this.actualPort = requestedPort;
2075
- return { port: this.actualPort, hostname };
2270
+ getModuleOptionsSchema() {
2271
+ return {
2272
+ _type: {},
2273
+ validate: (options) => {
2274
+ if (options.actionMiddlewares !== void 0 && !Array.isArray(options.actionMiddlewares)) {
2275
+ return "actionMiddlewares must be an array";
2276
+ }
2277
+ return true;
2076
2278
  }
2077
- logger_default.info(
2078
- `Port ${requestedPort} is in use, finding a random available port...`
2079
- );
2080
- }
2081
- this.actualPort = await this.getRandomPort(hostname);
2082
- return { port: this.actualPort, hostname };
2279
+ };
2083
2280
  }
2084
2281
  /**
2085
- * 初始化模块和插件
2282
+ * 引擎初始化钩子:获取Hono实例
2086
2283
  */
2087
- initializeModulesAndPlugins() {
2088
- this.loadModules();
2089
- this.executePluginHook("onInit", (plugin) => {
2090
- plugin.onInit?.(this);
2091
- });
2092
- this.executePluginHook("onModuleLoad", (plugin) => {
2093
- plugin.onModuleLoad?.(
2094
- this.modules
2095
- );
2096
- });
2284
+ onInit(engine) {
2285
+ this.engine = engine;
2286
+ logger_default.info("ActionPlugin initialized");
2097
2287
  }
2098
2288
  /**
2099
- * 处理 Handler 元数据和插件包装
2289
+ * Handler加载钩子:解析type="action"的Handler元数据,注册HTTP路由
2100
2290
  */
2101
- processHandlers() {
2102
- const handlers = this.loadHandlerMetadata();
2103
- const sortedPlugins = this.sortPluginsByPriority();
2104
- const { wrapperPlugins, routePlugins } = this.separateWrapperAndRoutePlugins(sortedPlugins);
2105
- this.executePluginHook(
2106
- "onHandlerLoad",
2107
- (plugin) => {
2108
- plugin.onHandlerLoad?.(handlers);
2109
- },
2110
- wrapperPlugins
2111
- );
2112
- this.applyWrapperChains();
2113
- this.executePluginHook(
2114
- "onHandlerLoad",
2115
- (plugin) => {
2116
- plugin.onHandlerLoad?.(handlers);
2117
- },
2118
- routePlugins
2291
+ onHandlerLoad(handlers) {
2292
+ const actionHandlers = handlers.filter(
2293
+ (handler) => handler.type === "action"
2119
2294
  );
2120
- }
2121
- /**
2122
- * 执行插件启动钩子
2123
- */
2124
- async executePluginStartHooks() {
2125
- this.executePluginHook("onBeforeStart", (plugin) => {
2126
- plugin.onBeforeStart?.(this);
2127
- });
2128
- for (const plugin of this.plugins) {
2129
- try {
2130
- await plugin.onAfterStart?.(this);
2131
- } catch (error) {
2132
- throw new Error(
2133
- `Plugin ${plugin.name} failed in onAfterStart: ${error instanceof Error ? error.message : String(error)}`
2295
+ logger_default.info(`Found ${actionHandlers.length} action handler(s)`);
2296
+ for (const handler of actionHandlers) {
2297
+ const actionOptions = handler.options;
2298
+ const methodName = handler.methodName;
2299
+ const moduleClass = handler.module;
2300
+ const moduleInstance = this.engine.get(moduleClass);
2301
+ if (!moduleInstance) {
2302
+ logger_default.warn(
2303
+ `Module instance not found for ${moduleClass.name}, skipping action registration`
2134
2304
  );
2305
+ continue;
2135
2306
  }
2136
- }
2137
- this.registerVersionRoute();
2138
- }
2139
- /**
2140
- * 注册版本路由(/prefix/version)
2141
- * 用于健康检查和探针
2142
- */
2143
- registerVersionRoute() {
2144
- const prefix = this.options.prefix || "";
2145
- const versionPath = prefix ? `${prefix}` : "/";
2146
- const existingRoutes = this.hono.routes;
2147
- const routeExists = existingRoutes.some(
2148
- (route) => route.path === versionPath && route.method === "GET"
2149
- );
2150
- if (routeExists) {
2151
- logger_default.info(
2152
- `Version route ${versionPath} already exists, skipping registration`
2153
- );
2154
- return;
2155
- }
2156
- try {
2157
- this.hono.get(versionPath, async (ctx) => {
2158
- return ctx.json({
2159
- name: this.options.name,
2160
- version: this.options.version,
2161
- status: "running"
2162
- });
2163
- });
2164
- logger_default.info(`Registered version route: GET ${versionPath}`);
2165
- } catch (error) {
2166
- if (error instanceof Error && error.message.includes("matcher is already built")) {
2307
+ const moduleMetadata = this.engine.getModules().find((m) => m.clazz === moduleClass);
2308
+ if (!moduleMetadata) {
2167
2309
  logger_default.warn(
2168
- `Cannot register version route ${versionPath}: route matcher is already built. If you have already registered a route at ${versionPath}, it will be used instead.`
2310
+ `Module metadata not found for ${moduleClass.name}, skipping action registration`
2169
2311
  );
2170
- } else {
2171
- throw error;
2312
+ continue;
2172
2313
  }
2314
+ const moduleOptions = moduleMetadata.options || {};
2315
+ const enginePrefix = this.engine.options.prefix || "";
2316
+ const actionPath = buildActionPath(
2317
+ enginePrefix,
2318
+ moduleMetadata.name,
2319
+ methodName
2320
+ );
2321
+ const paramSchemas = actionOptions.params || [];
2322
+ const returnSchema = actionOptions.returns;
2323
+ const actionHandler = async (ctx) => {
2324
+ const method = moduleInstance[methodName];
2325
+ if (typeof method !== "function") {
2326
+ const errorResponse = ejson.stringify({
2327
+ success: false,
2328
+ error: "Action method not found"
2329
+ });
2330
+ return ctx.text(errorResponse, 500, {
2331
+ "Content-Type": "application/json"
2332
+ });
2333
+ }
2334
+ try {
2335
+ let body = {};
2336
+ if (ctx.req.method === "GET") {
2337
+ const url = new URL(ctx.req.url);
2338
+ url.searchParams.forEach((value, key) => {
2339
+ body[key] = value;
2340
+ });
2341
+ } else {
2342
+ try {
2343
+ const rawBody = await ctx.req.text();
2344
+ if (rawBody) {
2345
+ body = ejson.parse(rawBody);
2346
+ }
2347
+ } catch (parseError) {
2348
+ try {
2349
+ body = await ctx.req.json().catch(() => ({}));
2350
+ } catch {
2351
+ body = {};
2352
+ }
2353
+ }
2354
+ }
2355
+ const validation = parseAndValidateParams(body, paramSchemas);
2356
+ if (!validation.success) {
2357
+ const errors = validation.error.issues || [];
2358
+ const errorMessage = `Validation failed: ${errors.map((e) => {
2359
+ const path = e.path || [];
2360
+ const pathStr = path.length > 0 ? path.map((p, i) => {
2361
+ if (i === 0 && typeof p === "string" && /^\d+$/.test(p)) {
2362
+ return `\u53C2\u6570[${p}]`;
2363
+ }
2364
+ return String(p);
2365
+ }).join(".") : "unknown";
2366
+ return `${pathStr}: ${e.message}`;
2367
+ }).join(", ")}`;
2368
+ const errorResponse = ejson.stringify({
2369
+ success: false,
2370
+ error: errorMessage
2371
+ });
2372
+ return ctx.text(errorResponse, 400, {
2373
+ "Content-Type": "application/json"
2374
+ });
2375
+ }
2376
+ const methodLength = method.length;
2377
+ const paramsLength = paramSchemas.length;
2378
+ const args = [...validation.data];
2379
+ if (methodLength > paramsLength) {
2380
+ args.unshift(ctx);
2381
+ }
2382
+ const result = await method.apply(moduleInstance, args);
2383
+ if (actionOptions.stream) {
2384
+ if (!isAsyncIterable(result)) {
2385
+ const errorResponse = ejson.stringify({
2386
+ success: false,
2387
+ error: "Stream action must return AsyncIterable"
2388
+ });
2389
+ return ctx.text(errorResponse, 500, {
2390
+ "Content-Type": "application/json"
2391
+ });
2392
+ }
2393
+ const encoder = new TextEncoder();
2394
+ const iterator = result[Symbol.asyncIterator]();
2395
+ const stream = new ReadableStream({
2396
+ async start(controller) {
2397
+ try {
2398
+ while (true) {
2399
+ const { value, done } = await iterator.next();
2400
+ if (done) {
2401
+ controller.enqueue(
2402
+ encoder.encode(ejson.stringify({ done: true }))
2403
+ );
2404
+ controller.close();
2405
+ break;
2406
+ }
2407
+ controller.enqueue(
2408
+ encoder.encode(ejson.stringify({ value, done: false }))
2409
+ );
2410
+ }
2411
+ } catch (error) {
2412
+ controller.enqueue(
2413
+ encoder.encode(
2414
+ ejson.stringify({
2415
+ error: error instanceof Error ? error.message : String(error),
2416
+ done: true
2417
+ })
2418
+ )
2419
+ );
2420
+ controller.close();
2421
+ }
2422
+ }
2423
+ });
2424
+ return new Response(stream, {
2425
+ headers: {
2426
+ "Content-Type": "text/event-stream",
2427
+ "Cache-Control": "no-cache",
2428
+ Connection: "keep-alive"
2429
+ }
2430
+ });
2431
+ }
2432
+ if (returnSchema) {
2433
+ const returnValidation = returnSchema.safeParse(result);
2434
+ if (!returnValidation.success) {
2435
+ logger_default.error(
2436
+ `Return value validation failed for ${moduleClass.name}.${methodName}`,
2437
+ returnValidation.error
2438
+ );
2439
+ const returnErrors = returnValidation.error.issues || [];
2440
+ const errorMessage = `Return value validation failed: ${returnErrors.map((e) => {
2441
+ const path = e.path || [];
2442
+ const pathStr = path.length > 0 ? path.map((p) => String(p)).join(".") : "root";
2443
+ return `${pathStr}: ${e.message}`;
2444
+ }).join(", ")}`;
2445
+ const errorResponse = ejson.stringify({
2446
+ success: false,
2447
+ error: errorMessage
2448
+ });
2449
+ return ctx.text(errorResponse, 400, {
2450
+ "Content-Type": "application/json"
2451
+ });
2452
+ }
2453
+ const successResponse2 = ejson.stringify({
2454
+ success: true,
2455
+ data: returnValidation.data
2456
+ });
2457
+ return ctx.text(successResponse2, 200, {
2458
+ "Content-Type": "application/json"
2459
+ });
2460
+ }
2461
+ const successResponse = ejson.stringify({
2462
+ success: true,
2463
+ data: result
2464
+ });
2465
+ return ctx.text(successResponse, 200, {
2466
+ "Content-Type": "application/json"
2467
+ });
2468
+ } catch (error) {
2469
+ logger_default.error(
2470
+ `Error in action handler ${moduleClass.name}.${methodName}`,
2471
+ error
2472
+ );
2473
+ const errorResponse = ejson.stringify({
2474
+ success: false,
2475
+ error: error instanceof Error ? error.message : String(error)
2476
+ });
2477
+ return ctx.text(errorResponse, 500, {
2478
+ "Content-Type": "application/json"
2479
+ });
2480
+ }
2481
+ };
2482
+ const hono = this.engine.getHono();
2483
+ let route = hono;
2484
+ if (moduleOptions.actionMiddlewares && moduleOptions.actionMiddlewares.length > 0) {
2485
+ route = route.use(actionPath, ...moduleOptions.actionMiddlewares);
2486
+ }
2487
+ if (actionOptions.middlewares && actionOptions.middlewares.length > 0) {
2488
+ route = route.use(actionPath, ...actionOptions.middlewares);
2489
+ }
2490
+ route.get(actionPath, actionHandler);
2491
+ route.post(actionPath, actionHandler);
2492
+ logger_default.info(
2493
+ `[\u6CE8\u518C\u52A8\u4F5C] ${moduleClass.name}.${methodName} ${actionOptions.description}`
2494
+ );
2173
2495
  }
2174
2496
  }
2175
- /**
2176
- * 启动 HTTP 服务器
2177
- */
2178
- startHttpServer(port, hostname) {
2179
- this.server = serve({
2180
- fetch: this.hono.fetch,
2181
- port,
2182
- hostname
2183
- });
2184
- logger_default.info(`HTTP server started on http://${hostname}:${port}`);
2497
+ };
2498
+
2499
+ // src/plugins/action/decorator.ts
2500
+ function Action(options) {
2501
+ return Handler({
2502
+ type: "action",
2503
+ options
2504
+ });
2505
+ }
2506
+
2507
+ // src/plugins/cache/adapter.ts
2508
+ var MemoryCacheAdapter = class {
2509
+ cache = /* @__PURE__ */ new Map();
2510
+ async get(key) {
2511
+ const item = this.cache.get(key);
2512
+ if (!item) {
2513
+ return null;
2514
+ }
2515
+ if (item.expiresAt <= Date.now()) {
2516
+ this.cache.delete(key);
2517
+ return null;
2518
+ }
2519
+ return item;
2185
2520
  }
2186
- /**
2187
- * 按优先级排序插件
2188
- */
2189
- sortPluginsByPriority() {
2190
- return [...this.plugins].sort((a, b) => {
2191
- const priorityA = this.getPluginPriority(a);
2192
- const priorityB = this.getPluginPriority(b);
2193
- if (priorityA !== priorityB) {
2194
- return priorityA - priorityB;
2195
- }
2196
- const indexA = this.plugins.indexOf(a);
2197
- const indexB = this.plugins.indexOf(b);
2198
- return indexA - indexB;
2199
- });
2521
+ async set(key, item) {
2522
+ this.cache.set(key, item);
2200
2523
  }
2201
- /**
2202
- * 获取插件优先级
2203
- */
2204
- getPluginPriority(plugin) {
2205
- return plugin.priority ?? 300 /* BUSINESS */;
2524
+ async delete(key) {
2525
+ return this.cache.delete(key);
2206
2526
  }
2207
- /**
2208
- * 分离包装插件和路由插件
2209
- */
2210
- separateWrapperAndRoutePlugins(sortedPlugins) {
2211
- const wrapperPlugins = [];
2212
- const routePlugins = [];
2213
- for (const plugin of sortedPlugins) {
2214
- const priority = this.getPluginPriority(plugin);
2215
- if (priority === 1e3 /* ROUTE */) {
2216
- routePlugins.push(plugin);
2217
- } else {
2218
- wrapperPlugins.push(plugin);
2219
- }
2220
- }
2221
- return { wrapperPlugins, routePlugins };
2527
+ async clear() {
2528
+ this.cache.clear();
2529
+ }
2530
+ async keys() {
2531
+ const now = Date.now();
2532
+ return Array.from(this.cache.entries()).filter(([_, item]) => item.expiresAt > now).map(([key]) => key);
2222
2533
  }
2223
2534
  /**
2224
- * 执行插件钩子(通用方法)
2535
+ * 清理所有过期项(高效批量清理)
2536
+ * 这个方法比通过 keys() + get() + delete() 更高效
2225
2537
  */
2226
- executePluginHook(hookName, callback, plugins = this.plugins) {
2227
- for (const plugin of plugins) {
2228
- try {
2229
- callback(plugin);
2230
- } catch (error) {
2231
- throw new Error(
2232
- `Plugin ${plugin.name} failed in ${hookName}: ${error instanceof Error ? error.message : String(error)}`
2233
- );
2538
+ async cleanupExpired() {
2539
+ const now = Date.now();
2540
+ let cleaned = 0;
2541
+ for (const [key, item] of this.cache.entries()) {
2542
+ if (item.expiresAt <= now) {
2543
+ this.cache.delete(key);
2544
+ cleaned++;
2234
2545
  }
2235
2546
  }
2547
+ return cleaned;
2236
2548
  }
2237
- /**
2238
- * 停止引擎
2239
- */
2240
- async stop() {
2241
- if (!this.started) {
2242
- return;
2549
+ async getStats() {
2550
+ const now = Date.now();
2551
+ const validEntries = Array.from(this.cache.entries()).filter(([_, item]) => item.expiresAt > now).map(([key, item]) => ({
2552
+ key,
2553
+ expiresAt: item.expiresAt,
2554
+ createdAt: item.createdAt
2555
+ }));
2556
+ return {
2557
+ size: validEntries.length,
2558
+ entries: validEntries
2559
+ };
2560
+ }
2561
+ };
2562
+ var RedisCacheAdapter = class {
2563
+ client;
2564
+ keyPrefix;
2565
+ constructor(options) {
2566
+ this.client = options.client;
2567
+ this.keyPrefix = options.keyPrefix || "cache:";
2568
+ }
2569
+ getKey(key) {
2570
+ return `${this.keyPrefix}${key}`;
2571
+ }
2572
+ async get(key) {
2573
+ const redisKey = this.getKey(key);
2574
+ const data = await this.client.get(redisKey);
2575
+ if (!data) {
2576
+ return null;
2243
2577
  }
2244
2578
  try {
2245
- if (this.server) {
2246
- if (typeof this.server.close === "function") {
2247
- if (this.server.close.length > 0) {
2248
- await new Promise((resolve, reject) => {
2249
- this.server.close((err) => {
2250
- if (err) {
2251
- reject(err);
2252
- } else {
2253
- resolve();
2254
- }
2255
- });
2256
- });
2257
- } else {
2258
- this.server.close();
2259
- await new Promise((resolve) => setTimeout(resolve, 10));
2260
- }
2261
- }
2262
- this.server = null;
2263
- logger_default.info("HTTP server stopped");
2264
- }
2265
- for (let i = this.plugins.length - 1; i >= 0; i--) {
2266
- const plugin = this.plugins[i];
2267
- try {
2268
- await plugin.onDestroy?.();
2269
- } catch (error) {
2270
- logger_default.error(`Plugin ${plugin.name} failed in onDestroy`, error);
2271
- }
2579
+ const item = JSON.parse(data);
2580
+ if (item.expiresAt <= Date.now()) {
2581
+ await this.delete(key);
2582
+ return null;
2272
2583
  }
2273
- this.modules = [];
2274
- this.moduleInstances.clear();
2275
- this.started = false;
2276
- this.actualPort = null;
2277
- logger_default.info(`${this.options.name} stopped`);
2584
+ return item;
2278
2585
  } catch (error) {
2279
- logger_default.error("Failed to stop engine", error);
2280
- throw error;
2586
+ await this.delete(key);
2587
+ return null;
2281
2588
  }
2282
2589
  }
2283
- /**
2284
- * 获取模块实例(单例)
2285
- * @param moduleClass 模块类
2286
- * @returns 模块实例
2287
- */
2288
- get(moduleClass) {
2289
- if (!this.moduleInstances.has(moduleClass)) {
2290
- this.moduleInstances.set(moduleClass, new moduleClass());
2291
- }
2292
- return this.moduleInstances.get(moduleClass);
2590
+ async set(key, item) {
2591
+ const redisKey = this.getKey(key);
2592
+ const data = JSON.stringify(item);
2593
+ const ttl = Math.max(0, Math.floor((item.expiresAt - Date.now()) / 1e3));
2594
+ await this.client.set(redisKey, data, "EX", ttl);
2293
2595
  }
2294
- /**
2295
- * 获取已注册的模块列表
2296
- */
2297
- getModules() {
2298
- if (this.modules.length === 0 && !this.started) {
2299
- this.loadModules();
2596
+ async delete(key) {
2597
+ const redisKey = this.getKey(key);
2598
+ const result = await this.client.del(redisKey);
2599
+ return result > 0;
2600
+ }
2601
+ async clear() {
2602
+ const pattern = `${this.keyPrefix}*`;
2603
+ const keys = await this.client.keys(pattern);
2604
+ if (keys.length > 0) {
2605
+ await Promise.all(keys.map((key) => this.client.del(key)));
2300
2606
  }
2301
- return [...this.modules];
2607
+ }
2608
+ async keys() {
2609
+ const pattern = `${this.keyPrefix}*`;
2610
+ const redisKeys = await this.client.keys(pattern);
2611
+ return redisKeys.map((key) => key.replace(this.keyPrefix, ""));
2302
2612
  }
2303
2613
  /**
2304
- * 获取实际使用的端口
2614
+ * 清理所有过期项
2615
+ *
2616
+ * 注意:Redis 本身支持自动过期(通过 SET key value EX seconds),
2617
+ * 过期键会被 Redis 自动删除。但在某些情况下(如测试环境、某些 Redis 客户端),
2618
+ * 可能需要手动清理。此方法会检查并清理:
2619
+ * 1. 已过期但尚未被 Redis 删除的键(双重保险)
2620
+ * 2. JSON 解析失败的数据
2305
2621
  */
2306
- getPort() {
2307
- return this.actualPort;
2308
- }
2309
- };
2310
-
2311
- // src/core/types.ts
2312
- var PluginPriority = /* @__PURE__ */ ((PluginPriority2) => {
2313
- PluginPriority2[PluginPriority2["SYSTEM"] = 50] = "SYSTEM";
2314
- PluginPriority2[PluginPriority2["SECURITY"] = 100] = "SECURITY";
2315
- PluginPriority2[PluginPriority2["LOGGING"] = 200] = "LOGGING";
2316
- PluginPriority2[PluginPriority2["BUSINESS"] = 300] = "BUSINESS";
2317
- PluginPriority2[PluginPriority2["PERFORMANCE"] = 400] = "PERFORMANCE";
2318
- PluginPriority2[PluginPriority2["ROUTE"] = 1e3] = "ROUTE";
2319
- return PluginPriority2;
2320
- })(PluginPriority || {});
2321
-
2322
- // src/core/checker.ts
2323
- async function startCheck(checkers, pass) {
2324
- logger_default.info("[ \u9884\u68C0\u5F00\u59CB ]");
2325
- for (const [index, checker] of checkers.entries()) {
2326
- const seq = index + 1;
2327
- logger_default.info(`${seq}. ${checker.name}`);
2328
- try {
2329
- if (checker.skip) {
2330
- logger_default.warn(`${seq}. ${checker.name} [\u8DF3\u8FC7]`);
2622
+ async cleanupExpired() {
2623
+ const pattern = `${this.keyPrefix}*`;
2624
+ const redisKeys = await this.client.keys(pattern);
2625
+ let cleaned = 0;
2626
+ for (const redisKey of redisKeys) {
2627
+ const data = await this.client.get(redisKey);
2628
+ if (!data) {
2331
2629
  continue;
2332
2630
  }
2333
- await checker.check();
2334
- logger_default.info(`${seq}. ${checker.name} [\u6210\u529F]`);
2335
- } catch (error) {
2336
- logger_default.error(`${seq}. ${checker.name} [\u5931\u8D25]`);
2337
- throw error;
2631
+ try {
2632
+ const item = JSON.parse(data);
2633
+ if (item.expiresAt <= Date.now()) {
2634
+ await this.client.del(redisKey);
2635
+ cleaned++;
2636
+ }
2637
+ } catch {
2638
+ await this.client.del(redisKey);
2639
+ cleaned++;
2640
+ }
2338
2641
  }
2642
+ return cleaned;
2339
2643
  }
2340
- logger_default.info("[ \u9884\u68C0\u5B8C\u6210 ]");
2341
- if (pass) await pass();
2342
- }
2644
+ async getStats() {
2645
+ const allKeys = await this.keys();
2646
+ const entries = [];
2647
+ for (const key of allKeys) {
2648
+ const item = await this.get(key);
2649
+ if (item) {
2650
+ entries.push({
2651
+ key,
2652
+ expiresAt: item.expiresAt,
2653
+ createdAt: item.createdAt
2654
+ });
2655
+ }
2656
+ }
2657
+ return {
2658
+ size: entries.length,
2659
+ entries
2660
+ };
2661
+ }
2662
+ };
2343
2663
 
2344
- // src/plugins/route/plugin.ts
2345
- var RoutePlugin = class {
2346
- name = "route-plugin";
2347
- priority = 1e3 /* ROUTE */;
2348
- engine;
2349
- globalMiddlewares;
2664
+ // src/plugins/cache/plugin.ts
2665
+ var CachePlugin = class {
2666
+ name = "cache-plugin";
2667
+ priority = 400 /* PERFORMANCE */;
2668
+ // 性能优化插件,优先级较低
2669
+ // 缓存适配器
2670
+ adapter;
2671
+ // 清理定时器
2672
+ cleanupTimer = null;
2673
+ // 引擎引用
2674
+ engine = null;
2675
+ // 模块配置
2676
+ defaultTtl = 6e4;
2677
+ // 默认1分钟
2678
+ cacheEnabled = true;
2679
+ cleanupInterval = 6e4;
2680
+ // 默认1分钟清理一次
2350
2681
  /**
2351
2682
  * 构造函数
2352
- * @param options 插件配置选项
2683
+ * @param adapter 缓存适配器,如果不提供则使用默认的内存缓存适配器
2353
2684
  */
2354
- constructor(options) {
2355
- this.globalMiddlewares = options?.globalMiddlewares || [];
2685
+ constructor(adapter) {
2686
+ this.adapter = adapter || new MemoryCacheAdapter();
2356
2687
  }
2357
2688
  /**
2358
- * 声明Module配置Schema(用于类型推导+运行时校验)
2689
+ * 声明Module配置Schema
2359
2690
  */
2360
2691
  getModuleOptionsSchema() {
2361
2692
  return {
2362
2693
  _type: {},
2363
2694
  validate: (options) => {
2364
- if (options.routePrefix !== void 0 && typeof options.routePrefix !== "string") {
2365
- return "routePrefix must be a string";
2366
- }
2367
- if (options.routePrefix && !options.routePrefix.startsWith("/")) {
2368
- return `routePrefix must start with '/'`;
2695
+ if (options.cacheDefaultTtl !== void 0 && options.cacheDefaultTtl < 0) {
2696
+ return `cacheDefaultTtl must be >= 0`;
2369
2697
  }
2370
- if (options.routeMiddlewares !== void 0 && !Array.isArray(options.routeMiddlewares)) {
2371
- return "routeMiddlewares must be an array";
2698
+ if (options.cacheCleanupInterval !== void 0 && options.cacheCleanupInterval < 0) {
2699
+ return `cacheCleanupInterval must be >= 0`;
2372
2700
  }
2373
2701
  return true;
2374
2702
  }
2375
2703
  };
2376
2704
  }
2377
2705
  /**
2378
- * 引擎初始化钩子:获取Hono实例
2706
+ * 引擎初始化前钩子
2379
2707
  */
2380
2708
  onInit(engine) {
2381
2709
  this.engine = engine;
2382
- logger_default.info("RoutePlugin initialized");
2710
+ logger_default.info("CachePlugin initialized cache storage");
2383
2711
  }
2384
2712
  /**
2385
- * Handler加载钩子:解析type="route"的Handler元数据,注册HTTP路由
2713
+ * Handler加载后钩子
2714
+ * 拦截带有 type="cache" 的 Handler,包装原始方法实现缓存逻辑
2386
2715
  */
2387
2716
  onHandlerLoad(handlers) {
2388
- const routeHandlers = handlers.filter(
2389
- (handler) => handler.type === "route"
2717
+ const cacheHandlers = handlers.filter(
2718
+ (handler) => handler.type === "cache"
2390
2719
  );
2391
- logger_default.info(`Found ${routeHandlers.length} route handler(s)`);
2392
- for (const handler of routeHandlers) {
2393
- const routeOptions = handler.options;
2720
+ logger_default.info(`Found ${cacheHandlers.length} cache handler(s)`);
2721
+ for (const handler of cacheHandlers) {
2394
2722
  const methodName = handler.methodName;
2395
2723
  const moduleClass = handler.module;
2396
- const moduleInstance = this.engine.get(moduleClass);
2397
- if (!moduleInstance) {
2398
- logger_default.warn(
2399
- `Module instance not found for ${moduleClass.name}, skipping route registration`
2400
- );
2724
+ const cacheOptions = handler.options || {};
2725
+ const moduleMetadata = this.engine?.getModules().find((m) => m.clazz === moduleClass);
2726
+ if (!moduleMetadata) {
2727
+ logger_default.warn(`Module metadata not found for ${moduleClass.name}`);
2401
2728
  continue;
2402
2729
  }
2403
- const moduleMetadata = this.engine.getModules().find((m) => m.clazz === moduleClass);
2404
- const moduleOptions = moduleMetadata?.options || {};
2405
- const routePrefix = moduleOptions.routePrefix || "";
2406
- const paths = Array.isArray(routeOptions.path) ? routeOptions.path : [routeOptions.path];
2407
- const fullPaths = paths.map((p) => routePrefix + p);
2408
- const routeHandler = async (ctx) => {
2409
- const method = moduleInstance[methodName];
2410
- if (typeof method !== "function") {
2411
- return ctx.json({ error: "Handler method not found" }, 500);
2412
- }
2413
- try {
2414
- const result = await method.call(moduleInstance, ctx);
2415
- if (result instanceof Response) {
2416
- return result;
2417
- }
2418
- if (typeof result === "string" || typeof result === "object" && result !== null && "type" in result) {
2419
- return result;
2420
- }
2421
- return ctx.json(result);
2422
- } catch (error) {
2423
- logger_default.error(
2424
- `Error in route handler ${moduleClass.name}.${methodName}`,
2425
- error
2426
- );
2427
- return ctx.json(
2428
- {
2429
- error: "Internal server error",
2430
- message: error instanceof Error ? error.message : String(error)
2431
- },
2432
- 500
2433
- );
2434
- }
2435
- };
2436
- const hono = this.engine.getHono();
2437
- const methods = routeOptions.method ? Array.isArray(routeOptions.method) ? routeOptions.method : [routeOptions.method] : ["GET"];
2438
- for (const fullPath of fullPaths) {
2439
- let route = hono;
2440
- if (this.globalMiddlewares.length > 0) {
2441
- route = route.use(fullPath, ...this.globalMiddlewares);
2442
- }
2443
- if (moduleOptions.routeMiddlewares && moduleOptions.routeMiddlewares.length > 0) {
2444
- route = route.use(fullPath, ...moduleOptions.routeMiddlewares);
2445
- }
2446
- if (routeOptions.middlewares && routeOptions.middlewares.length > 0) {
2447
- route = route.use(fullPath, ...routeOptions.middlewares);
2448
- }
2449
- for (const method of methods) {
2450
- const methodLower = method.toLowerCase();
2451
- if (methodLower === "get" || methodLower === "post" || methodLower === "put" || methodLower === "delete" || methodLower === "patch" || methodLower === "head" || methodLower === "options") {
2452
- route[methodLower](fullPath, routeHandler);
2453
- const description = routeOptions.description ? ` (${routeOptions.description})` : "";
2454
- logger_default.info(
2455
- `Registered route: ${method.toUpperCase()} ${fullPath} -> ${moduleClass.name}.${methodName}${description}`
2456
- );
2457
- } else {
2458
- logger_default.warn(
2459
- `Unsupported HTTP method: ${method}, skipping route ${fullPath}`
2460
- );
2461
- }
2462
- }
2730
+ const moduleOptions = moduleMetadata.options;
2731
+ const moduleDefaultTtl = moduleOptions?.cacheDefaultTtl ?? this.defaultTtl;
2732
+ const moduleCacheEnabled = moduleOptions?.cacheEnabled ?? this.cacheEnabled;
2733
+ const ttl = cacheOptions.ttl ?? moduleDefaultTtl;
2734
+ const enabled = cacheOptions.enabled ?? moduleCacheEnabled;
2735
+ if (!enabled) {
2736
+ logger_default.info(`Cache disabled for ${moduleClass.name}.${methodName}`);
2737
+ continue;
2463
2738
  }
2739
+ const moduleName = moduleMetadata.name;
2740
+ handler.wrap(async (next, instance, ...args) => {
2741
+ const cacheKey = this.generateCacheKey(
2742
+ moduleName,
2743
+ methodName,
2744
+ cacheOptions.key,
2745
+ args
2746
+ );
2747
+ const cached = await this.adapter.get(cacheKey);
2748
+ if (cached) {
2749
+ logger_default.debug(`Cache hit for ${cacheKey}`);
2750
+ return cached.value;
2751
+ }
2752
+ logger_default.debug(`Cache miss for ${cacheKey}`);
2753
+ const result = await next();
2754
+ await this.adapter.set(cacheKey, {
2755
+ value: result,
2756
+ expiresAt: Date.now() + ttl,
2757
+ createdAt: Date.now()
2758
+ });
2759
+ return result;
2760
+ });
2761
+ logger_default.info(
2762
+ `Wrapped ${moduleClass.name}.${methodName} with cache (TTL: ${ttl}ms)`
2763
+ );
2464
2764
  }
2465
2765
  }
2466
- };
2467
-
2468
- // src/plugins/route/decorator.ts
2469
- function Route(options) {
2470
- return Handler({
2471
- type: "route",
2472
- options
2473
- });
2474
- }
2475
- function Page(options) {
2476
- const pageOptions = {
2477
- method: options.method || "GET",
2478
- ...options
2479
- };
2480
- return Route(pageOptions);
2481
- }
2482
- var DEFAULT_FAVICON = /* @__PURE__ */ jsx(
2483
- "link",
2484
- {
2485
- rel: "icon",
2486
- 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>",
2487
- type: "image/svg+xml"
2488
- }
2489
- );
2490
- var BaseLayout = (props = {
2491
- title: "Microservice Template"
2492
- }) => html`<!DOCTYPE html>
2493
- <html>
2494
- <head>
2495
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2496
- <title>${props.title}</title>
2497
- ${props.heads}
2498
- </head>
2499
- <body>
2500
- ${props.children}
2501
- </body>
2502
- </html>`;
2503
- var HtmxLayout = (props = {
2504
- title: "Microservice Template"
2505
- }) => BaseLayout({
2506
- title: props.title,
2507
- heads: html`
2508
- <script src="https://unpkg.com/htmx.org@latest"></script>
2509
- <script src="https://unpkg.com/hyperscript.org@latest"></script>
2510
- <script src="https://cdn.tailwindcss.com"></script>
2511
- ${props.favicon || DEFAULT_FAVICON}
2512
- `,
2513
- children: props.children
2514
- });
2515
- function buildParamsSchema(schemas) {
2516
- const shape = {};
2517
- for (let i = 0; i < schemas.length; i++) {
2518
- shape[String(i)] = schemas[i];
2519
- }
2520
- return z.object(shape);
2521
- }
2522
- function parseAndValidateParams(body, schemas) {
2523
- if (!body || typeof body !== "object") {
2524
- body = {};
2525
- }
2526
- if (schemas.length === 0) {
2527
- return { success: true, data: [] };
2528
- }
2529
- const paramsSchema = buildParamsSchema(schemas);
2530
- const validation = paramsSchema.safeParse(body);
2531
- if (!validation.success) {
2532
- return {
2533
- success: false,
2534
- error: validation.error
2535
- };
2536
- }
2537
- const validatedData = [];
2538
- for (let i = 0; i < schemas.length; i++) {
2539
- validatedData[i] = validation.data[String(i)];
2540
- }
2541
- return {
2542
- success: true,
2543
- data: validatedData
2544
- };
2545
- }
2546
- function buildActionPath(enginePrefix, moduleName, handlerName) {
2547
- const normalizedPrefix = enginePrefix.replace(/\/+$/, "");
2548
- return `/${normalizedPrefix}/${moduleName}/${handlerName}`.replace(/\/+/g, "/").replace(/^\/\//, "/");
2549
- }
2550
-
2551
- // src/plugins/action/plugin.ts
2552
- function isAsyncIterable(obj) {
2553
- return obj != null && typeof obj[Symbol.asyncIterator] === "function";
2554
- }
2555
- var ActionPlugin = class {
2556
- name = "action-plugin";
2557
- priority = 1e3 /* ROUTE */;
2558
- // 路由插件优先级最低,必须最后执行
2559
- engine;
2560
2766
  /**
2561
- * 声明Module配置Schema(用于类型推导+运行时校验)
2767
+ * 引擎启动前钩子
2562
2768
  */
2563
- getModuleOptionsSchema() {
2564
- return {
2565
- _type: {},
2566
- validate: (options) => {
2567
- if (options.actionMiddlewares !== void 0 && !Array.isArray(options.actionMiddlewares)) {
2568
- return "actionMiddlewares must be an array";
2569
- }
2570
- return true;
2571
- }
2572
- };
2769
+ onBeforeStart(engine) {
2770
+ const moduleOptions = engine.options;
2771
+ const cleanupInterval = moduleOptions?.cacheCleanupInterval ?? this.cleanupInterval;
2772
+ if (cleanupInterval > 0) {
2773
+ this.cleanupTimer = setInterval(() => {
2774
+ this.cleanup().catch((error) => {
2775
+ logger_default.error("Cache cleanup failed", error);
2776
+ });
2777
+ }, cleanupInterval);
2778
+ logger_default.info(
2779
+ `Started cache cleanup timer (interval: ${cleanupInterval}ms)`
2780
+ );
2781
+ }
2573
2782
  }
2574
2783
  /**
2575
- * 引擎初始化钩子:获取Hono实例
2784
+ * 引擎停止时钩子
2576
2785
  */
2577
- onInit(engine) {
2578
- this.engine = engine;
2579
- logger_default.info("ActionPlugin initialized");
2786
+ async onDestroy() {
2787
+ if (this.cleanupTimer) {
2788
+ clearInterval(this.cleanupTimer);
2789
+ this.cleanupTimer = null;
2790
+ }
2791
+ await this.adapter.clear();
2792
+ logger_default.info("Cache storage cleared");
2580
2793
  }
2581
2794
  /**
2582
- * Handler加载钩子:解析type="action"的Handler元数据,注册HTTP路由
2795
+ * 生成缓存键
2796
+ * @param moduleName 模块名
2797
+ * @param methodName 方法名
2798
+ * @param keyFunction 可选的 key 函数
2799
+ * @param args 方法参数
2800
+ * @returns 缓存键(格式:模块名:方法名:hash)
2583
2801
  */
2584
- onHandlerLoad(handlers) {
2585
- const actionHandlers = handlers.filter(
2586
- (handler) => handler.type === "action"
2587
- );
2588
- logger_default.info(`Found ${actionHandlers.length} action handler(s)`);
2589
- for (const handler of actionHandlers) {
2590
- const actionOptions = handler.options;
2591
- const methodName = handler.methodName;
2592
- const moduleClass = handler.module;
2593
- const moduleInstance = this.engine.get(moduleClass);
2594
- if (!moduleInstance) {
2595
- logger_default.warn(
2596
- `Module instance not found for ${moduleClass.name}, skipping action registration`
2597
- );
2598
- continue;
2599
- }
2600
- const moduleMetadata = this.engine.getModules().find((m) => m.clazz === moduleClass);
2601
- if (!moduleMetadata) {
2602
- logger_default.warn(
2603
- `Module metadata not found for ${moduleClass.name}, skipping action registration`
2604
- );
2605
- continue;
2802
+ generateCacheKey(moduleName, methodName, keyFunction, args) {
2803
+ const keyData = keyFunction ? keyFunction(...args) : args;
2804
+ const serialized = ejson.stringify(keyData);
2805
+ const hash = createHash("sha256").update(serialized).digest("hex");
2806
+ return `${moduleName}:${methodName}:${hash}`;
2807
+ }
2808
+ /**
2809
+ * 清理过期缓存
2810
+ */
2811
+ async cleanup() {
2812
+ if (typeof this.adapter.cleanupExpired === "function") {
2813
+ const cleaned = await this.adapter.cleanupExpired();
2814
+ if (cleaned > 0) {
2815
+ logger_default.debug(`Cleaned up ${cleaned} expired cache entry(ies)`);
2606
2816
  }
2607
- const moduleOptions = moduleMetadata.options || {};
2608
- const enginePrefix = this.engine.options.prefix || "";
2609
- const actionPath = buildActionPath(
2610
- enginePrefix,
2611
- moduleMetadata.name,
2612
- methodName
2613
- );
2614
- const paramSchemas = actionOptions.params || [];
2615
- const returnSchema = actionOptions.returns;
2616
- const actionHandler = async (ctx) => {
2617
- const method = moduleInstance[methodName];
2618
- if (typeof method !== "function") {
2619
- const errorResponse = ejson.stringify({
2620
- success: false,
2621
- error: "Action method not found"
2622
- });
2623
- return ctx.text(errorResponse, 500, {
2624
- "Content-Type": "application/json"
2625
- });
2626
- }
2627
- try {
2628
- let body = {};
2629
- if (ctx.req.method === "GET") {
2630
- const url = new URL(ctx.req.url);
2631
- url.searchParams.forEach((value, key) => {
2632
- body[key] = value;
2633
- });
2634
- } else {
2635
- try {
2636
- const rawBody = await ctx.req.text();
2637
- if (rawBody) {
2638
- body = ejson.parse(rawBody);
2639
- }
2640
- } catch (parseError) {
2641
- try {
2642
- body = await ctx.req.json().catch(() => ({}));
2643
- } catch {
2644
- body = {};
2645
- }
2646
- }
2647
- }
2648
- const validation = parseAndValidateParams(body, paramSchemas);
2649
- if (!validation.success) {
2650
- const errors = validation.error.issues || [];
2651
- const errorMessage = `Validation failed: ${errors.map((e) => {
2652
- const path = e.path || [];
2653
- const pathStr = path.length > 0 ? path.map((p, i) => {
2654
- if (i === 0 && typeof p === "string" && /^\d+$/.test(p)) {
2655
- return `\u53C2\u6570[${p}]`;
2656
- }
2657
- return String(p);
2658
- }).join(".") : "unknown";
2659
- return `${pathStr}: ${e.message}`;
2660
- }).join(", ")}`;
2661
- const errorResponse = ejson.stringify({
2662
- success: false,
2663
- error: errorMessage
2664
- });
2665
- return ctx.text(errorResponse, 400, {
2666
- "Content-Type": "application/json"
2667
- });
2668
- }
2669
- const methodLength = method.length;
2670
- const paramsLength = paramSchemas.length;
2671
- const args = [...validation.data];
2672
- if (methodLength > paramsLength) {
2673
- args.unshift(ctx);
2674
- }
2675
- const result = await method.apply(moduleInstance, args);
2676
- if (actionOptions.stream) {
2677
- if (!isAsyncIterable(result)) {
2678
- const errorResponse = ejson.stringify({
2679
- success: false,
2680
- error: "Stream action must return AsyncIterable"
2681
- });
2682
- return ctx.text(errorResponse, 500, {
2683
- "Content-Type": "application/json"
2684
- });
2685
- }
2686
- const encoder = new TextEncoder();
2687
- const iterator = result[Symbol.asyncIterator]();
2688
- const stream = new ReadableStream({
2689
- async start(controller) {
2690
- try {
2691
- while (true) {
2692
- const { value, done } = await iterator.next();
2693
- if (done) {
2694
- controller.enqueue(
2695
- encoder.encode(ejson.stringify({ done: true }))
2696
- );
2697
- controller.close();
2698
- break;
2699
- }
2700
- controller.enqueue(
2701
- encoder.encode(ejson.stringify({ value, done: false }))
2702
- );
2703
- }
2704
- } catch (error) {
2705
- controller.enqueue(
2706
- encoder.encode(
2707
- ejson.stringify({
2708
- error: error instanceof Error ? error.message : String(error),
2709
- done: true
2710
- })
2711
- )
2712
- );
2713
- controller.close();
2714
- }
2715
- }
2716
- });
2717
- return new Response(stream, {
2718
- headers: {
2719
- "Content-Type": "text/event-stream",
2720
- "Cache-Control": "no-cache",
2721
- Connection: "keep-alive"
2722
- }
2723
- });
2724
- }
2725
- if (returnSchema) {
2726
- const returnValidation = returnSchema.safeParse(result);
2727
- if (!returnValidation.success) {
2728
- logger_default.error(
2729
- `Return value validation failed for ${moduleClass.name}.${methodName}`,
2730
- returnValidation.error
2731
- );
2732
- const returnErrors = returnValidation.error.issues || [];
2733
- const errorMessage = `Return value validation failed: ${returnErrors.map((e) => {
2734
- const path = e.path || [];
2735
- const pathStr = path.length > 0 ? path.map((p) => String(p)).join(".") : "root";
2736
- return `${pathStr}: ${e.message}`;
2737
- }).join(", ")}`;
2738
- const errorResponse = ejson.stringify({
2739
- success: false,
2740
- error: errorMessage
2741
- });
2742
- return ctx.text(errorResponse, 400, {
2743
- "Content-Type": "application/json"
2744
- });
2745
- }
2746
- const successResponse2 = ejson.stringify({
2747
- success: true,
2748
- data: returnValidation.data
2749
- });
2750
- return ctx.text(successResponse2, 200, {
2751
- "Content-Type": "application/json"
2752
- });
2753
- }
2754
- const successResponse = ejson.stringify({
2755
- success: true,
2756
- data: result
2757
- });
2758
- return ctx.text(successResponse, 200, {
2759
- "Content-Type": "application/json"
2760
- });
2761
- } catch (error) {
2762
- logger_default.error(
2763
- `Error in action handler ${moduleClass.name}.${methodName}`,
2764
- error
2765
- );
2766
- const errorResponse = ejson.stringify({
2767
- success: false,
2768
- error: error instanceof Error ? error.message : String(error)
2769
- });
2770
- return ctx.text(errorResponse, 500, {
2771
- "Content-Type": "application/json"
2772
- });
2817
+ } else {
2818
+ const now = Date.now();
2819
+ let cleaned = 0;
2820
+ const keys = await this.adapter.keys();
2821
+ for (const key of keys) {
2822
+ const item = await this.adapter.get(key);
2823
+ if (!item || item.expiresAt <= now) {
2824
+ await this.adapter.delete(key);
2825
+ cleaned++;
2773
2826
  }
2774
- };
2775
- const hono = this.engine.getHono();
2776
- let route = hono;
2777
- if (moduleOptions.actionMiddlewares && moduleOptions.actionMiddlewares.length > 0) {
2778
- route = route.use(actionPath, ...moduleOptions.actionMiddlewares);
2779
2827
  }
2780
- if (actionOptions.middlewares && actionOptions.middlewares.length > 0) {
2781
- route = route.use(actionPath, ...actionOptions.middlewares);
2828
+ if (cleaned > 0) {
2829
+ logger_default.debug(`Cleaned up ${cleaned} expired cache entry(ies)`);
2782
2830
  }
2783
- route.get(actionPath, actionHandler);
2784
- route.post(actionPath, actionHandler);
2785
- logger_default.info(
2786
- `[\u6CE8\u518C\u52A8\u4F5C] ${moduleClass.name}.${methodName} ${actionOptions.description}`
2787
- );
2788
2831
  }
2789
2832
  }
2833
+ /**
2834
+ * 获取缓存统计信息
2835
+ */
2836
+ async getStats() {
2837
+ return await this.adapter.getStats();
2838
+ }
2839
+ /**
2840
+ * 清空所有缓存
2841
+ */
2842
+ async clear() {
2843
+ await this.adapter.clear();
2844
+ logger_default.info("All cache cleared");
2845
+ }
2846
+ /**
2847
+ * 删除指定的缓存项
2848
+ */
2849
+ async delete(key) {
2850
+ return await this.adapter.delete(key);
2851
+ }
2790
2852
  };
2791
2853
 
2792
- // src/plugins/action/decorator.ts
2793
- function Action(options) {
2854
+ // src/plugins/cache/decorator.ts
2855
+ function Cache(options = {}) {
2794
2856
  return Handler({
2795
- type: "action",
2796
- options
2857
+ type: "cache",
2858
+ options: {
2859
+ ttl: options.ttl ?? 6e4,
2860
+ // 默认1分钟
2861
+ key: options.key,
2862
+ // key 函数
2863
+ enabled: options.enabled ?? true
2864
+ }
2797
2865
  });
2798
2866
  }
2799
2867
 
2800
- // src/plugins/cache/adapter.ts
2801
- var MemoryCacheAdapter = class {
2802
- cache = /* @__PURE__ */ new Map();
2803
- async get(key) {
2804
- const item = this.cache.get(key);
2805
- if (!item) {
2806
- return null;
2807
- }
2808
- if (item.expiresAt <= Date.now()) {
2809
- this.cache.delete(key);
2810
- return null;
2811
- }
2812
- return item;
2813
- }
2814
- async set(key, item) {
2815
- this.cache.set(key, item);
2816
- }
2817
- async delete(key) {
2818
- return this.cache.delete(key);
2819
- }
2820
- async clear() {
2821
- this.cache.clear();
2868
+ // src/plugins/graceful-shutdown/plugin.ts
2869
+ var GracefulShutdownPlugin = class {
2870
+ name = "graceful-shutdown-plugin";
2871
+ priority = 50 /* SYSTEM */;
2872
+ // 系统级插件,最高优先级
2873
+ engine = null;
2874
+ options;
2875
+ // 正在执行的处理器计数
2876
+ activeHandlers = 0;
2877
+ // 是否正在停机
2878
+ isShuttingDown = false;
2879
+ // 停机超时定时器
2880
+ shutdownTimer = null;
2881
+ // 信号监听器(用于清理)
2882
+ signalListeners = /* @__PURE__ */ new Map();
2883
+ constructor(options) {
2884
+ this.options = {
2885
+ shutdownTimeout: options?.shutdownTimeout ?? 10 * 60 * 1e3,
2886
+ // 默认10分钟
2887
+ enabled: options?.enabled ?? true
2888
+ };
2822
2889
  }
2823
- async keys() {
2824
- const now = Date.now();
2825
- return Array.from(this.cache.entries()).filter(([_, item]) => item.expiresAt > now).map(([key]) => key);
2890
+ /**
2891
+ * 引擎初始化钩子
2892
+ */
2893
+ onInit(engine) {
2894
+ this.engine = engine;
2895
+ logger_default.info("GracefulShutdownPlugin initialized");
2826
2896
  }
2827
2897
  /**
2828
- * 清理所有过期项(高效批量清理)
2829
- * 这个方法比通过 keys() + get() + delete() 更高效
2898
+ * Handler加载钩子:拦截所有处理器,追踪执行状态
2830
2899
  */
2831
- async cleanupExpired() {
2832
- const now = Date.now();
2833
- let cleaned = 0;
2834
- for (const [key, item] of this.cache.entries()) {
2835
- if (item.expiresAt <= now) {
2836
- this.cache.delete(key);
2837
- cleaned++;
2838
- }
2900
+ onHandlerLoad(handlers) {
2901
+ if (!this.options.enabled) {
2902
+ return;
2839
2903
  }
2840
- return cleaned;
2841
- }
2842
- async getStats() {
2843
- const now = Date.now();
2844
- const validEntries = Array.from(this.cache.entries()).filter(([_, item]) => item.expiresAt > now).map(([key, item]) => ({
2845
- key,
2846
- expiresAt: item.expiresAt,
2847
- createdAt: item.createdAt
2848
- }));
2849
- return {
2850
- size: validEntries.length,
2851
- entries: validEntries
2852
- };
2853
- }
2854
- };
2855
- var RedisCacheAdapter = class {
2856
- client;
2857
- keyPrefix;
2858
- constructor(options) {
2859
- this.client = options.client;
2860
- this.keyPrefix = options.keyPrefix || "cache:";
2861
- }
2862
- getKey(key) {
2863
- return `${this.keyPrefix}${key}`;
2904
+ for (const handler of handlers) {
2905
+ handler.wrap(async (next, instance, ...args) => {
2906
+ if (this.isShuttingDown) {
2907
+ throw new Error("Service is shutting down, new requests are not accepted");
2908
+ }
2909
+ this.incrementActiveHandlers();
2910
+ try {
2911
+ const result = await next();
2912
+ return result;
2913
+ } catch (error) {
2914
+ throw error;
2915
+ } finally {
2916
+ this.decrementActiveHandlers();
2917
+ }
2918
+ });
2919
+ }
2920
+ logger_default.info(
2921
+ `GracefulShutdownPlugin: Tracking ${handlers.length} handler(s)`
2922
+ );
2864
2923
  }
2865
- async get(key) {
2866
- const redisKey = this.getKey(key);
2867
- const data = await this.client.get(redisKey);
2868
- if (!data) {
2869
- return null;
2924
+ /**
2925
+ * 引擎启动后钩子:注册系统信号监听器
2926
+ */
2927
+ async onAfterStart(engine) {
2928
+ if (!this.options.enabled) {
2929
+ return;
2870
2930
  }
2871
- try {
2872
- const item = JSON.parse(data);
2873
- if (item.expiresAt <= Date.now()) {
2874
- await this.delete(key);
2875
- return null;
2931
+ this.engine = engine;
2932
+ this.registerSignalHandlers();
2933
+ logger_default.info(
2934
+ `GracefulShutdownPlugin: Signal handlers registered, shutdown timeout: ${this.options.shutdownTimeout}ms`
2935
+ );
2936
+ }
2937
+ /**
2938
+ * 注册系统信号监听器
2939
+ */
2940
+ registerSignalHandlers() {
2941
+ const signals = [
2942
+ "SIGINT",
2943
+ // Ctrl+C (Unix/Linux/Mac)
2944
+ "SIGTERM",
2945
+ // 终止信号 (Unix/Linux/Mac)
2946
+ "SIGBREAK"
2947
+ // Ctrl+Break (Windows)
2948
+ ];
2949
+ for (const signal of signals) {
2950
+ if (process.platform === "win32" && signal === "SIGTERM") {
2951
+ continue;
2876
2952
  }
2877
- return item;
2878
- } catch (error) {
2879
- await this.delete(key);
2880
- return null;
2953
+ const handler = () => {
2954
+ logger_default.info(`GracefulShutdownPlugin: Received ${signal} signal`);
2955
+ this.initiateShutdown();
2956
+ };
2957
+ process.on(signal, handler);
2958
+ this.signalListeners.set(signal, handler);
2881
2959
  }
2882
2960
  }
2883
- async set(key, item) {
2884
- const redisKey = this.getKey(key);
2885
- const data = JSON.stringify(item);
2886
- const ttl = Math.max(0, Math.floor((item.expiresAt - Date.now()) / 1e3));
2887
- await this.client.set(redisKey, data, "EX", ttl);
2888
- }
2889
- async delete(key) {
2890
- const redisKey = this.getKey(key);
2891
- const result = await this.client.del(redisKey);
2892
- return result > 0;
2961
+ /**
2962
+ * 增加活跃处理器计数
2963
+ */
2964
+ incrementActiveHandlers() {
2965
+ this.activeHandlers++;
2966
+ logger_default.debug(
2967
+ `GracefulShutdownPlugin: Active handlers: ${this.activeHandlers}`
2968
+ );
2893
2969
  }
2894
- async clear() {
2895
- const pattern = `${this.keyPrefix}*`;
2896
- const keys = await this.client.keys(pattern);
2897
- if (keys.length > 0) {
2898
- await Promise.all(keys.map((key) => this.client.del(key)));
2970
+ /**
2971
+ * 减少活跃处理器计数
2972
+ */
2973
+ decrementActiveHandlers() {
2974
+ this.activeHandlers = Math.max(0, this.activeHandlers - 1);
2975
+ logger_default.debug(
2976
+ `GracefulShutdownPlugin: Active handlers: ${this.activeHandlers}`
2977
+ );
2978
+ if (this.isShuttingDown && this.activeHandlers === 0) {
2979
+ logger_default.info(
2980
+ "GracefulShutdownPlugin: All handlers completed, proceeding with shutdown"
2981
+ );
2982
+ this.completeShutdown();
2899
2983
  }
2900
2984
  }
2901
- async keys() {
2902
- const pattern = `${this.keyPrefix}*`;
2903
- const redisKeys = await this.client.keys(pattern);
2904
- return redisKeys.map((key) => key.replace(this.keyPrefix, ""));
2905
- }
2906
2985
  /**
2907
- * 清理所有过期项
2908
- *
2909
- * 注意:Redis 本身支持自动过期(通过 SET key value EX seconds),
2910
- * 过期键会被 Redis 自动删除。但在某些情况下(如测试环境、某些 Redis 客户端),
2911
- * 可能需要手动清理。此方法会检查并清理:
2912
- * 1. 已过期但尚未被 Redis 删除的键(双重保险)
2913
- * 2. JSON 解析失败的数据
2986
+ * 启动优雅停机流程
2914
2987
  */
2915
- async cleanupExpired() {
2916
- const pattern = `${this.keyPrefix}*`;
2917
- const redisKeys = await this.client.keys(pattern);
2918
- let cleaned = 0;
2919
- for (const redisKey of redisKeys) {
2920
- const data = await this.client.get(redisKey);
2921
- if (!data) {
2922
- continue;
2923
- }
2924
- try {
2925
- const item = JSON.parse(data);
2926
- if (item.expiresAt <= Date.now()) {
2927
- await this.client.del(redisKey);
2928
- cleaned++;
2929
- }
2930
- } catch {
2931
- await this.client.del(redisKey);
2932
- cleaned++;
2933
- }
2988
+ async initiateShutdown() {
2989
+ if (this.isShuttingDown) {
2990
+ return;
2991
+ }
2992
+ this.isShuttingDown = true;
2993
+ logger_default.info(
2994
+ `GracefulShutdownPlugin: Initiating graceful shutdown, waiting for ${this.activeHandlers} active handler(s) to complete`
2995
+ );
2996
+ if (this.activeHandlers === 0) {
2997
+ logger_default.info(
2998
+ "GracefulShutdownPlugin: No active handlers, proceeding with shutdown immediately"
2999
+ );
3000
+ await this.completeShutdown();
3001
+ return;
2934
3002
  }
2935
- return cleaned;
3003
+ this.shutdownTimer = setTimeout(() => {
3004
+ logger_default.warn(
3005
+ `GracefulShutdownPlugin: Shutdown timeout (${this.options.shutdownTimeout}ms) reached, forcing shutdown`
3006
+ );
3007
+ this.completeShutdown();
3008
+ }, this.options.shutdownTimeout);
2936
3009
  }
2937
- async getStats() {
2938
- const allKeys = await this.keys();
2939
- const entries = [];
2940
- for (const key of allKeys) {
2941
- const item = await this.get(key);
2942
- if (item) {
2943
- entries.push({
2944
- key,
2945
- expiresAt: item.expiresAt,
2946
- createdAt: item.createdAt
2947
- });
3010
+ /**
3011
+ * 完成停机流程
3012
+ */
3013
+ async completeShutdown() {
3014
+ if (this.shutdownTimer) {
3015
+ clearTimeout(this.shutdownTimer);
3016
+ this.shutdownTimer = null;
3017
+ }
3018
+ this.cleanupSignalHandlers();
3019
+ if (this.engine) {
3020
+ try {
3021
+ await this.engine.stop();
3022
+ logger_default.info("GracefulShutdownPlugin: Engine stopped successfully");
3023
+ } catch (error) {
3024
+ logger_default.error("GracefulShutdownPlugin: Failed to stop engine", error);
2948
3025
  }
2949
3026
  }
2950
- return {
2951
- size: entries.length,
2952
- entries
2953
- };
3027
+ logger_default.info("GracefulShutdownPlugin: Process exiting");
3028
+ process.exit(0);
2954
3029
  }
2955
- };
2956
-
2957
- // src/plugins/cache/plugin.ts
2958
- var CachePlugin = class {
2959
- name = "cache-plugin";
2960
- priority = 400 /* PERFORMANCE */;
2961
- // 性能优化插件,优先级较低
2962
- // 缓存适配器
2963
- adapter;
2964
- // 清理定时器
2965
- cleanupTimer = null;
2966
- // 引擎引用
2967
- engine = null;
2968
- // 模块配置
2969
- defaultTtl = 6e4;
2970
- // 默认1分钟
2971
- cacheEnabled = true;
2972
- cleanupInterval = 6e4;
2973
- // 默认1分钟清理一次
2974
3030
  /**
2975
- * 构造函数
2976
- * @param adapter 缓存适配器,如果不提供则使用默认的内存缓存适配器
3031
+ * 清理信号监听器
2977
3032
  */
2978
- constructor(adapter) {
2979
- this.adapter = adapter || new MemoryCacheAdapter();
3033
+ cleanupSignalHandlers() {
3034
+ for (const [signal, handler] of this.signalListeners.entries()) {
3035
+ process.removeListener(signal, handler);
3036
+ }
3037
+ this.signalListeners.clear();
2980
3038
  }
2981
3039
  /**
2982
- * 声明Module配置Schema
3040
+ * 引擎销毁钩子:清理资源
2983
3041
  */
2984
- getModuleOptionsSchema() {
2985
- return {
2986
- _type: {},
2987
- validate: (options) => {
2988
- if (options.cacheDefaultTtl !== void 0 && options.cacheDefaultTtl < 0) {
2989
- return `cacheDefaultTtl must be >= 0`;
2990
- }
2991
- if (options.cacheCleanupInterval !== void 0 && options.cacheCleanupInterval < 0) {
2992
- return `cacheCleanupInterval must be >= 0`;
2993
- }
2994
- return true;
2995
- }
2996
- };
3042
+ async onDestroy() {
3043
+ this.cleanupSignalHandlers();
3044
+ if (this.shutdownTimer) {
3045
+ clearTimeout(this.shutdownTimer);
3046
+ this.shutdownTimer = null;
3047
+ }
3048
+ logger_default.info("GracefulShutdownPlugin: Cleaned up");
2997
3049
  }
2998
3050
  /**
2999
- * 引擎初始化前钩子
3051
+ * 获取当前活跃处理器数量(用于测试和监控)
3000
3052
  */
3001
- onInit(engine) {
3002
- this.engine = engine;
3003
- logger_default.info("CachePlugin initialized cache storage");
3053
+ getActiveHandlersCount() {
3054
+ return this.activeHandlers;
3004
3055
  }
3005
3056
  /**
3006
- * Handler加载后钩子
3007
- * 拦截带有 type="cache" 的 Handler,包装原始方法实现缓存逻辑
3057
+ * 检查是否正在停机(用于测试和监控)
3008
3058
  */
3009
- onHandlerLoad(handlers) {
3010
- const cacheHandlers = handlers.filter(
3011
- (handler) => handler.type === "cache"
3012
- );
3013
- logger_default.info(`Found ${cacheHandlers.length} cache handler(s)`);
3014
- for (const handler of cacheHandlers) {
3015
- const methodName = handler.methodName;
3016
- const moduleClass = handler.module;
3017
- const cacheOptions = handler.options || {};
3018
- const moduleMetadata = this.engine?.getModules().find((m) => m.clazz === moduleClass);
3019
- if (!moduleMetadata) {
3020
- logger_default.warn(
3021
- `Module metadata not found for ${moduleClass.name}`
3022
- );
3023
- continue;
3024
- }
3025
- const moduleOptions = moduleMetadata.options;
3026
- const moduleDefaultTtl = moduleOptions?.cacheDefaultTtl ?? this.defaultTtl;
3027
- const moduleCacheEnabled = moduleOptions?.cacheEnabled ?? this.cacheEnabled;
3028
- const ttl = cacheOptions.ttl ?? moduleDefaultTtl;
3029
- const enabled = cacheOptions.enabled ?? moduleCacheEnabled;
3030
- if (!enabled) {
3031
- logger_default.info(
3032
- `Cache disabled for ${moduleClass.name}.${methodName}`
3033
- );
3034
- continue;
3035
- }
3036
- const moduleName = moduleMetadata.name;
3037
- handler.wrap(async (next, instance, ...args) => {
3038
- const cacheKey = this.generateCacheKey(
3039
- moduleName,
3040
- methodName,
3041
- cacheOptions.key,
3042
- args
3043
- );
3044
- const cached = await this.adapter.get(cacheKey);
3045
- if (cached) {
3046
- logger_default.debug(`Cache hit for ${cacheKey}`);
3047
- return cached.value;
3059
+ isShuttingDownNow() {
3060
+ return this.isShuttingDown;
3061
+ }
3062
+ };
3063
+
3064
+ // src/plugins/schedule/types.ts
3065
+ var ScheduleMode = /* @__PURE__ */ ((ScheduleMode2) => {
3066
+ ScheduleMode2["FIXED_RATE"] = "FIXED_RATE";
3067
+ ScheduleMode2["FIXED_DELAY"] = "FIXED_DELAY";
3068
+ return ScheduleMode2;
3069
+ })(ScheduleMode || {});
3070
+
3071
+ // src/plugins/schedule/decorator.ts
3072
+ function Schedule(options) {
3073
+ return Handler({
3074
+ type: "schedule",
3075
+ options: {
3076
+ interval: options.interval,
3077
+ mode: options.mode || "FIXED_RATE" /* FIXED_RATE */
3078
+ }
3079
+ });
3080
+ }
3081
+
3082
+ // src/plugins/schedule/mock-etcd.ts
3083
+ var MockEtcd3 = class {
3084
+ elections = /* @__PURE__ */ new Map();
3085
+ election(key, ttl) {
3086
+ if (!this.elections.has(key)) {
3087
+ this.elections.set(key, new MockElection(key));
3088
+ }
3089
+ return this.elections.get(key);
3090
+ }
3091
+ getElection(key) {
3092
+ return this.elections.get(key);
3093
+ }
3094
+ clearElections() {
3095
+ this.elections.clear();
3096
+ }
3097
+ };
3098
+ var MockElection = class {
3099
+ constructor(key) {
3100
+ this.key = key;
3101
+ }
3102
+ observers = [];
3103
+ campaigns = [];
3104
+ currentLeader = null;
3105
+ async observe() {
3106
+ const observer = new MockObserver(this);
3107
+ this.observers.push(observer);
3108
+ if (this.currentLeader) {
3109
+ setTimeout(() => {
3110
+ observer.notifyChange(this.currentLeader);
3111
+ }, 0);
3112
+ }
3113
+ return observer;
3114
+ }
3115
+ campaign(candidate) {
3116
+ const campaign = new MockCampaign(candidate, this);
3117
+ this.campaigns.push(campaign);
3118
+ if (!this.currentLeader) {
3119
+ setTimeout(() => {
3120
+ this.setLeader(candidate);
3121
+ }, 50);
3122
+ }
3123
+ return campaign;
3124
+ }
3125
+ setLeader(leader) {
3126
+ this.currentLeader = leader;
3127
+ for (const observer of this.observers) {
3128
+ observer.notifyChange(leader);
3129
+ }
3130
+ setTimeout(() => {
3131
+ for (const campaign of this.campaigns) {
3132
+ if (campaign.candidate === leader && !campaign.resigned) {
3133
+ campaign.notifyElected();
3048
3134
  }
3049
- logger_default.debug(`Cache miss for ${cacheKey}`);
3050
- const result = await next();
3051
- await this.adapter.set(cacheKey, {
3052
- value: result,
3053
- expiresAt: Date.now() + ttl,
3054
- createdAt: Date.now()
3055
- });
3056
- return result;
3057
- });
3058
- logger_default.info(
3059
- `Wrapped ${moduleClass.name}.${methodName} with cache (TTL: ${ttl}ms)`
3060
- );
3135
+ }
3136
+ }, 50);
3137
+ }
3138
+ getCurrentLeader() {
3139
+ return this.currentLeader;
3140
+ }
3141
+ };
3142
+ var MockObserver = class {
3143
+ constructor(election) {
3144
+ this.election = election;
3145
+ }
3146
+ changeCallbacks = [];
3147
+ on(event, callback) {
3148
+ if (event === "change") {
3149
+ this.changeCallbacks.push(callback);
3150
+ }
3151
+ }
3152
+ notifyChange(leader) {
3153
+ for (const callback of this.changeCallbacks) {
3154
+ callback(leader);
3155
+ }
3156
+ }
3157
+ };
3158
+ var MockCampaign = class {
3159
+ constructor(candidate, election) {
3160
+ this.candidate = candidate;
3161
+ this.election = election;
3162
+ }
3163
+ errorCallbacks = [];
3164
+ electedCallbacks = [];
3165
+ resigned = false;
3166
+ on(event, callback) {
3167
+ if (event === "error") {
3168
+ this.errorCallbacks.push(callback);
3169
+ } else if (event === "elected") {
3170
+ this.electedCallbacks.push(callback);
3061
3171
  }
3062
3172
  }
3063
- /**
3064
- * 引擎启动前钩子
3065
- */
3066
- onBeforeStart(engine) {
3067
- const moduleOptions = engine.options;
3068
- const cleanupInterval = moduleOptions?.cacheCleanupInterval ?? this.cleanupInterval;
3069
- if (cleanupInterval > 0) {
3070
- this.cleanupTimer = setInterval(() => {
3071
- this.cleanup().catch((error) => {
3072
- logger_default.error("Cache cleanup failed", error);
3073
- });
3074
- }, cleanupInterval);
3075
- logger_default.info(
3076
- `Started cache cleanup timer (interval: ${cleanupInterval}ms)`
3077
- );
3173
+ notifyElected() {
3174
+ if (!this.resigned) {
3175
+ for (const callback of this.electedCallbacks) {
3176
+ callback();
3177
+ }
3078
3178
  }
3079
3179
  }
3080
- /**
3081
- * 引擎停止时钩子
3082
- */
3083
- async onDestroy() {
3084
- if (this.cleanupTimer) {
3085
- clearInterval(this.cleanupTimer);
3086
- this.cleanupTimer = null;
3180
+ notifyError(error) {
3181
+ for (const callback of this.errorCallbacks) {
3182
+ callback(error);
3087
3183
  }
3088
- await this.adapter.clear();
3089
- logger_default.info("Cache storage cleared");
3090
3184
  }
3091
- /**
3092
- * 生成缓存键
3093
- * @param moduleName 模块名
3094
- * @param methodName 方法名
3095
- * @param keyFunction 可选的 key 函数
3096
- * @param args 方法参数
3097
- * @returns 缓存键(格式:模块名:方法名:hash)
3098
- */
3099
- generateCacheKey(moduleName, methodName, keyFunction, args) {
3100
- const keyData = keyFunction ? keyFunction(...args) : args;
3101
- const serialized = ejson.stringify(keyData);
3102
- const hash = createHash("sha256").update(serialized).digest("hex");
3103
- return `${moduleName}:${methodName}:${hash}`;
3185
+ async resign() {
3186
+ this.resigned = true;
3187
+ }
3188
+ };
3189
+
3190
+ // src/plugins/schedule/scheduler.ts
3191
+ var import_api = __toESM(require_src());
3192
+ var tracer = import_api.trace.getTracer("scheduler");
3193
+ var Scheduler = class {
3194
+ constructor(etcdClient) {
3195
+ this.etcdClient = etcdClient;
3104
3196
  }
3197
+ campaigns = /* @__PURE__ */ new Map();
3198
+ timers = /* @__PURE__ */ new Map();
3199
+ isLeader = /* @__PURE__ */ new Map();
3105
3200
  /**
3106
- * 清理过期缓存
3201
+ * 启动调度任务
3107
3202
  */
3108
- async cleanup() {
3109
- if (typeof this.adapter.cleanupExpired === "function") {
3110
- const cleaned = await this.adapter.cleanupExpired();
3111
- if (cleaned > 0) {
3112
- logger_default.debug(`Cleaned up ${cleaned} expired cache entry(ies)`);
3113
- }
3114
- } else {
3115
- const now = Date.now();
3116
- let cleaned = 0;
3117
- const keys = await this.adapter.keys();
3118
- for (const key of keys) {
3119
- const item = await this.adapter.get(key);
3120
- if (!item || item.expiresAt <= now) {
3121
- await this.adapter.delete(key);
3122
- cleaned++;
3123
- }
3124
- }
3125
- if (cleaned > 0) {
3126
- logger_default.debug(`Cleaned up ${cleaned} expired cache entry(ies)`);
3203
+ async startSchedule(serviceId, moduleName, methodName, electionKey, metadata, method) {
3204
+ const election = this.etcdClient.election(electionKey, 10);
3205
+ const observe = await election.observe();
3206
+ observe.on("change", (leader) => {
3207
+ const isLeader = leader === serviceId;
3208
+ this.isLeader.set(serviceId, isLeader);
3209
+ if (!isLeader) {
3210
+ this.stopTimer(serviceId);
3127
3211
  }
3128
- }
3212
+ });
3213
+ const campaign = election.campaign(serviceId);
3214
+ this.campaigns.set(serviceId, campaign);
3215
+ campaign.on("error", (error) => {
3216
+ logger_default.error(`Error in campaign for ${moduleName}.${methodName}:`, error);
3217
+ });
3218
+ campaign.on("elected", () => {
3219
+ this.isLeader.set(serviceId, true);
3220
+ this.startTimer(serviceId, metadata, moduleName, method);
3221
+ logger_default.info(`become leader for ${moduleName}.${methodName}`);
3222
+ });
3129
3223
  }
3130
3224
  /**
3131
- * 获取缓存统计信息
3225
+ * 启动定时器
3132
3226
  */
3133
- async getStats() {
3134
- return await this.adapter.getStats();
3227
+ startTimer(serviceId, metadata, moduleName, method) {
3228
+ this.stopTimer(serviceId);
3229
+ const wrappedMethod = async () => {
3230
+ tracer.startActiveSpan(
3231
+ `ScheduleTask ${moduleName}.${metadata.name}`,
3232
+ { root: true },
3233
+ async (span) => {
3234
+ span.setAttribute("serviceId", serviceId);
3235
+ span.setAttribute("methodName", metadata.name);
3236
+ span.setAttribute("moduleName", moduleName);
3237
+ span.setAttribute("interval", metadata.interval);
3238
+ span.setAttribute("mode", metadata.mode);
3239
+ try {
3240
+ await method();
3241
+ } catch (error) {
3242
+ span.setStatus({
3243
+ code: import_api.SpanStatusCode.ERROR,
3244
+ message: error.message
3245
+ });
3246
+ logger_default.error(
3247
+ `Error executing schedule task ${moduleName}.${metadata.name}:`,
3248
+ error
3249
+ );
3250
+ } finally {
3251
+ span.setStatus({ code: import_api.SpanStatusCode.OK });
3252
+ span.end();
3253
+ }
3254
+ }
3255
+ );
3256
+ };
3257
+ if (metadata.mode === "FIXED_DELAY" /* FIXED_DELAY */) {
3258
+ const runTask = async () => {
3259
+ if (!this.isLeader.get(serviceId)) return;
3260
+ try {
3261
+ await wrappedMethod();
3262
+ } finally {
3263
+ this.timers.set(serviceId, setTimeout(runTask, metadata.interval));
3264
+ }
3265
+ };
3266
+ runTask();
3267
+ } else {
3268
+ this.timers.set(
3269
+ serviceId,
3270
+ setInterval(async () => {
3271
+ if (!this.isLeader.get(serviceId)) return;
3272
+ await wrappedMethod();
3273
+ }, metadata.interval)
3274
+ );
3275
+ }
3135
3276
  }
3136
3277
  /**
3137
- * 清空所有缓存
3278
+ * 停止定时器
3138
3279
  */
3139
- async clear() {
3140
- await this.adapter.clear();
3141
- logger_default.info("All cache cleared");
3280
+ stopTimer(serviceId) {
3281
+ const timer = this.timers.get(serviceId);
3282
+ if (timer) {
3283
+ clearTimeout(timer);
3284
+ clearInterval(timer);
3285
+ this.timers.delete(serviceId);
3286
+ }
3142
3287
  }
3143
3288
  /**
3144
- * 删除指定的缓存项
3289
+ * 停止所有调度任务
3145
3290
  */
3146
- async delete(key) {
3147
- return await this.adapter.delete(key);
3291
+ async stop() {
3292
+ for (const serviceId of this.timers.keys()) {
3293
+ this.stopTimer(serviceId);
3294
+ }
3295
+ for (const [serviceId, campaign] of this.campaigns.entries()) {
3296
+ try {
3297
+ await campaign.resign().catch(() => {
3298
+ });
3299
+ } catch (error) {
3300
+ logger_default.error(`Error stopping schedule ${serviceId}:`, error);
3301
+ } finally {
3302
+ this.campaigns.delete(serviceId);
3303
+ this.isLeader.delete(serviceId);
3304
+ }
3305
+ }
3148
3306
  }
3149
3307
  };
3150
3308
 
3151
- // src/plugins/cache/decorator.ts
3152
- function Cache(options = {}) {
3153
- return Handler({
3154
- type: "cache",
3155
- options: {
3156
- ttl: options.ttl ?? 6e4,
3157
- // 默认1分钟
3158
- key: options.key,
3159
- // key 函数
3160
- enabled: options.enabled ?? true
3309
+ // src/plugins/schedule/utils.ts
3310
+ function extractScheduleMetadata(handlers) {
3311
+ const result = /* @__PURE__ */ new Map();
3312
+ const scheduleHandlers = handlers.filter(
3313
+ (handler) => handler.type === "schedule"
3314
+ );
3315
+ for (const handler of scheduleHandlers) {
3316
+ const moduleClass = handler.module;
3317
+ const methodName = handler.methodName;
3318
+ const options = handler.options || {};
3319
+ if (!result.has(moduleClass)) {
3320
+ result.set(moduleClass, /* @__PURE__ */ new Map());
3161
3321
  }
3162
- });
3322
+ const moduleMetadata = result.get(moduleClass);
3323
+ moduleMetadata.set(methodName, {
3324
+ name: methodName,
3325
+ interval: options.interval,
3326
+ mode: options.mode || "FIXED_RATE" /* FIXED_RATE */
3327
+ });
3328
+ }
3329
+ return result;
3163
3330
  }
3164
3331
 
3165
- // src/plugins/graceful-shutdown/plugin.ts
3166
- var GracefulShutdownPlugin = class {
3167
- name = "graceful-shutdown-plugin";
3168
- priority = 50 /* SYSTEM */;
3169
- // 系统级插件,最高优先级
3170
- engine = null;
3171
- options;
3172
- // 正在执行的处理器计数
3173
- activeHandlers = 0;
3174
- // 是否正在停机
3175
- isShuttingDown = false;
3176
- // 停机超时定时器
3177
- shutdownTimer = null;
3178
- // 信号监听器(用于清理)
3179
- signalListeners = /* @__PURE__ */ new Map();
3332
+ // src/plugins/schedule/plugin.ts
3333
+ var SchedulePlugin = class {
3334
+ name = "schedule-plugin";
3335
+ priority = 300 /* BUSINESS */;
3336
+ // 业务逻辑优先级
3337
+ engine;
3338
+ scheduler = null;
3339
+ etcdClient = null;
3340
+ scheduleHandlers = [];
3341
+ useMockEtcd = false;
3180
3342
  constructor(options) {
3181
- this.options = {
3182
- shutdownTimeout: options?.shutdownTimeout ?? 10 * 60 * 1e3,
3183
- // 默认10分钟
3184
- enabled: options?.enabled ?? true
3185
- };
3343
+ if (options?.useMockEtcd) {
3344
+ this.useMockEtcd = true;
3345
+ this.etcdClient = new MockEtcd3();
3346
+ logger_default.info("SchedulePlugin: Using MockEtcd3 for local development/testing");
3347
+ } else if (options?.etcdClient) {
3348
+ this.etcdClient = options.etcdClient;
3349
+ }
3186
3350
  }
3187
3351
  /**
3188
3352
  * 引擎初始化钩子
3189
3353
  */
3190
3354
  onInit(engine) {
3191
3355
  this.engine = engine;
3192
- logger_default.info("GracefulShutdownPlugin initialized");
3356
+ logger_default.info("SchedulePlugin initialized");
3193
3357
  }
3194
3358
  /**
3195
- * Handler加载钩子:拦截所有处理器,追踪执行状态
3359
+ * Handler加载钩子:收集所有调度任务
3196
3360
  */
3197
3361
  onHandlerLoad(handlers) {
3198
- if (!this.options.enabled) {
3199
- return;
3200
- }
3201
- for (const handler of handlers) {
3202
- handler.wrap(async (next, instance, ...args) => {
3203
- if (this.isShuttingDown) {
3204
- throw new Error("Service is shutting down, new requests are not accepted");
3205
- }
3206
- this.incrementActiveHandlers();
3207
- try {
3208
- const result = await next();
3209
- return result;
3210
- } catch (error) {
3211
- throw error;
3212
- } finally {
3213
- this.decrementActiveHandlers();
3214
- }
3215
- });
3216
- }
3362
+ this.scheduleHandlers = handlers.filter(
3363
+ (handler) => handler.type === "schedule"
3364
+ );
3217
3365
  logger_default.info(
3218
- `GracefulShutdownPlugin: Tracking ${handlers.length} handler(s)`
3366
+ `SchedulePlugin: Found ${this.scheduleHandlers.length} schedule handler(s)`
3219
3367
  );
3220
3368
  }
3221
3369
  /**
3222
- * 引擎启动后钩子:注册系统信号监听器
3370
+ * 引擎启动后钩子:启动所有调度任务
3223
3371
  */
3224
3372
  async onAfterStart(engine) {
3225
- if (!this.options.enabled) {
3373
+ if (!this.etcdClient) {
3374
+ logger_default.warn(
3375
+ "SchedulePlugin: etcdClient not configured and useMockEtcd is false, schedule tasks will not start"
3376
+ );
3226
3377
  return;
3227
3378
  }
3228
- this.engine = engine;
3229
- this.registerSignalHandlers();
3230
- logger_default.info(
3231
- `GracefulShutdownPlugin: Signal handlers registered, shutdown timeout: ${this.options.shutdownTimeout}ms`
3232
- );
3233
- }
3234
- /**
3235
- * 注册系统信号监听器
3236
- */
3237
- registerSignalHandlers() {
3238
- const signals = [
3239
- "SIGINT",
3240
- // Ctrl+C (Unix/Linux/Mac)
3241
- "SIGTERM",
3242
- // 终止信号 (Unix/Linux/Mac)
3243
- "SIGBREAK"
3244
- // Ctrl+Break (Windows)
3245
- ];
3246
- for (const signal of signals) {
3247
- if (process.platform === "win32" && signal === "SIGTERM") {
3379
+ this.scheduler = new Scheduler(this.etcdClient);
3380
+ const modules = engine.getModules();
3381
+ if (!this.scheduleHandlers) {
3382
+ logger_default.warn(
3383
+ "SchedulePlugin: No schedule handlers found, schedule tasks will not start"
3384
+ );
3385
+ return;
3386
+ }
3387
+ const scheduleMetadataMap = extractScheduleMetadata(this.scheduleHandlers);
3388
+ const serviceId = `${engine.options.name}-${Date.now()}-${Math.random()}`;
3389
+ for (const moduleMetadata of modules) {
3390
+ const moduleClass = moduleMetadata.clazz;
3391
+ const moduleName = moduleMetadata.name;
3392
+ const moduleInstance = engine.get(moduleClass);
3393
+ const moduleScheduleMetadata = scheduleMetadataMap.get(moduleClass);
3394
+ if (!moduleScheduleMetadata || moduleScheduleMetadata.size === 0) {
3248
3395
  continue;
3249
3396
  }
3250
- const handler = () => {
3251
- logger_default.info(`GracefulShutdownPlugin: Received ${signal} signal`);
3252
- this.initiateShutdown();
3253
- };
3254
- process.on(signal, handler);
3255
- this.signalListeners.set(signal, handler);
3397
+ for (const [methodName, metadata] of moduleScheduleMetadata.entries()) {
3398
+ const method = moduleInstance[methodName];
3399
+ if (typeof method !== "function") {
3400
+ logger_default.warn(
3401
+ `SchedulePlugin: Method ${moduleName}.${methodName} is not a function, skipping`
3402
+ );
3403
+ continue;
3404
+ }
3405
+ const electionKey = `/schedule/${engine.options.name}/${moduleName}/${methodName}`;
3406
+ const taskServiceId = `${serviceId}-${moduleName}-${methodName}`;
3407
+ try {
3408
+ await this.scheduler.startSchedule(
3409
+ taskServiceId,
3410
+ moduleName,
3411
+ methodName,
3412
+ electionKey,
3413
+ metadata,
3414
+ method.bind(moduleInstance)
3415
+ );
3416
+ logger_default.info(
3417
+ `SchedulePlugin: Started schedule task ${moduleName}.${methodName} (interval: ${metadata.interval}ms, mode: ${metadata.mode})`
3418
+ );
3419
+ } catch (error) {
3420
+ logger_default.error(
3421
+ `SchedulePlugin: Failed to start schedule task ${moduleName}.${methodName}:`,
3422
+ error
3423
+ );
3424
+ }
3425
+ }
3256
3426
  }
3257
3427
  }
3258
3428
  /**
3259
- * 增加活跃处理器计数
3260
- */
3261
- incrementActiveHandlers() {
3262
- this.activeHandlers++;
3263
- logger_default.debug(
3264
- `GracefulShutdownPlugin: Active handlers: ${this.activeHandlers}`
3265
- );
3266
- }
3267
- /**
3268
- * 减少活跃处理器计数
3429
+ * 引擎销毁钩子:停止所有调度任务
3269
3430
  */
3270
- decrementActiveHandlers() {
3271
- this.activeHandlers = Math.max(0, this.activeHandlers - 1);
3272
- logger_default.debug(
3273
- `GracefulShutdownPlugin: Active handlers: ${this.activeHandlers}`
3274
- );
3275
- if (this.isShuttingDown && this.activeHandlers === 0) {
3276
- logger_default.info(
3277
- "GracefulShutdownPlugin: All handlers completed, proceeding with shutdown"
3278
- );
3279
- this.completeShutdown();
3431
+ async onDestroy() {
3432
+ if (this.scheduler) {
3433
+ try {
3434
+ await this.scheduler.stop();
3435
+ logger_default.info("SchedulePlugin: All schedule tasks stopped");
3436
+ } catch (error) {
3437
+ logger_default.error("SchedulePlugin: Error stopping schedule tasks:", error);
3438
+ }
3280
3439
  }
3281
3440
  }
3282
- /**
3283
- * 启动优雅停机流程
3284
- */
3285
- async initiateShutdown() {
3286
- if (this.isShuttingDown) {
3287
- return;
3441
+ };
3442
+ async function formatCode(code) {
3443
+ try {
3444
+ return prettier.format(code, { parser: "typescript" });
3445
+ } catch {
3446
+ return code;
3447
+ }
3448
+ }
3449
+
3450
+ // src/plugins/client-code/generator.ts
3451
+ function getZodTypeString(schema, defaultOptional = false) {
3452
+ function processType(type) {
3453
+ if (!type) {
3454
+ return "unknown";
3288
3455
  }
3289
- this.isShuttingDown = true;
3290
- logger_default.info(
3291
- `GracefulShutdownPlugin: Initiating graceful shutdown, waiting for ${this.activeHandlers} active handler(s) to complete`
3292
- );
3293
- if (this.activeHandlers === 0) {
3294
- logger_default.info(
3295
- "GracefulShutdownPlugin: No active handlers, proceeding with shutdown immediately"
3296
- );
3297
- await this.completeShutdown();
3298
- return;
3456
+ const def = type._def;
3457
+ const typeName = def.type;
3458
+ if (typeName === "nullable") {
3459
+ return `${processType(def.innerType)} | null`;
3299
3460
  }
3300
- this.shutdownTimer = setTimeout(() => {
3301
- logger_default.warn(
3302
- `GracefulShutdownPlugin: Shutdown timeout (${this.options.shutdownTimeout}ms) reached, forcing shutdown`
3303
- );
3304
- this.completeShutdown();
3305
- }, this.options.shutdownTimeout);
3306
- }
3307
- /**
3308
- * 完成停机流程
3309
- */
3310
- async completeShutdown() {
3311
- if (this.shutdownTimer) {
3312
- clearTimeout(this.shutdownTimer);
3313
- this.shutdownTimer = null;
3461
+ if (typeName === "optional") {
3462
+ return processType(def.innerType);
3314
3463
  }
3315
- this.cleanupSignalHandlers();
3316
- if (this.engine) {
3317
- try {
3318
- await this.engine.stop();
3319
- logger_default.info("GracefulShutdownPlugin: Engine stopped successfully");
3320
- } catch (error) {
3321
- logger_default.error("GracefulShutdownPlugin: Failed to stop engine", error);
3464
+ if (typeName === "pipe" && def.in) {
3465
+ return processType(def.in);
3466
+ }
3467
+ if (typeName === "effects" && def.schema) {
3468
+ return processType(def.schema);
3469
+ }
3470
+ switch (typeName) {
3471
+ case "string": {
3472
+ return "string";
3473
+ }
3474
+ case "number": {
3475
+ return "number";
3476
+ }
3477
+ case "bigint": {
3478
+ return "bigint";
3479
+ }
3480
+ case "boolean": {
3481
+ return "boolean";
3482
+ }
3483
+ case "array": {
3484
+ const elementType = processType(def.element);
3485
+ return `${elementType}[]`;
3486
+ }
3487
+ case "date": {
3488
+ return "Date";
3489
+ }
3490
+ case "object": {
3491
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
3492
+ const props = Object.entries(shape).map(([key, value]) => {
3493
+ if (key.includes("-")) {
3494
+ key = `'${key}'`;
3495
+ }
3496
+ const fieldDef = value._def;
3497
+ const fieldTypeName = fieldDef.type;
3498
+ const isOptional = fieldTypeName === "optional";
3499
+ const isDefault = defaultOptional && fieldTypeName === "default";
3500
+ const fieldType = processType(
3501
+ isOptional ? fieldDef.innerType : value
3502
+ );
3503
+ return `${key}${isOptional || isDefault ? "?" : ""}: ${fieldType}`;
3504
+ }).join("; ");
3505
+ return `{ ${props} }`;
3506
+ }
3507
+ case "union": {
3508
+ return def.options.map((opt) => processType(opt)).join(" | ");
3509
+ }
3510
+ case "null": {
3511
+ return "null";
3512
+ }
3513
+ case "promise": {
3514
+ return `Promise<${processType(def.type)}>`;
3515
+ }
3516
+ case "void": {
3517
+ return "void";
3518
+ }
3519
+ case "record": {
3520
+ if (def.valueType) {
3521
+ const keyType = def.keyType ? processType(def.keyType) : "string";
3522
+ return `Record<${keyType}, ${processType(def.valueType)}>`;
3523
+ } else if (def.keyType) {
3524
+ return `Record<string, ${processType(def.keyType)}>`;
3525
+ }
3526
+ return "Record<string, any>";
3527
+ }
3528
+ case "map": {
3529
+ return `Map<${processType(def.keyType)}, ${processType(
3530
+ def.valueType
3531
+ )}>`;
3532
+ }
3533
+ case "any": {
3534
+ return "any";
3535
+ }
3536
+ case "unknown": {
3537
+ return "unknown";
3538
+ }
3539
+ case "enum": {
3540
+ const values = def.entries ? Object.values(def.entries) : [];
3541
+ return "(" + values.map((opt) => `"${String(opt)}"`).join(" | ") + ")";
3542
+ }
3543
+ case "default": {
3544
+ return processType(def.innerType);
3545
+ }
3546
+ default: {
3547
+ if (type.safeParse(new Uint8Array()).success) {
3548
+ return "Uint8Array";
3549
+ }
3550
+ return "unknown";
3322
3551
  }
3323
3552
  }
3324
- logger_default.info("GracefulShutdownPlugin: Process exiting");
3325
- process.exit(0);
3326
- }
3327
- /**
3328
- * 清理信号监听器
3329
- */
3330
- cleanupSignalHandlers() {
3331
- for (const [signal, handler] of this.signalListeners.entries()) {
3332
- process.removeListener(signal, handler);
3333
- }
3334
- this.signalListeners.clear();
3335
3553
  }
3336
- /**
3337
- * 引擎销毁钩子:清理资源
3338
- */
3339
- async onDestroy() {
3340
- this.cleanupSignalHandlers();
3341
- if (this.shutdownTimer) {
3342
- clearTimeout(this.shutdownTimer);
3343
- this.shutdownTimer = null;
3344
- }
3345
- logger_default.info("GracefulShutdownPlugin: Cleaned up");
3554
+ return processType(schema);
3555
+ }
3556
+ async function generateClientCode(modules) {
3557
+ const imports = [
3558
+ "// \u8FD9\u4E2A\u6587\u4EF6\u662F\u81EA\u52A8\u751F\u6210\u7684\uFF0C\u8BF7\u4E0D\u8981\u624B\u52A8\u4FEE\u6539",
3559
+ "",
3560
+ 'import { MicroserviceClient as BaseMicroserviceClient } from "imean-service-client";',
3561
+ 'export * from "imean-service-client";',
3562
+ ""
3563
+ ].join("\n");
3564
+ function toPascalCase(moduleName) {
3565
+ return moduleName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
3346
3566
  }
3347
- /**
3348
- * 获取当前活跃处理器数量(用于测试和监控)
3349
- */
3350
- getActiveHandlersCount() {
3351
- return this.activeHandlers;
3567
+ function toCamelCase(moduleName) {
3568
+ const parts = moduleName.split("-");
3569
+ return parts[0] + parts.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
3352
3570
  }
3353
- /**
3354
- * 检查是否正在停机(用于测试和监控)
3355
- */
3356
- isShuttingDownNow() {
3357
- return this.isShuttingDown;
3571
+ const interfaces = Object.entries(modules).map(([name, module]) => {
3572
+ const methods = Object.entries(module.actions).map(([actionName, action]) => {
3573
+ if (!action.params) {
3574
+ throw new Error(`Missing params for action ${actionName}`);
3575
+ }
3576
+ const paramNames = action.paramNames || [];
3577
+ const params = action.params.map((param, index) => {
3578
+ const paramName = paramNames[index] || param.description || `arg${index}`;
3579
+ const paramDef = param._def;
3580
+ const isOptional = param.isOptional();
3581
+ const hasDefault = paramDef?.type === "default";
3582
+ return `${paramName}${isOptional || hasDefault ? "?" : ""}: ${getZodTypeString(
3583
+ param,
3584
+ true
3585
+ )}`;
3586
+ }).join(", ");
3587
+ const returnType = action.returns ? getZodTypeString(action.returns) : "void";
3588
+ return `
3589
+ /**
3590
+ * ${action.description || ""}
3591
+ */
3592
+ ${actionName}: (${params}) => Promise<${action.stream ? `AsyncIterable<${returnType}>` : returnType}>;`;
3593
+ }).join("\n ");
3594
+ const interfaceName = `${toPascalCase(name)}Module`;
3595
+ return `export interface ${interfaceName} {
3596
+ ${methods}
3597
+ }`;
3598
+ }).join("\n\n");
3599
+ const clientClass = `export class MicroserviceClient extends BaseMicroserviceClient {
3600
+ constructor(options: any) {
3601
+ super(options);
3358
3602
  }
3359
- };
3360
-
3361
- // src/plugins/schedule/types.ts
3362
- var ScheduleMode = /* @__PURE__ */ ((ScheduleMode2) => {
3363
- ScheduleMode2["FIXED_RATE"] = "FIXED_RATE";
3364
- ScheduleMode2["FIXED_DELAY"] = "FIXED_DELAY";
3365
- return ScheduleMode2;
3366
- })(ScheduleMode || {});
3367
3603
 
3368
- // src/plugins/schedule/decorator.ts
3369
- function Schedule(options) {
3370
- return Handler({
3371
- type: "schedule",
3372
- options: {
3373
- interval: options.interval,
3374
- mode: options.mode || "FIXED_RATE" /* FIXED_RATE */
3375
- }
3376
- });
3604
+ ${Object.entries(modules).map(([name, module]) => {
3605
+ const methods = Object.entries(module.actions).map(([actionName, action]) => {
3606
+ return `${actionName}: { idempotent: ${!!action.idempotence}, stream: ${!!action.stream} }`;
3607
+ }).join(",\n ");
3608
+ const propertyName = toCamelCase(name);
3609
+ const interfaceName = `${toPascalCase(name)}Module`;
3610
+ return `public readonly ${propertyName} = this.registerModule<${interfaceName}>("${name}", {
3611
+ ${methods}
3612
+ });`;
3613
+ }).join("\n\n ")}
3614
+ }`;
3615
+ return await formatCode([imports, interfaces, clientClass].join("\n\n"));
3377
3616
  }
3378
3617
 
3379
- // src/plugins/schedule/mock-etcd.ts
3380
- var MockEtcd3 = class {
3381
- elections = /* @__PURE__ */ new Map();
3382
- election(key, ttl) {
3383
- if (!this.elections.has(key)) {
3384
- this.elections.set(key, new MockElection(key));
3385
- }
3386
- return this.elections.get(key);
3387
- }
3388
- getElection(key) {
3389
- return this.elections.get(key);
3390
- }
3391
- clearElections() {
3392
- this.elections.clear();
3393
- }
3394
- };
3395
- var MockElection = class {
3396
- constructor(key) {
3397
- this.key = key;
3618
+ // src/plugins/client-code/utils.ts
3619
+ function extractParamNames(func) {
3620
+ if (!func || typeof func !== "function") {
3621
+ return [];
3398
3622
  }
3399
- observers = [];
3400
- campaigns = [];
3401
- currentLeader = null;
3402
- async observe() {
3403
- const observer = new MockObserver(this);
3404
- this.observers.push(observer);
3405
- if (this.currentLeader) {
3406
- setTimeout(() => {
3407
- observer.notifyChange(this.currentLeader);
3408
- }, 0);
3623
+ try {
3624
+ const funcStr = func.toString();
3625
+ const match = funcStr.match(
3626
+ /(?:async\s+)?(?:function\s+\w*\s*)?\(([^)]*)\)|(?:async\s+)?\(([^)]*)\)\s*=>/
3627
+ );
3628
+ if (!match) {
3629
+ return [];
3409
3630
  }
3410
- return observer;
3411
- }
3412
- campaign(candidate) {
3413
- const campaign = new MockCampaign(candidate, this);
3414
- this.campaigns.push(campaign);
3415
- if (!this.currentLeader) {
3416
- setTimeout(() => {
3417
- this.setLeader(candidate);
3418
- }, 50);
3631
+ const paramsStr = match[1] || match[2] || "";
3632
+ if (!paramsStr.trim()) {
3633
+ return [];
3419
3634
  }
3420
- return campaign;
3635
+ return paramsStr.split(",").map((param) => {
3636
+ return param.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*$/g, "").replace(/:\s*[^=,]+/g, "").replace(/\s*=\s*[^,]+/g, "").trim();
3637
+ }).filter((name) => name.length > 0);
3638
+ } catch (error) {
3639
+ return [];
3421
3640
  }
3422
- setLeader(leader) {
3423
- this.currentLeader = leader;
3424
- for (const observer of this.observers) {
3425
- observer.notifyChange(leader);
3641
+ }
3642
+ function convertHandlersToModuleInfo(handlers) {
3643
+ const modules = {};
3644
+ const actionHandlers = handlers.filter(
3645
+ (handler) => handler.type === "action"
3646
+ );
3647
+ for (const handler of actionHandlers) {
3648
+ const actionOptions = handler.options;
3649
+ const moduleClass = handler.module;
3650
+ const methodName = handler.methodName;
3651
+ const moduleName = moduleClass.name.toLowerCase().replace(/service$/, "");
3652
+ if (!modules[moduleName]) {
3653
+ modules[moduleName] = {
3654
+ name: moduleName,
3655
+ actions: {}
3656
+ };
3426
3657
  }
3427
- setTimeout(() => {
3428
- for (const campaign of this.campaigns) {
3429
- if (campaign.candidate === leader && !campaign.resigned) {
3430
- campaign.notifyElected();
3431
- }
3432
- }
3433
- }, 50);
3434
- }
3435
- getCurrentLeader() {
3436
- return this.currentLeader;
3437
- }
3438
- };
3439
- var MockObserver = class {
3440
- constructor(election) {
3441
- this.election = election;
3658
+ const paramNames = extractParamNames(handler.method);
3659
+ modules[moduleName].actions[methodName] = {
3660
+ description: actionOptions.description,
3661
+ params: actionOptions.params || [],
3662
+ returns: actionOptions.returns,
3663
+ stream: actionOptions.stream || false,
3664
+ idempotence: actionOptions.idempotence || false,
3665
+ paramNames
3666
+ };
3442
3667
  }
3443
- changeCallbacks = [];
3444
- on(event, callback) {
3445
- if (event === "change") {
3446
- this.changeCallbacks.push(callback);
3668
+ return modules;
3669
+ }
3670
+ function convertHandlersToModuleInfoWithMetadata(handlers, getModuleMetadata) {
3671
+ const modules = {};
3672
+ const actionHandlers = handlers.filter(
3673
+ (handler) => handler.type === "action"
3674
+ );
3675
+ for (const handler of actionHandlers) {
3676
+ const actionOptions = handler.options;
3677
+ const moduleClass = handler.module;
3678
+ const methodName = handler.methodName;
3679
+ const moduleMetadata = getModuleMetadata(moduleClass);
3680
+ if (!moduleMetadata) {
3681
+ continue;
3682
+ }
3683
+ const moduleName = moduleMetadata.name;
3684
+ if (!modules[moduleName]) {
3685
+ modules[moduleName] = {
3686
+ name: moduleName,
3687
+ actions: {}
3688
+ };
3447
3689
  }
3690
+ const paramNames = extractParamNames(handler.method);
3691
+ modules[moduleName].actions[methodName] = {
3692
+ description: actionOptions.description,
3693
+ params: actionOptions.params || [],
3694
+ returns: actionOptions.returns,
3695
+ stream: actionOptions.stream || false,
3696
+ idempotence: actionOptions.idempotence || false,
3697
+ paramNames
3698
+ };
3448
3699
  }
3449
- notifyChange(leader) {
3450
- for (const callback of this.changeCallbacks) {
3451
- callback(leader);
3452
- }
3700
+ return modules;
3701
+ }
3702
+
3703
+ // src/plugins/client-code/plugin.ts
3704
+ var ClientCodePlugin = class {
3705
+ name = "client-code-plugin";
3706
+ priority = 1e3 /* ROUTE */;
3707
+ // 路由插件优先级,在 ActionPlugin 之后
3708
+ engine;
3709
+ actionHandlers = [];
3710
+ generatedCode = null;
3711
+ clientSavePath;
3712
+ constructor(options) {
3713
+ this.clientSavePath = options?.clientSavePath;
3453
3714
  }
3454
- };
3455
- var MockCampaign = class {
3456
- constructor(candidate, election) {
3457
- this.candidate = candidate;
3458
- this.election = election;
3715
+ /**
3716
+ * 引擎初始化钩子
3717
+ */
3718
+ onInit(engine) {
3719
+ this.engine = engine;
3720
+ logger_default.info("ClientCodePlugin initialized");
3459
3721
  }
3460
- errorCallbacks = [];
3461
- electedCallbacks = [];
3462
- resigned = false;
3463
- on(event, callback) {
3464
- if (event === "error") {
3465
- this.errorCallbacks.push(callback);
3466
- } else if (event === "elected") {
3467
- this.electedCallbacks.push(callback);
3468
- }
3722
+ /**
3723
+ * Handler加载钩子:收集所有 Action handlers
3724
+ */
3725
+ onHandlerLoad(handlers) {
3726
+ const actionHandlers = handlers.filter(
3727
+ (handler) => handler.type === "action"
3728
+ );
3729
+ this.actionHandlers = actionHandlers;
3730
+ logger_default.info(
3731
+ `ClientCodePlugin collected ${actionHandlers.length} action handler(s)`
3732
+ );
3469
3733
  }
3470
- notifyElected() {
3471
- if (!this.resigned) {
3472
- for (const callback of this.electedCallbacks) {
3473
- callback();
3734
+ /**
3735
+ * 引擎启动后钩子:注册客户端代码下载路由
3736
+ */
3737
+ async onAfterStart(engine) {
3738
+ await this.generateCode();
3739
+ const prefix = engine.options.prefix || "";
3740
+ const clientPath = prefix ? `${prefix}/client.ts` : "/client.ts";
3741
+ const hono = engine.getHono();
3742
+ hono.get(clientPath, async (ctx) => {
3743
+ if (!this.generatedCode) {
3744
+ await this.generateCode();
3474
3745
  }
3475
- }
3746
+ return ctx.text(this.generatedCode || "", 200, {
3747
+ "Content-Type": "text/typescript; charset=utf-8",
3748
+ "Content-Disposition": `attachment; filename="client.ts"`
3749
+ });
3750
+ });
3751
+ logger_default.info(`Client code available at ${clientPath}`);
3476
3752
  }
3477
- notifyError(error) {
3478
- for (const callback of this.errorCallbacks) {
3479
- callback(error);
3753
+ /**
3754
+ * 生成客户端代码
3755
+ */
3756
+ async generateCode() {
3757
+ try {
3758
+ const getModuleMetadata = (moduleClass) => {
3759
+ const modules2 = this.engine.getModules();
3760
+ const moduleMetadata = modules2.find((m) => m.clazz === moduleClass);
3761
+ return moduleMetadata ? { name: moduleMetadata.name } : void 0;
3762
+ };
3763
+ const modules = convertHandlersToModuleInfoWithMetadata(
3764
+ this.actionHandlers,
3765
+ getModuleMetadata
3766
+ );
3767
+ this.generatedCode = await generateClientCode(modules);
3768
+ logger_default.debug(
3769
+ `Generated client code for ${Object.keys(modules).length} module(s)`
3770
+ );
3771
+ if (this.clientSavePath && this.generatedCode) {
3772
+ await this.saveCodeToFile(this.clientSavePath, this.generatedCode);
3773
+ }
3774
+ } catch (error) {
3775
+ logger_default.error("Failed to generate client code", error);
3776
+ this.generatedCode = "// Error: Failed to generate client code";
3480
3777
  }
3481
3778
  }
3482
- async resign() {
3483
- this.resigned = true;
3779
+ /**
3780
+ * 保存代码到文件
3781
+ */
3782
+ async saveCodeToFile(path, code) {
3783
+ try {
3784
+ const dir = dirname(path);
3785
+ await promises.mkdir(dir, { recursive: true });
3786
+ await promises.writeFile(path, code, "utf-8");
3787
+ logger_default.info(`Client code saved to ${path}`);
3788
+ } catch (error) {
3789
+ logger_default.error(`Failed to save client code to ${path}`, error);
3790
+ }
3484
3791
  }
3485
3792
  };
3486
-
3487
- // src/plugins/schedule/scheduler.ts
3488
- var import_api = __toESM(require_src());
3489
- var tracer = import_api.trace.getTracer("scheduler");
3490
- var Scheduler = class {
3491
- constructor(etcdClient) {
3492
- this.etcdClient = etcdClient;
3793
+ var Microservice3 = class {
3794
+ // 存储已注册的模块
3795
+ modules = [];
3796
+ // 存储已注册的插件(按注册顺序)
3797
+ plugins = [];
3798
+ // 插件注册表(以name为键,用于覆盖逻辑)
3799
+ pluginRegistry = /* @__PURE__ */ new Map();
3800
+ // 模块实例缓存(单例管理)
3801
+ moduleInstances = /* @__PURE__ */ new Map();
3802
+ // 引擎配置(冻结,只读)
3803
+ options;
3804
+ // 是否已启动
3805
+ started = false;
3806
+ // 模块元数据键(用于通过双向访问查找模块)
3807
+ moduleMetadataKey;
3808
+ // 实际使用的端口(启动后设置)
3809
+ actualPort = null;
3810
+ // Hono 实例(由引擎统一管理)
3811
+ hono = new Hono();
3812
+ // HTTP 服务器实例
3813
+ server = null;
3814
+ constructor(options, moduleMetadataKey) {
3815
+ this.options = Object.freeze({
3816
+ hostname: "0.0.0.0",
3817
+ ...options
3818
+ });
3819
+ this.moduleMetadataKey = moduleMetadataKey;
3493
3820
  }
3494
- campaigns = /* @__PURE__ */ new Map();
3495
- timers = /* @__PURE__ */ new Map();
3496
- isLeader = /* @__PURE__ */ new Map();
3497
3821
  /**
3498
- * 启动调度任务
3822
+ * 获取 Hono 实例(供插件使用)
3499
3823
  */
3500
- async startSchedule(serviceId, moduleName, methodName, electionKey, metadata, method) {
3501
- const election = this.etcdClient.election(electionKey, 10);
3502
- const observe = await election.observe();
3503
- observe.on("change", (leader) => {
3504
- const isLeader = leader === serviceId;
3505
- this.isLeader.set(serviceId, isLeader);
3506
- if (!isLeader) {
3507
- this.stopTimer(serviceId);
3824
+ getHono() {
3825
+ return this.hono;
3826
+ }
3827
+ /**
3828
+ * 内部注册插件方法(供构造函数和子类使用)
3829
+ * @protected
3830
+ */
3831
+ registerPlugin(plugin) {
3832
+ if (!plugin.name || plugin.name.trim() === "") {
3833
+ throw new PluginNameRequiredError();
3834
+ }
3835
+ const existingPlugin = this.pluginRegistry.get(plugin.name);
3836
+ if (existingPlugin) {
3837
+ const oldIndex = this.plugins.indexOf(existingPlugin);
3838
+ if (oldIndex >= 0) {
3839
+ this.plugins.splice(oldIndex, 1);
3508
3840
  }
3509
- });
3510
- const campaign = election.campaign(serviceId);
3511
- this.campaigns.set(serviceId, campaign);
3512
- campaign.on("error", (error) => {
3513
- logger_default.error(`Error in campaign for ${moduleName}.${methodName}:`, error);
3514
- });
3515
- campaign.on("elected", () => {
3516
- this.isLeader.set(serviceId, true);
3517
- this.startTimer(serviceId, metadata, moduleName, method);
3518
- logger_default.info(`become leader for ${moduleName}.${methodName}`);
3519
- });
3841
+ logger_default.info(`Override plugin: ${plugin.name}`);
3842
+ }
3843
+ this.pluginRegistry.set(plugin.name, plugin);
3844
+ this.plugins.push(plugin);
3520
3845
  }
3521
3846
  /**
3522
- * 启动定时器
3847
+ * 加载并注册所有模块(通过唯一的 key 查找)
3848
+ * 在引擎启动时调用,使用双向访问机制查找所有被装饰的类
3849
+ *
3850
+ * 注意:
3851
+ * - 每个引擎实例使用唯一的 moduleMetadataKey,实现隔离
3852
+ * - 模块类是静态的,可以被多个引擎实例共享
3853
+ * - 每个引擎实例会创建独立的模块实例,互不影响
3523
3854
  */
3524
- startTimer(serviceId, metadata, moduleName, method) {
3525
- this.stopTimer(serviceId);
3526
- const wrappedMethod = async () => {
3527
- tracer.startActiveSpan(
3528
- `ScheduleTask ${moduleName}.${metadata.name}`,
3529
- { root: true },
3530
- async (span) => {
3531
- span.setAttribute("serviceId", serviceId);
3532
- span.setAttribute("methodName", metadata.name);
3533
- span.setAttribute("moduleName", moduleName);
3534
- span.setAttribute("interval", metadata.interval);
3535
- span.setAttribute("mode", metadata.mode);
3536
- try {
3537
- await method();
3538
- } catch (error) {
3539
- span.setStatus({
3540
- code: import_api.SpanStatusCode.ERROR,
3541
- message: error.message
3542
- });
3543
- logger_default.error(
3544
- `Error executing schedule task ${moduleName}.${metadata.name}:`,
3545
- error
3546
- );
3547
- } finally {
3548
- span.setStatus({ code: import_api.SpanStatusCode.OK });
3549
- span.end();
3550
- }
3551
- }
3552
- );
3553
- };
3554
- if (metadata.mode === "FIXED_DELAY" /* FIXED_DELAY */) {
3555
- const runTask = async () => {
3556
- if (!this.isLeader.get(serviceId)) return;
3557
- try {
3558
- await wrappedMethod();
3559
- } finally {
3560
- this.timers.set(serviceId, setTimeout(runTask, metadata.interval));
3561
- }
3855
+ loadModules() {
3856
+ this.modules = [];
3857
+ const moduleClasses = getClassesByKey(this.moduleMetadataKey);
3858
+ for (const moduleClass of moduleClasses) {
3859
+ const metadata = getClassMetadata(moduleClass, this.moduleMetadataKey);
3860
+ const moduleName = metadata.name || moduleClass.name;
3861
+ const existingModule = this.modules.find((m) => m.name === moduleName);
3862
+ if (existingModule) {
3863
+ throw new DuplicateModuleError(moduleName);
3864
+ }
3865
+ const moduleMetadata = {
3866
+ name: moduleName,
3867
+ clazz: moduleClass,
3868
+ options: metadata.options || {}
3562
3869
  };
3563
- runTask();
3564
- } else {
3565
- this.timers.set(
3566
- serviceId,
3567
- setInterval(async () => {
3568
- if (!this.isLeader.get(serviceId)) return;
3569
- await wrappedMethod();
3570
- }, metadata.interval)
3571
- );
3870
+ this.modules.push(moduleMetadata);
3572
3871
  }
3573
3872
  }
3574
3873
  /**
3575
- * 停止定时器
3874
+ * 加载Handler元数据(平铺结构)
3875
+ * 每个装饰器都是独立的HandlerMetadata条目
3876
+ * 为每个方法创建包装链管理器,提供简单的 wrap API
3576
3877
  */
3577
- stopTimer(serviceId) {
3578
- const timer = this.timers.get(serviceId);
3579
- if (timer) {
3580
- clearTimeout(timer);
3581
- clearInterval(timer);
3582
- this.timers.delete(serviceId);
3878
+ loadHandlerMetadata() {
3879
+ const handlers = [];
3880
+ const wrapperChains = /* @__PURE__ */ new Map();
3881
+ const originalMethods = /* @__PURE__ */ new Map();
3882
+ for (const module of this.modules) {
3883
+ if (!this.moduleInstances.has(module.clazz)) {
3884
+ this.get(module.clazz);
3885
+ }
3886
+ const allMetadata = getAllHandlerMetadata(module.clazz);
3887
+ for (const [methodName, metadataList] of allMetadata.entries()) {
3888
+ const method = module.clazz.prototype[methodName];
3889
+ const methodNameStr = String(methodName);
3890
+ const methodKey = `${module.clazz.name}.${methodNameStr}`;
3891
+ if (!originalMethods.has(methodKey)) {
3892
+ originalMethods.set(methodKey, method);
3893
+ wrapperChains.set(methodKey, []);
3894
+ }
3895
+ for (const meta of metadataList) {
3896
+ const chain = wrapperChains.get(methodKey);
3897
+ handlers.push({
3898
+ ...meta,
3899
+ method: originalMethods.get(methodKey),
3900
+ methodName: methodNameStr,
3901
+ module: module.clazz,
3902
+ // 提供简单的 wrap API:插件只需要调用这个方法
3903
+ wrap: (wrapper) => {
3904
+ chain.push(wrapper);
3905
+ }
3906
+ });
3907
+ }
3908
+ }
3583
3909
  }
3910
+ this.__wrapperChains__ = wrapperChains;
3911
+ this.__originalMethods__ = originalMethods;
3912
+ return handlers;
3584
3913
  }
3585
3914
  /**
3586
- * 停止所有调度任务
3915
+ * 应用所有包装链到原型
3916
+ * 在所有插件执行完 onHandlerLoad 后调用
3587
3917
  */
3588
- async stop() {
3589
- for (const serviceId of this.timers.keys()) {
3590
- this.stopTimer(serviceId);
3591
- }
3592
- for (const [serviceId, campaign] of this.campaigns.entries()) {
3593
- try {
3594
- await campaign.resign().catch(() => {
3595
- });
3596
- } catch (error) {
3597
- logger_default.error(`Error stopping schedule ${serviceId}:`, error);
3598
- } finally {
3599
- this.campaigns.delete(serviceId);
3600
- this.isLeader.delete(serviceId);
3918
+ applyWrapperChains() {
3919
+ const wrapperChains = this.__wrapperChains__;
3920
+ const originalMethods = this.__originalMethods__;
3921
+ if (!wrapperChains || !originalMethods) return;
3922
+ for (const [methodKey, chain] of wrapperChains.entries()) {
3923
+ if (chain.length === 0) continue;
3924
+ const [moduleName, methodName] = methodKey.split(".");
3925
+ const module = this.modules.find((m) => m.clazz.name === moduleName);
3926
+ if (!module) continue;
3927
+ const originalMethod = originalMethods.get(methodKey);
3928
+ const prototype = module.clazz.prototype;
3929
+ let wrappedMethod = originalMethod;
3930
+ for (let i = chain.length - 1; i >= 0; i--) {
3931
+ const wrapper = chain[i];
3932
+ const next = wrappedMethod;
3933
+ wrappedMethod = async function(...args) {
3934
+ return wrapper(() => next.apply(this, args), this, ...args);
3935
+ };
3601
3936
  }
3937
+ prototype[methodName] = wrappedMethod;
3602
3938
  }
3603
3939
  }
3604
- };
3605
-
3606
- // src/plugins/schedule/utils.ts
3607
- function extractScheduleMetadata(handlers) {
3608
- const result = /* @__PURE__ */ new Map();
3609
- const scheduleHandlers = handlers.filter(
3610
- (handler) => handler.type === "schedule"
3611
- );
3612
- for (const handler of scheduleHandlers) {
3613
- const moduleClass = handler.module;
3614
- const methodName = handler.methodName;
3615
- const options = handler.options || {};
3616
- if (!result.has(moduleClass)) {
3617
- result.set(moduleClass, /* @__PURE__ */ new Map());
3618
- }
3619
- const moduleMetadata = result.get(moduleClass);
3620
- moduleMetadata.set(methodName, {
3621
- name: methodName,
3622
- interval: options.interval,
3623
- mode: options.mode || "FIXED_RATE" /* FIXED_RATE */
3940
+ /**
3941
+ * 寻找一个随机的可用端口
3942
+ */
3943
+ getRandomPort(hostname) {
3944
+ return new Promise((resolve, reject) => {
3945
+ const server = net.createServer();
3946
+ server.unref();
3947
+ server.on("error", reject);
3948
+ server.listen(0, hostname, () => {
3949
+ const port = server.address().port;
3950
+ server.close((err) => {
3951
+ if (err) {
3952
+ return reject(err);
3953
+ }
3954
+ resolve(port);
3955
+ });
3956
+ });
3624
3957
  });
3625
3958
  }
3626
- return result;
3627
- }
3628
-
3629
- // src/plugins/schedule/plugin.ts
3630
- var SchedulePlugin = class {
3631
- name = "schedule-plugin";
3632
- priority = 300 /* BUSINESS */;
3633
- // 业务逻辑优先级
3634
- engine;
3635
- scheduler = null;
3636
- etcdClient = null;
3637
- scheduleHandlers = [];
3638
- useMockEtcd = false;
3639
- constructor(options) {
3640
- if (options?.useMockEtcd) {
3641
- this.useMockEtcd = true;
3642
- this.etcdClient = new MockEtcd3();
3643
- logger_default.info("SchedulePlugin: Using MockEtcd3 for local development/testing");
3644
- } else if (options?.etcdClient) {
3645
- this.etcdClient = options.etcdClient;
3646
- }
3647
- }
3648
3959
  /**
3649
- * 引擎初始化钩子
3960
+ * 启动引擎
3961
+ * @param requestedPort 启动端口(可选,默认0,表示随机端口)
3962
+ * @returns 实际使用的端口号
3650
3963
  */
3651
- onInit(engine) {
3652
- this.engine = engine;
3653
- logger_default.info("SchedulePlugin initialized");
3964
+ async start(requestedPort = 0) {
3965
+ if (this.started) {
3966
+ throw new Error("Engine is already started");
3967
+ }
3968
+ try {
3969
+ const { port, hostname } = await this.determinePort(requestedPort);
3970
+ this.initializeModulesAndPlugins();
3971
+ this.processHandlers();
3972
+ await this.executePluginStartHooks();
3973
+ this.startHttpServer(port, hostname);
3974
+ this.started = true;
3975
+ logger_default.info(
3976
+ `${this.options.name} v${this.options.version} started successfully on port ${this.actualPort}`
3977
+ );
3978
+ return this.actualPort;
3979
+ } catch (error) {
3980
+ logger_default.error("Failed to start engine", error);
3981
+ throw error;
3982
+ }
3654
3983
  }
3655
3984
  /**
3656
- * Handler加载钩子:收集所有调度任务
3985
+ * 确定实际使用的端口
3657
3986
  */
3658
- onHandlerLoad(handlers) {
3659
- this.scheduleHandlers = handlers.filter(
3660
- (handler) => handler.type === "schedule"
3661
- );
3662
- logger_default.info(
3663
- `SchedulePlugin: Found ${this.scheduleHandlers.length} schedule handler(s)`
3664
- );
3987
+ async determinePort(requestedPort) {
3988
+ const hostname = this.options.hostname || "0.0.0.0";
3989
+ if (requestedPort !== 0) {
3990
+ this.actualPort = requestedPort;
3991
+ return { port: this.actualPort, hostname };
3992
+ }
3993
+ this.actualPort = await this.getRandomPort(hostname);
3994
+ return { port: this.actualPort, hostname };
3665
3995
  }
3666
3996
  /**
3667
- * 引擎启动后钩子:启动所有调度任务
3997
+ * 初始化模块和插件
3668
3998
  */
3669
- async onAfterStart(engine) {
3670
- if (!this.etcdClient) {
3671
- logger_default.warn(
3672
- "SchedulePlugin: etcdClient not configured and useMockEtcd is false, schedule tasks will not start"
3673
- );
3674
- return;
3675
- }
3676
- this.scheduler = new Scheduler(this.etcdClient);
3677
- const modules = engine.getModules();
3678
- if (!this.scheduleHandlers) {
3679
- logger_default.warn(
3680
- "SchedulePlugin: No schedule handlers found, schedule tasks will not start"
3681
- );
3682
- return;
3683
- }
3684
- const scheduleMetadataMap = extractScheduleMetadata(this.scheduleHandlers);
3685
- const serviceId = `${engine.options.name}-${Date.now()}-${Math.random()}`;
3686
- for (const moduleMetadata of modules) {
3687
- const moduleClass = moduleMetadata.clazz;
3688
- const moduleName = moduleMetadata.name;
3689
- const moduleInstance = engine.get(moduleClass);
3690
- const moduleScheduleMetadata = scheduleMetadataMap.get(moduleClass);
3691
- if (!moduleScheduleMetadata || moduleScheduleMetadata.size === 0) {
3692
- continue;
3693
- }
3694
- for (const [methodName, metadata] of moduleScheduleMetadata.entries()) {
3695
- const method = moduleInstance[methodName];
3696
- if (typeof method !== "function") {
3697
- logger_default.warn(
3698
- `SchedulePlugin: Method ${moduleName}.${methodName} is not a function, skipping`
3699
- );
3700
- continue;
3701
- }
3702
- const electionKey = `/schedule/${engine.options.name}/${moduleName}/${methodName}`;
3703
- const taskServiceId = `${serviceId}-${moduleName}-${methodName}`;
3704
- try {
3705
- await this.scheduler.startSchedule(
3706
- taskServiceId,
3707
- moduleName,
3708
- methodName,
3709
- electionKey,
3710
- metadata,
3711
- method.bind(moduleInstance)
3712
- );
3713
- logger_default.info(
3714
- `SchedulePlugin: Started schedule task ${moduleName}.${methodName} (interval: ${metadata.interval}ms, mode: ${metadata.mode})`
3715
- );
3716
- } catch (error) {
3717
- logger_default.error(
3718
- `SchedulePlugin: Failed to start schedule task ${moduleName}.${methodName}:`,
3719
- error
3720
- );
3721
- }
3722
- }
3723
- }
3999
+ initializeModulesAndPlugins() {
4000
+ this.loadModules();
4001
+ this.executePluginHook("onInit", (plugin) => {
4002
+ plugin.onInit?.(this);
4003
+ });
4004
+ this.executePluginHook("onModuleLoad", (plugin) => {
4005
+ plugin.onModuleLoad?.(
4006
+ this.modules
4007
+ );
4008
+ });
3724
4009
  }
3725
4010
  /**
3726
- * 引擎销毁钩子:停止所有调度任务
4011
+ * 处理 Handler 元数据和插件包装
3727
4012
  */
3728
- async onDestroy() {
3729
- if (this.scheduler) {
4013
+ processHandlers() {
4014
+ const handlers = this.loadHandlerMetadata();
4015
+ const sortedPlugins = this.sortPluginsByPriority();
4016
+ const { wrapperPlugins, routePlugins } = this.separateWrapperAndRoutePlugins(sortedPlugins);
4017
+ this.executePluginHook(
4018
+ "onHandlerLoad",
4019
+ (plugin) => {
4020
+ plugin.onHandlerLoad?.(handlers);
4021
+ },
4022
+ wrapperPlugins
4023
+ );
4024
+ this.applyWrapperChains();
4025
+ this.executePluginHook(
4026
+ "onHandlerLoad",
4027
+ (plugin) => {
4028
+ plugin.onHandlerLoad?.(handlers);
4029
+ },
4030
+ routePlugins
4031
+ );
4032
+ }
4033
+ /**
4034
+ * 执行插件启动钩子
4035
+ */
4036
+ async executePluginStartHooks() {
4037
+ this.executePluginHook("onBeforeStart", (plugin) => {
4038
+ plugin.onBeforeStart?.(this);
4039
+ });
4040
+ for (const plugin of this.plugins) {
3730
4041
  try {
3731
- await this.scheduler.stop();
3732
- logger_default.info("SchedulePlugin: All schedule tasks stopped");
4042
+ await plugin.onAfterStart?.(this);
3733
4043
  } catch (error) {
3734
- logger_default.error("SchedulePlugin: Error stopping schedule tasks:", error);
4044
+ throw new Error(
4045
+ `Plugin ${plugin.name} failed in onAfterStart: ${error instanceof Error ? error.message : String(error)}`
4046
+ );
3735
4047
  }
3736
4048
  }
4049
+ this.registerVersionRoute();
3737
4050
  }
3738
- };
3739
-
3740
- // src/plugins/client-code/utils.ts
3741
- function extractParamNames(func) {
3742
- if (!func || typeof func !== "function") {
3743
- return [];
3744
- }
3745
- try {
3746
- const funcStr = func.toString();
3747
- const match = funcStr.match(
3748
- /(?:async\s+)?(?:function\s+\w*\s*)?\(([^)]*)\)|(?:async\s+)?\(([^)]*)\)\s*=>/
4051
+ /**
4052
+ * 注册版本路由(/prefix/version)
4053
+ * 用于健康检查和探针
4054
+ */
4055
+ registerVersionRoute() {
4056
+ const prefix = this.options.prefix || "";
4057
+ const versionPath = prefix ? `${prefix}` : "/";
4058
+ const existingRoutes = this.hono.routes;
4059
+ const routeExists = existingRoutes.some(
4060
+ (route) => route.path === versionPath && route.method === "GET"
3749
4061
  );
3750
- if (!match) {
3751
- return [];
3752
- }
3753
- const paramsStr = match[1] || match[2] || "";
3754
- if (!paramsStr.trim()) {
3755
- return [];
3756
- }
3757
- return paramsStr.split(",").map((param) => {
3758
- return param.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*$/g, "").replace(/:\s*[^=,]+/g, "").replace(/\s*=\s*[^,]+/g, "").trim();
3759
- }).filter((name) => name.length > 0);
3760
- } catch (error) {
3761
- return [];
3762
- }
3763
- }
3764
- function convertHandlersToModuleInfo(handlers) {
3765
- const modules = {};
3766
- const actionHandlers = handlers.filter(
3767
- (handler) => handler.type === "action"
3768
- );
3769
- for (const handler of actionHandlers) {
3770
- const actionOptions = handler.options;
3771
- const moduleClass = handler.module;
3772
- const methodName = handler.methodName;
3773
- const moduleName = moduleClass.name.toLowerCase().replace(/service$/, "");
3774
- if (!modules[moduleName]) {
3775
- modules[moduleName] = {
3776
- name: moduleName,
3777
- actions: {}
3778
- };
3779
- }
3780
- const paramNames = extractParamNames(handler.method);
3781
- modules[moduleName].actions[methodName] = {
3782
- description: actionOptions.description,
3783
- params: actionOptions.params || [],
3784
- returns: actionOptions.returns,
3785
- stream: actionOptions.stream || false,
3786
- idempotence: actionOptions.idempotence || false,
3787
- paramNames
3788
- };
3789
- }
3790
- return modules;
3791
- }
3792
- function convertHandlersToModuleInfoWithMetadata(handlers, getModuleMetadata) {
3793
- const modules = {};
3794
- const actionHandlers = handlers.filter(
3795
- (handler) => handler.type === "action"
3796
- );
3797
- for (const handler of actionHandlers) {
3798
- const actionOptions = handler.options;
3799
- const moduleClass = handler.module;
3800
- const methodName = handler.methodName;
3801
- const moduleMetadata = getModuleMetadata(moduleClass);
3802
- if (!moduleMetadata) {
3803
- continue;
3804
- }
3805
- const moduleName = moduleMetadata.name;
3806
- if (!modules[moduleName]) {
3807
- modules[moduleName] = {
3808
- name: moduleName,
3809
- actions: {}
3810
- };
3811
- }
3812
- const paramNames = extractParamNames(handler.method);
3813
- modules[moduleName].actions[methodName] = {
3814
- description: actionOptions.description,
3815
- params: actionOptions.params || [],
3816
- returns: actionOptions.returns,
3817
- stream: actionOptions.stream || false,
3818
- idempotence: actionOptions.idempotence || false,
3819
- paramNames
3820
- };
3821
- }
3822
- return modules;
3823
- }
3824
- async function formatCode(code) {
3825
- try {
3826
- return prettier.format(code, { parser: "typescript" });
3827
- } catch {
3828
- return code;
3829
- }
3830
- }
3831
-
3832
- // src/plugins/client-code/generator.ts
3833
- function getZodTypeString(schema, defaultOptional = false) {
3834
- function processType(type) {
3835
- if (!type) {
3836
- return "unknown";
3837
- }
3838
- const def = type._def;
3839
- const typeName = def.type;
3840
- if (typeName === "nullable") {
3841
- return `${processType(def.innerType)} | null`;
3842
- }
3843
- if (typeName === "optional") {
3844
- return processType(def.innerType);
3845
- }
3846
- if (typeName === "pipe" && def.in) {
3847
- return processType(def.in);
3848
- }
3849
- if (typeName === "effects" && def.schema) {
3850
- return processType(def.schema);
3851
- }
3852
- switch (typeName) {
3853
- case "string": {
3854
- return "string";
3855
- }
3856
- case "number": {
3857
- return "number";
3858
- }
3859
- case "bigint": {
3860
- return "bigint";
3861
- }
3862
- case "boolean": {
3863
- return "boolean";
3864
- }
3865
- case "array": {
3866
- const elementType = processType(def.element);
3867
- return `${elementType}[]`;
3868
- }
3869
- case "date": {
3870
- return "Date";
3871
- }
3872
- case "object": {
3873
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
3874
- const props = Object.entries(shape).map(([key, value]) => {
3875
- if (key.includes("-")) {
3876
- key = `'${key}'`;
3877
- }
3878
- const fieldDef = value._def;
3879
- const fieldTypeName = fieldDef.type;
3880
- const isOptional = fieldTypeName === "optional";
3881
- const isDefault = defaultOptional && fieldTypeName === "default";
3882
- const fieldType = processType(
3883
- isOptional ? fieldDef.innerType : value
3884
- );
3885
- return `${key}${isOptional || isDefault ? "?" : ""}: ${fieldType}`;
3886
- }).join("; ");
3887
- return `{ ${props} }`;
3888
- }
3889
- case "union": {
3890
- return def.options.map((opt) => processType(opt)).join(" | ");
3891
- }
3892
- case "null": {
3893
- return "null";
3894
- }
3895
- case "promise": {
3896
- return `Promise<${processType(def.type)}>`;
3897
- }
3898
- case "void": {
3899
- return "void";
3900
- }
3901
- case "record": {
3902
- if (def.valueType) {
3903
- const keyType = def.keyType ? processType(def.keyType) : "string";
3904
- return `Record<${keyType}, ${processType(def.valueType)}>`;
3905
- } else if (def.keyType) {
3906
- return `Record<string, ${processType(def.keyType)}>`;
3907
- }
3908
- return "Record<string, any>";
3909
- }
3910
- case "map": {
3911
- return `Map<${processType(def.keyType)}, ${processType(
3912
- def.valueType
3913
- )}>`;
3914
- }
3915
- case "any": {
3916
- return "any";
3917
- }
3918
- case "unknown": {
3919
- return "unknown";
3920
- }
3921
- case "enum": {
3922
- const values = def.entries ? Object.values(def.entries) : [];
3923
- return "(" + values.map((opt) => `"${String(opt)}"`).join(" | ") + ")";
3924
- }
3925
- case "default": {
3926
- return processType(def.innerType);
3927
- }
3928
- default: {
3929
- if (type.safeParse(new Uint8Array()).success) {
3930
- return "Uint8Array";
3931
- }
3932
- return "unknown";
4062
+ if (routeExists) {
4063
+ logger_default.info(
4064
+ `Version route ${versionPath} already exists, skipping registration`
4065
+ );
4066
+ return;
4067
+ }
4068
+ try {
4069
+ this.hono.get(versionPath, async (ctx) => {
4070
+ return ctx.json({
4071
+ name: this.options.name,
4072
+ version: this.options.version,
4073
+ status: "running"
4074
+ });
4075
+ });
4076
+ logger_default.info(`Registered version route: GET ${versionPath}`);
4077
+ } catch (error) {
4078
+ if (error instanceof Error && error.message.includes("matcher is already built")) {
4079
+ logger_default.warn(
4080
+ `Cannot register version route ${versionPath}: route matcher is already built. If you have already registered a route at ${versionPath}, it will be used instead.`
4081
+ );
4082
+ } else {
4083
+ throw error;
3933
4084
  }
3934
4085
  }
3935
4086
  }
3936
- return processType(schema);
3937
- }
3938
- async function generateClientCode(modules) {
3939
- const imports = [
3940
- "// \u8FD9\u4E2A\u6587\u4EF6\u662F\u81EA\u52A8\u751F\u6210\u7684\uFF0C\u8BF7\u4E0D\u8981\u624B\u52A8\u4FEE\u6539",
3941
- "",
3942
- 'import { MicroserviceClient as BaseMicroserviceClient } from "imean-service-client";',
3943
- 'export * from "imean-service-client";',
3944
- ""
3945
- ].join("\n");
3946
- function toPascalCase(moduleName) {
3947
- return moduleName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
3948
- }
3949
- function toCamelCase(moduleName) {
3950
- const parts = moduleName.split("-");
3951
- return parts[0] + parts.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
4087
+ /**
4088
+ * 启动 HTTP 服务器
4089
+ */
4090
+ startHttpServer(port, hostname) {
4091
+ this.server = serve({
4092
+ fetch: this.hono.fetch,
4093
+ port,
4094
+ hostname
4095
+ });
4096
+ logger_default.info(`HTTP server started on http://${hostname}:${port}`);
3952
4097
  }
3953
- const interfaces = Object.entries(modules).map(([name, module]) => {
3954
- const methods = Object.entries(module.actions).map(([actionName, action]) => {
3955
- if (!action.params) {
3956
- throw new Error(`Missing params for action ${actionName}`);
4098
+ /**
4099
+ * 按优先级排序插件
4100
+ */
4101
+ sortPluginsByPriority() {
4102
+ return [...this.plugins].sort((a, b) => {
4103
+ const priorityA = this.getPluginPriority(a);
4104
+ const priorityB = this.getPluginPriority(b);
4105
+ if (priorityA !== priorityB) {
4106
+ return priorityA - priorityB;
3957
4107
  }
3958
- const paramNames = action.paramNames || [];
3959
- const params = action.params.map((param, index) => {
3960
- const paramName = paramNames[index] || param.description || `arg${index}`;
3961
- const paramDef = param._def;
3962
- const isOptional = param.isOptional();
3963
- const hasDefault = paramDef?.type === "default";
3964
- return `${paramName}${isOptional || hasDefault ? "?" : ""}: ${getZodTypeString(
3965
- param,
3966
- true
3967
- )}`;
3968
- }).join(", ");
3969
- const returnType = action.returns ? getZodTypeString(action.returns) : "void";
3970
- return `
3971
- /**
3972
- * ${action.description || ""}
3973
- */
3974
- ${actionName}: (${params}) => Promise<${action.stream ? `AsyncIterable<${returnType}>` : returnType}>;`;
3975
- }).join("\n ");
3976
- const interfaceName = `${toPascalCase(name)}Module`;
3977
- return `export interface ${interfaceName} {
3978
- ${methods}
3979
- }`;
3980
- }).join("\n\n");
3981
- const clientClass = `export class MicroserviceClient extends BaseMicroserviceClient {
3982
- constructor(options: any) {
3983
- super(options);
3984
- }
3985
-
3986
- ${Object.entries(modules).map(([name, module]) => {
3987
- const methods = Object.entries(module.actions).map(([actionName, action]) => {
3988
- return `${actionName}: { idempotent: ${!!action.idempotence}, stream: ${!!action.stream} }`;
3989
- }).join(",\n ");
3990
- const propertyName = toCamelCase(name);
3991
- const interfaceName = `${toPascalCase(name)}Module`;
3992
- return `public readonly ${propertyName} = this.registerModule<${interfaceName}>("${name}", {
3993
- ${methods}
3994
- });`;
3995
- }).join("\n\n ")}
3996
- }`;
3997
- return await formatCode([imports, interfaces, clientClass].join("\n\n"));
3998
- }
3999
-
4000
- // src/plugins/client-code/plugin.ts
4001
- var ClientCodePlugin = class {
4002
- name = "client-code-plugin";
4003
- priority = 1e3 /* ROUTE */;
4004
- // 路由插件优先级,在 ActionPlugin 之后
4005
- engine;
4006
- actionHandlers = [];
4007
- generatedCode = null;
4008
- clientSavePath;
4009
- constructor(options) {
4010
- this.clientSavePath = options?.clientSavePath;
4108
+ const indexA = this.plugins.indexOf(a);
4109
+ const indexB = this.plugins.indexOf(b);
4110
+ return indexA - indexB;
4111
+ });
4011
4112
  }
4012
4113
  /**
4013
- * 引擎初始化钩子
4114
+ * 获取插件优先级
4014
4115
  */
4015
- onInit(engine) {
4016
- this.engine = engine;
4017
- logger_default.info("ClientCodePlugin initialized");
4116
+ getPluginPriority(plugin) {
4117
+ return plugin.priority ?? 300 /* BUSINESS */;
4018
4118
  }
4019
4119
  /**
4020
- * Handler加载钩子:收集所有 Action handlers
4120
+ * 分离包装插件和路由插件
4021
4121
  */
4022
- onHandlerLoad(handlers) {
4023
- const actionHandlers = handlers.filter(
4024
- (handler) => handler.type === "action"
4025
- );
4026
- this.actionHandlers = actionHandlers;
4027
- logger_default.info(
4028
- `ClientCodePlugin collected ${actionHandlers.length} action handler(s)`
4029
- );
4122
+ separateWrapperAndRoutePlugins(sortedPlugins) {
4123
+ const wrapperPlugins = [];
4124
+ const routePlugins = [];
4125
+ for (const plugin of sortedPlugins) {
4126
+ const priority = this.getPluginPriority(plugin);
4127
+ if (priority === 1e3 /* ROUTE */) {
4128
+ routePlugins.push(plugin);
4129
+ } else {
4130
+ wrapperPlugins.push(plugin);
4131
+ }
4132
+ }
4133
+ return { wrapperPlugins, routePlugins };
4030
4134
  }
4031
4135
  /**
4032
- * 引擎启动后钩子:注册客户端代码下载路由
4136
+ * 执行插件钩子(通用方法)
4033
4137
  */
4034
- async onAfterStart(engine) {
4035
- await this.generateCode();
4036
- const prefix = engine.options.prefix || "";
4037
- const clientPath = prefix ? `${prefix}/client.ts` : "/client.ts";
4038
- const hono = engine.getHono();
4039
- hono.get(clientPath, async (ctx) => {
4040
- if (!this.generatedCode) {
4041
- await this.generateCode();
4138
+ executePluginHook(hookName, callback, plugins = this.plugins) {
4139
+ for (const plugin of plugins) {
4140
+ try {
4141
+ callback(plugin);
4142
+ } catch (error) {
4143
+ throw new Error(
4144
+ `Plugin ${plugin.name} failed in ${hookName}: ${error instanceof Error ? error.message : String(error)}`
4145
+ );
4042
4146
  }
4043
- return ctx.text(this.generatedCode || "", 200, {
4044
- "Content-Type": "text/typescript; charset=utf-8",
4045
- "Content-Disposition": `attachment; filename="client.ts"`
4046
- });
4047
- });
4048
- logger_default.info(`Client code available at ${clientPath}`);
4147
+ }
4049
4148
  }
4050
4149
  /**
4051
- * 生成客户端代码
4150
+ * 停止引擎
4052
4151
  */
4053
- async generateCode() {
4152
+ async stop() {
4153
+ if (!this.started) {
4154
+ return;
4155
+ }
4054
4156
  try {
4055
- const getModuleMetadata = (moduleClass) => {
4056
- const modules2 = this.engine.getModules();
4057
- const moduleMetadata = modules2.find((m) => m.clazz === moduleClass);
4058
- return moduleMetadata ? { name: moduleMetadata.name } : void 0;
4059
- };
4060
- const modules = convertHandlersToModuleInfoWithMetadata(
4061
- this.actionHandlers,
4062
- getModuleMetadata
4063
- );
4064
- this.generatedCode = await generateClientCode(modules);
4065
- logger_default.debug(
4066
- `Generated client code for ${Object.keys(modules).length} module(s)`
4067
- );
4068
- if (this.clientSavePath && this.generatedCode) {
4069
- await this.saveCodeToFile(this.clientSavePath, this.generatedCode);
4157
+ if (this.server) {
4158
+ if (typeof this.server.close === "function") {
4159
+ if (this.server.close.length > 0) {
4160
+ await new Promise((resolve, reject) => {
4161
+ this.server.close((err) => {
4162
+ if (err) {
4163
+ reject(err);
4164
+ } else {
4165
+ resolve();
4166
+ }
4167
+ });
4168
+ });
4169
+ } else {
4170
+ this.server.close();
4171
+ await new Promise((resolve) => setTimeout(resolve, 10));
4172
+ }
4173
+ }
4174
+ this.server = null;
4175
+ logger_default.info("HTTP server stopped");
4070
4176
  }
4177
+ for (let i = this.plugins.length - 1; i >= 0; i--) {
4178
+ const plugin = this.plugins[i];
4179
+ try {
4180
+ await plugin.onDestroy?.();
4181
+ } catch (error) {
4182
+ logger_default.error(`Plugin ${plugin.name} failed in onDestroy`, error);
4183
+ }
4184
+ }
4185
+ this.modules = [];
4186
+ this.moduleInstances.clear();
4187
+ this.started = false;
4188
+ this.actualPort = null;
4189
+ logger_default.info(`${this.options.name} stopped`);
4071
4190
  } catch (error) {
4072
- logger_default.error("Failed to generate client code", error);
4073
- this.generatedCode = "// Error: Failed to generate client code";
4191
+ logger_default.error("Failed to stop engine", error);
4192
+ throw error;
4074
4193
  }
4075
4194
  }
4076
4195
  /**
4077
- * 保存代码到文件
4196
+ * 获取模块实例(单例)
4197
+ * @param moduleClass 模块类
4198
+ * @returns 模块实例
4078
4199
  */
4079
- async saveCodeToFile(path, code) {
4080
- try {
4081
- const dir = dirname(path);
4082
- await promises.mkdir(dir, { recursive: true });
4083
- await promises.writeFile(path, code, "utf-8");
4084
- logger_default.info(`Client code saved to ${path}`);
4085
- } catch (error) {
4086
- logger_default.error(`Failed to save client code to ${path}`, error);
4200
+ get(moduleClass) {
4201
+ if (!this.moduleInstances.has(moduleClass)) {
4202
+ this.moduleInstances.set(moduleClass, new moduleClass());
4087
4203
  }
4204
+ return this.moduleInstances.get(moduleClass);
4205
+ }
4206
+ /**
4207
+ * 获取已注册的模块列表
4208
+ */
4209
+ getModules() {
4210
+ if (this.modules.length === 0 && !this.started) {
4211
+ this.loadModules();
4212
+ }
4213
+ return [...this.modules];
4214
+ }
4215
+ /**
4216
+ * 获取实际使用的端口
4217
+ */
4218
+ getPort() {
4219
+ return this.actualPort;
4088
4220
  }
4089
4221
  };
4090
4222
 
@@ -4100,7 +4232,7 @@ var Factory = class {
4100
4232
  const moduleMetadataKey = /* @__PURE__ */ Symbol.for(
4101
4233
  `imean:moduleMetadata:${Date.now()}:${Math.random()}`
4102
4234
  );
4103
- class TypedMicroservice extends Microservice {
4235
+ class TypedMicroservice extends Microservice3 {
4104
4236
  constructor(options) {
4105
4237
  super(options, moduleMetadataKey);
4106
4238
  for (const plugin of plugins) {
@@ -4130,8 +4262,8 @@ var DEFAULT_TEST_OPTIONS = {
4130
4262
  };
4131
4263
  function createTestEngine(config) {
4132
4264
  const factory = Factory.create(...config.plugins);
4133
- const Microservice6 = factory.Microservice;
4134
- const engine = new Microservice6({
4265
+ const Microservice4 = factory.Microservice;
4266
+ const engine = new Microservice4({
4135
4267
  ...DEFAULT_TEST_OPTIONS,
4136
4268
  ...config.options
4137
4269
  });
@@ -4149,4 +4281,4 @@ var Testing = {
4149
4281
  wait
4150
4282
  };
4151
4283
 
4152
- export { Action, ActionPlugin, BaseLayout, Cache, CachePlugin, ClientCodePlugin, DEFAULT_TEST_OPTIONS, DuplicateModuleError, Factory, GracefulShutdownPlugin, Handler, HtmxLayout, MemoryCacheAdapter, Microservice, MockEtcd3, ModuleConfigValidationError, Page, PluginNameRequiredError, PluginPriority, RedisCacheAdapter, Route, RoutePlugin, Schedule, ScheduleMode, SchedulePlugin, Scheduler, Testing, convertHandlersToModuleInfo, convertHandlersToModuleInfoWithMetadata, generateClientCode, getZodTypeString, startCheck };
4284
+ export { Action, ActionPlugin, BaseLayout, Cache, CachePlugin, ClientCodePlugin, DEFAULT_TEST_OPTIONS, DuplicateModuleError, Factory, GracefulShutdownPlugin, Handler, HtmxLayout, MemoryCacheAdapter, MockEtcd3, ModuleConfigValidationError, Page, PluginNameRequiredError, PluginPriority, RedisCacheAdapter, Route, RoutePlugin, Schedule, ScheduleMode, SchedulePlugin, Scheduler, ServiceInfoCards, ServiceStatusPage, Testing, convertHandlersToModuleInfo, convertHandlersToModuleInfoWithMetadata, generateClientCode, getZodTypeString, startCheck };