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