imean-service-engine 1.2.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mod.cjs +77 -49
- package/dist/mod.d.cts +34 -14
- package/dist/mod.d.ts +34 -14
- package/dist/mod.js +73 -49
- package/package.json +7 -2
package/dist/mod.cjs
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
3
|
var zod = require('zod');
|
4
|
+
var ejson4 = require('ejson');
|
5
|
+
var lruCache = require('lru-cache');
|
4
6
|
var nodeServer = require('@hono/node-server');
|
5
|
-
var ejson3 = require('ejson');
|
6
7
|
var etcd3 = require('etcd3');
|
7
8
|
var fs = require('fs-extra');
|
8
9
|
var hono = require('hono');
|
9
|
-
var lruCache = require('lru-cache');
|
10
10
|
var api = require('@opentelemetry/api');
|
11
11
|
var winston = require('winston');
|
12
12
|
var prettier = require('prettier');
|
@@ -17,7 +17,7 @@ var dayjs = require('dayjs');
|
|
17
17
|
|
18
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
19
19
|
|
20
|
-
var
|
20
|
+
var ejson4__default = /*#__PURE__*/_interopDefault(ejson4);
|
21
21
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
22
22
|
var winston__default = /*#__PURE__*/_interopDefault(winston);
|
23
23
|
var prettier__default = /*#__PURE__*/_interopDefault(prettier);
|
@@ -25,6 +25,38 @@ var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
|
|
25
25
|
var dayjs__default = /*#__PURE__*/_interopDefault(dayjs);
|
26
26
|
|
27
27
|
// mod.ts
|
28
|
+
var CacheAdapter = class {
|
29
|
+
};
|
30
|
+
var RedisCacheAdapter = class extends CacheAdapter {
|
31
|
+
constructor(redis) {
|
32
|
+
super();
|
33
|
+
this.redis = redis;
|
34
|
+
}
|
35
|
+
async get(key) {
|
36
|
+
const value = await this.redis.get(key);
|
37
|
+
return value ? ejson4__default.default.parse(value) : null;
|
38
|
+
}
|
39
|
+
async set(key, value, ttl) {
|
40
|
+
return this.redis.set(key, ejson4__default.default.stringify(value), "EX", ttl / 1e3);
|
41
|
+
}
|
42
|
+
};
|
43
|
+
var MemoryCacheAdapter = class extends CacheAdapter {
|
44
|
+
cache;
|
45
|
+
constructor(options) {
|
46
|
+
super();
|
47
|
+
this.cache = new lruCache.LRUCache({
|
48
|
+
max: 1e3,
|
49
|
+
ttl: 1e3 * 60 * 10,
|
50
|
+
...options
|
51
|
+
});
|
52
|
+
}
|
53
|
+
async get(key) {
|
54
|
+
return this.cache.get(key);
|
55
|
+
}
|
56
|
+
async set(key, value, ttl) {
|
57
|
+
this.cache.set(key, value, { ttl });
|
58
|
+
}
|
59
|
+
};
|
28
60
|
var ACTION_METADATA = Symbol("action:metadata");
|
29
61
|
function Action(options) {
|
30
62
|
options.params = options.params || [];
|
@@ -53,7 +85,8 @@ function Action(options) {
|
|
53
85
|
printError: options.printError ?? false,
|
54
86
|
cache: options.cache,
|
55
87
|
ttl: options.ttl,
|
56
|
-
stream: options.stream
|
88
|
+
stream: options.stream,
|
89
|
+
mcp: options.mcp
|
57
90
|
};
|
58
91
|
prototype[ACTION_METADATA] = existingMetadata;
|
59
92
|
});
|
@@ -89,6 +122,8 @@ var ScheduleMode = /* @__PURE__ */ ((ScheduleMode2) => {
|
|
89
122
|
ScheduleMode2["FIXED_DELAY"] = "FIXED_DELAY";
|
90
123
|
return ScheduleMode2;
|
91
124
|
})(ScheduleMode || {});
|
125
|
+
var Plugin = class {
|
126
|
+
};
|
92
127
|
var logger = winston__default.default.createLogger({
|
93
128
|
level: "info",
|
94
129
|
transports: [
|
@@ -297,9 +332,6 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
297
332
|
}).join("; ");
|
298
333
|
return `{ ${props} }`;
|
299
334
|
}
|
300
|
-
case "ZodEnum": {
|
301
|
-
return def.values.map((opt) => `"${opt}"`).join(" | ");
|
302
|
-
}
|
303
335
|
case "ZodUnion": {
|
304
336
|
return def.options.map((opt) => processType(opt)).join(" | ");
|
305
337
|
}
|
@@ -328,8 +360,8 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
328
360
|
case "ZodUnknown": {
|
329
361
|
return "unknown";
|
330
362
|
}
|
331
|
-
case "ZodEnum
|
332
|
-
return def.values.map((opt) => `"${opt}"`).join(" | ");
|
363
|
+
case "ZodEnum": {
|
364
|
+
return "(" + def.values.map((opt) => `"${opt}"`).join(" | ") + ")";
|
333
365
|
}
|
334
366
|
case "ZodDefault": {
|
335
367
|
return processType(def.innerType);
|
@@ -455,7 +487,7 @@ var ActionHandler = class {
|
|
455
487
|
let args;
|
456
488
|
if (typeof req === "string") {
|
457
489
|
try {
|
458
|
-
args = Object.values(
|
490
|
+
args = Object.values(ejson4__default.default.parse(req));
|
459
491
|
} catch (error) {
|
460
492
|
throw new Error(`Invalid request body: ${error.message}`);
|
461
493
|
}
|
@@ -473,7 +505,7 @@ var ActionHandler = class {
|
|
473
505
|
}
|
474
506
|
});
|
475
507
|
}
|
476
|
-
const requestHash = hashText(
|
508
|
+
const requestHash = hashText(ejson4__default.default.stringify(args));
|
477
509
|
span.setAttribute("requestHash", requestHash);
|
478
510
|
return { args, requestHash };
|
479
511
|
} finally {
|
@@ -484,7 +516,7 @@ var ActionHandler = class {
|
|
484
516
|
const span = api.trace.getActiveSpan();
|
485
517
|
const { args, requestHash } = await this._validate(req);
|
486
518
|
const cacheHash = typeof this.metadata.cache === "function" ? this.metadata.cache(...args) : requestHash;
|
487
|
-
const cacheKey = `${this.moduleName}
|
519
|
+
const cacheKey = `${this.microservice.options.name}:cache:${this.moduleName}:${this.actionName}:${hashText(ejson4__default.default.stringify(cacheHash))}`;
|
488
520
|
span?.setAttribute("args", JSON.stringify(args));
|
489
521
|
if (!this.metadata.stream && this.metadata.cache && !this.microservice.options.disableCache) {
|
490
522
|
const cached = await this.microservice.cache.get(cacheKey);
|
@@ -539,7 +571,7 @@ var ActionHandler = class {
|
|
539
571
|
data: parsedResult,
|
540
572
|
expireAt: this.metadata.ttl ? now + this.metadata.ttl : void 0
|
541
573
|
},
|
542
|
-
|
574
|
+
this.metadata.ttl ?? 1e3
|
543
575
|
);
|
544
576
|
}
|
545
577
|
return parsedResult;
|
@@ -568,7 +600,7 @@ var WebSocketHandler = class {
|
|
568
600
|
this.sockets.add(ws);
|
569
601
|
}
|
570
602
|
async encodeMessage(message) {
|
571
|
-
const jsonStr =
|
603
|
+
const jsonStr = ejson4__default.default.stringify(message);
|
572
604
|
const data = new TextEncoder().encode(jsonStr);
|
573
605
|
const compressed = await brotli.compress(data, 6);
|
574
606
|
return compressed;
|
@@ -577,7 +609,7 @@ var WebSocketHandler = class {
|
|
577
609
|
try {
|
578
610
|
const decompressed = await brotli.decompress(new Uint8Array(data));
|
579
611
|
const jsonStr = new TextDecoder().decode(decompressed);
|
580
|
-
return
|
612
|
+
return ejson4__default.default.parse(jsonStr);
|
581
613
|
} catch (error) {
|
582
614
|
console.error("Error decoding message", error);
|
583
615
|
throw error;
|
@@ -638,7 +670,7 @@ var WebSocketHandler = class {
|
|
638
670
|
false
|
639
671
|
);
|
640
672
|
ws.send(
|
641
|
-
|
673
|
+
ejson4__default.default.stringify({
|
642
674
|
id: message.id,
|
643
675
|
success: false,
|
644
676
|
error: "Request timeout"
|
@@ -754,17 +786,24 @@ var Microservice = class {
|
|
754
786
|
this.options = {
|
755
787
|
prefix: "/api",
|
756
788
|
name: "microservice",
|
789
|
+
version: "0.0.1",
|
790
|
+
env: "default",
|
791
|
+
printError: false,
|
792
|
+
disableCache: false,
|
793
|
+
generateClient: false,
|
794
|
+
etcd: false,
|
795
|
+
events: {},
|
796
|
+
websocket: { enabled: false },
|
797
|
+
cacheAdapter: new MemoryCacheAdapter(),
|
798
|
+
plugins: [],
|
757
799
|
...options
|
758
800
|
};
|
801
|
+
this.cache = this.options.cacheAdapter;
|
759
802
|
this.fetch = this.app.request;
|
760
803
|
this.waitingInitialization = this.initialize();
|
761
804
|
ServiceContext.service = this;
|
762
805
|
}
|
763
806
|
async initialize() {
|
764
|
-
this.cache = new lruCache.LRUCache({
|
765
|
-
max: 1e3,
|
766
|
-
ttl: 1e3 * 60 * 10
|
767
|
-
});
|
768
807
|
if (this.options.etcd) {
|
769
808
|
this.initEtcd(this.options.etcd);
|
770
809
|
}
|
@@ -775,6 +814,7 @@ var Microservice = class {
|
|
775
814
|
this.initStatsEventManager();
|
776
815
|
}
|
777
816
|
await this.registerService(true);
|
817
|
+
await this.initPlugins();
|
778
818
|
}
|
779
819
|
async initModules() {
|
780
820
|
for (const ModuleClass of this.options.modules) {
|
@@ -799,7 +839,7 @@ var Microservice = class {
|
|
799
839
|
);
|
800
840
|
this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
|
801
841
|
logger_default.info(
|
802
|
-
`[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
|
842
|
+
`[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
|
803
843
|
);
|
804
844
|
}
|
805
845
|
const schedules = getScheduleMetadata(ModuleClass.prototype);
|
@@ -1128,12 +1168,12 @@ Received SIGINT signal`);
|
|
1128
1168
|
try {
|
1129
1169
|
for await (const value of result) {
|
1130
1170
|
const response = { value, done: false };
|
1131
|
-
const chunk = encoder.encode(
|
1171
|
+
const chunk = encoder.encode(ejson4__default.default.stringify(response) + "\n");
|
1132
1172
|
controller.enqueue(chunk);
|
1133
1173
|
}
|
1134
1174
|
controller.enqueue(
|
1135
1175
|
encoder.encode(
|
1136
|
-
|
1176
|
+
ejson4__default.default.stringify({ done: true }) + "\n"
|
1137
1177
|
)
|
1138
1178
|
);
|
1139
1179
|
controller.close();
|
@@ -1143,7 +1183,7 @@ Received SIGINT signal`);
|
|
1143
1183
|
done: true
|
1144
1184
|
};
|
1145
1185
|
controller.enqueue(
|
1146
|
-
encoder.encode(
|
1186
|
+
encoder.encode(ejson4__default.default.stringify(response) + "\n")
|
1147
1187
|
);
|
1148
1188
|
controller.close();
|
1149
1189
|
}
|
@@ -1157,37 +1197,21 @@ Received SIGINT signal`);
|
|
1157
1197
|
}
|
1158
1198
|
});
|
1159
1199
|
}
|
1160
|
-
return ctx.text(
|
1200
|
+
return ctx.text(ejson4__default.default.stringify({ success: true, data: result }));
|
1161
1201
|
} catch (error) {
|
1162
1202
|
return ctx.json({ success: false, error: error.message });
|
1163
1203
|
}
|
1164
1204
|
};
|
1165
|
-
async
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
async getServices() {
|
1172
|
-
if (!this.etcdClient) {
|
1173
|
-
throw new Error("etcd is not configured");
|
1205
|
+
async initPlugins() {
|
1206
|
+
for (const plugin of this.options.plugins) {
|
1207
|
+
logger_default.info(`[ \u521D\u59CB\u5316\u63D2\u4EF6 ] ${plugin.constructor.name}`);
|
1208
|
+
await plugin.initialize(this).catch((e) => {
|
1209
|
+
logger_default.error(`[ \u521D\u59CB\u5316\u63D2\u4EF6\u5931\u8D25 ] ${plugin.constructor.name}: ${e}`);
|
1210
|
+
});
|
1174
1211
|
}
|
1175
|
-
const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
|
1176
|
-
const servicesKey = `${namespace}services`;
|
1177
|
-
const services = await this.etcdClient.getAll().prefix(servicesKey);
|
1178
|
-
return Object.values(services).map((value) => JSON.parse(value));
|
1179
1212
|
}
|
1180
|
-
|
1181
|
-
|
1182
|
-
*/
|
1183
|
-
async getServiceInstances(serviceName) {
|
1184
|
-
if (!this.etcdClient) {
|
1185
|
-
throw new Error("etcd is not configured");
|
1186
|
-
}
|
1187
|
-
const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
|
1188
|
-
const serviceKey = `${namespace}services/${serviceName}`;
|
1189
|
-
const instances = await this.etcdClient.getAll().prefix(serviceKey);
|
1190
|
-
return Object.values(instances).map((value) => JSON.parse(value));
|
1213
|
+
async init() {
|
1214
|
+
await this.waitingInitialization;
|
1191
1215
|
}
|
1192
1216
|
};
|
1193
1217
|
|
@@ -1218,8 +1242,12 @@ Object.defineProperty(exports, "dayjs", {
|
|
1218
1242
|
get: function () { return dayjs__default.default; }
|
1219
1243
|
});
|
1220
1244
|
exports.Action = Action;
|
1245
|
+
exports.CacheAdapter = CacheAdapter;
|
1246
|
+
exports.MemoryCacheAdapter = MemoryCacheAdapter;
|
1221
1247
|
exports.Microservice = Microservice;
|
1222
1248
|
exports.Module = Module;
|
1249
|
+
exports.Plugin = Plugin;
|
1250
|
+
exports.RedisCacheAdapter = RedisCacheAdapter;
|
1223
1251
|
exports.Schedule = Schedule;
|
1224
1252
|
exports.ScheduleMode = ScheduleMode;
|
1225
1253
|
exports.ServiceContext = ServiceContext;
|
package/dist/mod.d.cts
CHANGED
@@ -1,12 +1,33 @@
|
|
1
1
|
import { z } from 'zod';
|
2
2
|
export * from 'zod';
|
3
|
+
import { Redis, Cluster } from 'ioredis';
|
4
|
+
import { LRUCache } from 'lru-cache';
|
3
5
|
import { Lease, Etcd3 } from 'etcd3';
|
4
6
|
import { Hono } from 'hono';
|
5
|
-
import { LRUCache } from 'lru-cache';
|
6
7
|
export { default as dayjs } from 'dayjs';
|
7
8
|
import winston from 'winston';
|
8
9
|
|
10
|
+
declare abstract class CacheAdapter {
|
11
|
+
abstract get(key: string): Promise<any>;
|
12
|
+
abstract set(key: string, value: any, ttl: number): Promise<any>;
|
13
|
+
}
|
14
|
+
declare class RedisCacheAdapter extends CacheAdapter {
|
15
|
+
private readonly redis;
|
16
|
+
constructor(redis: Redis | Cluster);
|
17
|
+
get(key: string): Promise<any>;
|
18
|
+
set(key: string, value: string, ttl: number): Promise<"OK">;
|
19
|
+
}
|
20
|
+
declare class MemoryCacheAdapter extends CacheAdapter {
|
21
|
+
private cache;
|
22
|
+
constructor(options?: LRUCache.Options<string, any, any>);
|
23
|
+
get(key: string): Promise<any>;
|
24
|
+
set(key: string, value: any, ttl: number): Promise<void>;
|
25
|
+
}
|
26
|
+
|
9
27
|
type CacheFn = (...args: any[]) => any;
|
28
|
+
type McpOptions = {
|
29
|
+
type: "tool";
|
30
|
+
};
|
10
31
|
interface ActionOptions {
|
11
32
|
params?: z.ZodType<any>[];
|
12
33
|
returns?: z.ZodType<any>;
|
@@ -16,6 +37,7 @@ interface ActionOptions {
|
|
16
37
|
cache?: boolean | CacheFn;
|
17
38
|
ttl?: number;
|
18
39
|
stream?: boolean;
|
40
|
+
mcp?: McpOptions;
|
19
41
|
}
|
20
42
|
interface ActionMetadata extends ActionOptions {
|
21
43
|
name: string;
|
@@ -121,7 +143,7 @@ interface MicroserviceOptions {
|
|
121
143
|
disableCache?: boolean;
|
122
144
|
modules: (new () => any)[];
|
123
145
|
generateClient?: URL | boolean;
|
124
|
-
etcd?: EtcdConfig;
|
146
|
+
etcd?: EtcdConfig | false;
|
125
147
|
events?: StatisticsEventOptions & {
|
126
148
|
onStats?: (event: StatisticsEvent) => void | Promise<void>;
|
127
149
|
onRegister?: (serviceInfo: ServiceInfo) => void | Promise<void>;
|
@@ -131,6 +153,8 @@ interface MicroserviceOptions {
|
|
131
153
|
enabled?: boolean;
|
132
154
|
timeout?: number;
|
133
155
|
};
|
156
|
+
cacheAdapter?: CacheAdapter;
|
157
|
+
plugins?: Plugin[];
|
134
158
|
}
|
135
159
|
interface ModuleInfo extends ModuleOptions {
|
136
160
|
actions: Record<string, ActionMetadata>;
|
@@ -141,6 +165,9 @@ interface StreamResponse<T = any> {
|
|
141
165
|
done: boolean;
|
142
166
|
error?: string;
|
143
167
|
}
|
168
|
+
declare abstract class Plugin {
|
169
|
+
abstract initialize(engine: Microservice): Promise<void>;
|
170
|
+
}
|
144
171
|
|
145
172
|
declare class ActionHandler {
|
146
173
|
private moduleInstance;
|
@@ -176,8 +203,8 @@ declare class Microservice {
|
|
176
203
|
private actionHandlers;
|
177
204
|
modules: Map<string, ModuleInfo>;
|
178
205
|
readonly fetch: typeof fetch;
|
179
|
-
options: MicroserviceOptions
|
180
|
-
cache:
|
206
|
+
options: Required<MicroserviceOptions>;
|
207
|
+
cache: CacheAdapter;
|
181
208
|
serviceId: string;
|
182
209
|
constructor(options: MicroserviceOptions);
|
183
210
|
private initialize;
|
@@ -205,7 +232,7 @@ declare class Microservice {
|
|
205
232
|
/**
|
206
233
|
* 获取所有模块的元数据
|
207
234
|
*/
|
208
|
-
|
235
|
+
getModules(withTypes?: boolean): Record<string, ModuleInfo>;
|
209
236
|
/**
|
210
237
|
* 停止服务
|
211
238
|
*/
|
@@ -222,15 +249,8 @@ declare class Microservice {
|
|
222
249
|
private updateStats;
|
223
250
|
getActionHandler(moduleName: string, actionName: string): ActionHandler;
|
224
251
|
private handleRequest;
|
252
|
+
private initPlugins;
|
225
253
|
init(): Promise<void>;
|
226
|
-
/**
|
227
|
-
* 获取所有注册的服务
|
228
|
-
*/
|
229
|
-
getServices(): Promise<ServiceInfo[]>;
|
230
|
-
/**
|
231
|
-
* 获取指定服务的实例
|
232
|
-
*/
|
233
|
-
getServiceInstances(serviceName: string): Promise<ServiceInfo[]>;
|
234
254
|
}
|
235
255
|
|
236
256
|
interface PreStartChecker {
|
@@ -259,4 +279,4 @@ declare function Schedule(options: ScheduleOptions): Function;
|
|
259
279
|
|
260
280
|
declare const logger: winston.Logger;
|
261
281
|
|
262
|
-
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, type CacheFn, type EtcdConfig, type EventServiceInfo, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, type PreStartChecker, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
282
|
+
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
package/dist/mod.d.ts
CHANGED
@@ -1,12 +1,33 @@
|
|
1
1
|
import { z } from 'zod';
|
2
2
|
export * from 'zod';
|
3
|
+
import { Redis, Cluster } from 'ioredis';
|
4
|
+
import { LRUCache } from 'lru-cache';
|
3
5
|
import { Lease, Etcd3 } from 'etcd3';
|
4
6
|
import { Hono } from 'hono';
|
5
|
-
import { LRUCache } from 'lru-cache';
|
6
7
|
export { default as dayjs } from 'dayjs';
|
7
8
|
import winston from 'winston';
|
8
9
|
|
10
|
+
declare abstract class CacheAdapter {
|
11
|
+
abstract get(key: string): Promise<any>;
|
12
|
+
abstract set(key: string, value: any, ttl: number): Promise<any>;
|
13
|
+
}
|
14
|
+
declare class RedisCacheAdapter extends CacheAdapter {
|
15
|
+
private readonly redis;
|
16
|
+
constructor(redis: Redis | Cluster);
|
17
|
+
get(key: string): Promise<any>;
|
18
|
+
set(key: string, value: string, ttl: number): Promise<"OK">;
|
19
|
+
}
|
20
|
+
declare class MemoryCacheAdapter extends CacheAdapter {
|
21
|
+
private cache;
|
22
|
+
constructor(options?: LRUCache.Options<string, any, any>);
|
23
|
+
get(key: string): Promise<any>;
|
24
|
+
set(key: string, value: any, ttl: number): Promise<void>;
|
25
|
+
}
|
26
|
+
|
9
27
|
type CacheFn = (...args: any[]) => any;
|
28
|
+
type McpOptions = {
|
29
|
+
type: "tool";
|
30
|
+
};
|
10
31
|
interface ActionOptions {
|
11
32
|
params?: z.ZodType<any>[];
|
12
33
|
returns?: z.ZodType<any>;
|
@@ -16,6 +37,7 @@ interface ActionOptions {
|
|
16
37
|
cache?: boolean | CacheFn;
|
17
38
|
ttl?: number;
|
18
39
|
stream?: boolean;
|
40
|
+
mcp?: McpOptions;
|
19
41
|
}
|
20
42
|
interface ActionMetadata extends ActionOptions {
|
21
43
|
name: string;
|
@@ -121,7 +143,7 @@ interface MicroserviceOptions {
|
|
121
143
|
disableCache?: boolean;
|
122
144
|
modules: (new () => any)[];
|
123
145
|
generateClient?: URL | boolean;
|
124
|
-
etcd?: EtcdConfig;
|
146
|
+
etcd?: EtcdConfig | false;
|
125
147
|
events?: StatisticsEventOptions & {
|
126
148
|
onStats?: (event: StatisticsEvent) => void | Promise<void>;
|
127
149
|
onRegister?: (serviceInfo: ServiceInfo) => void | Promise<void>;
|
@@ -131,6 +153,8 @@ interface MicroserviceOptions {
|
|
131
153
|
enabled?: boolean;
|
132
154
|
timeout?: number;
|
133
155
|
};
|
156
|
+
cacheAdapter?: CacheAdapter;
|
157
|
+
plugins?: Plugin[];
|
134
158
|
}
|
135
159
|
interface ModuleInfo extends ModuleOptions {
|
136
160
|
actions: Record<string, ActionMetadata>;
|
@@ -141,6 +165,9 @@ interface StreamResponse<T = any> {
|
|
141
165
|
done: boolean;
|
142
166
|
error?: string;
|
143
167
|
}
|
168
|
+
declare abstract class Plugin {
|
169
|
+
abstract initialize(engine: Microservice): Promise<void>;
|
170
|
+
}
|
144
171
|
|
145
172
|
declare class ActionHandler {
|
146
173
|
private moduleInstance;
|
@@ -176,8 +203,8 @@ declare class Microservice {
|
|
176
203
|
private actionHandlers;
|
177
204
|
modules: Map<string, ModuleInfo>;
|
178
205
|
readonly fetch: typeof fetch;
|
179
|
-
options: MicroserviceOptions
|
180
|
-
cache:
|
206
|
+
options: Required<MicroserviceOptions>;
|
207
|
+
cache: CacheAdapter;
|
181
208
|
serviceId: string;
|
182
209
|
constructor(options: MicroserviceOptions);
|
183
210
|
private initialize;
|
@@ -205,7 +232,7 @@ declare class Microservice {
|
|
205
232
|
/**
|
206
233
|
* 获取所有模块的元数据
|
207
234
|
*/
|
208
|
-
|
235
|
+
getModules(withTypes?: boolean): Record<string, ModuleInfo>;
|
209
236
|
/**
|
210
237
|
* 停止服务
|
211
238
|
*/
|
@@ -222,15 +249,8 @@ declare class Microservice {
|
|
222
249
|
private updateStats;
|
223
250
|
getActionHandler(moduleName: string, actionName: string): ActionHandler;
|
224
251
|
private handleRequest;
|
252
|
+
private initPlugins;
|
225
253
|
init(): Promise<void>;
|
226
|
-
/**
|
227
|
-
* 获取所有注册的服务
|
228
|
-
*/
|
229
|
-
getServices(): Promise<ServiceInfo[]>;
|
230
|
-
/**
|
231
|
-
* 获取指定服务的实例
|
232
|
-
*/
|
233
|
-
getServiceInstances(serviceName: string): Promise<ServiceInfo[]>;
|
234
254
|
}
|
235
255
|
|
236
256
|
interface PreStartChecker {
|
@@ -259,4 +279,4 @@ declare function Schedule(options: ScheduleOptions): Function;
|
|
259
279
|
|
260
280
|
declare const logger: winston.Logger;
|
261
281
|
|
262
|
-
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, type CacheFn, type EtcdConfig, type EventServiceInfo, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, type PreStartChecker, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
282
|
+
export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
|
package/dist/mod.js
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
import { z } from 'zod';
|
2
2
|
export * from 'zod';
|
3
|
+
import ejson4 from 'ejson';
|
4
|
+
import { LRUCache } from 'lru-cache';
|
3
5
|
import { serve } from '@hono/node-server';
|
4
|
-
import ejson3 from 'ejson';
|
5
6
|
import { Etcd3 } from 'etcd3';
|
6
7
|
import fs from 'fs-extra';
|
7
8
|
import { Hono } from 'hono';
|
8
|
-
import { LRUCache } from 'lru-cache';
|
9
9
|
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
10
10
|
import winston, { format } from 'winston';
|
11
11
|
import prettier from 'prettier';
|
@@ -16,6 +16,38 @@ import { createNodeWebSocket } from '@hono/node-ws';
|
|
16
16
|
export { default as dayjs } from 'dayjs';
|
17
17
|
|
18
18
|
// mod.ts
|
19
|
+
var CacheAdapter = class {
|
20
|
+
};
|
21
|
+
var RedisCacheAdapter = class extends CacheAdapter {
|
22
|
+
constructor(redis) {
|
23
|
+
super();
|
24
|
+
this.redis = redis;
|
25
|
+
}
|
26
|
+
async get(key) {
|
27
|
+
const value = await this.redis.get(key);
|
28
|
+
return value ? ejson4.parse(value) : null;
|
29
|
+
}
|
30
|
+
async set(key, value, ttl) {
|
31
|
+
return this.redis.set(key, ejson4.stringify(value), "EX", ttl / 1e3);
|
32
|
+
}
|
33
|
+
};
|
34
|
+
var MemoryCacheAdapter = class extends CacheAdapter {
|
35
|
+
cache;
|
36
|
+
constructor(options) {
|
37
|
+
super();
|
38
|
+
this.cache = new LRUCache({
|
39
|
+
max: 1e3,
|
40
|
+
ttl: 1e3 * 60 * 10,
|
41
|
+
...options
|
42
|
+
});
|
43
|
+
}
|
44
|
+
async get(key) {
|
45
|
+
return this.cache.get(key);
|
46
|
+
}
|
47
|
+
async set(key, value, ttl) {
|
48
|
+
this.cache.set(key, value, { ttl });
|
49
|
+
}
|
50
|
+
};
|
19
51
|
var ACTION_METADATA = Symbol("action:metadata");
|
20
52
|
function Action(options) {
|
21
53
|
options.params = options.params || [];
|
@@ -44,7 +76,8 @@ function Action(options) {
|
|
44
76
|
printError: options.printError ?? false,
|
45
77
|
cache: options.cache,
|
46
78
|
ttl: options.ttl,
|
47
|
-
stream: options.stream
|
79
|
+
stream: options.stream,
|
80
|
+
mcp: options.mcp
|
48
81
|
};
|
49
82
|
prototype[ACTION_METADATA] = existingMetadata;
|
50
83
|
});
|
@@ -80,6 +113,8 @@ var ScheduleMode = /* @__PURE__ */ ((ScheduleMode2) => {
|
|
80
113
|
ScheduleMode2["FIXED_DELAY"] = "FIXED_DELAY";
|
81
114
|
return ScheduleMode2;
|
82
115
|
})(ScheduleMode || {});
|
116
|
+
var Plugin = class {
|
117
|
+
};
|
83
118
|
var logger = winston.createLogger({
|
84
119
|
level: "info",
|
85
120
|
transports: [
|
@@ -288,9 +323,6 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
288
323
|
}).join("; ");
|
289
324
|
return `{ ${props} }`;
|
290
325
|
}
|
291
|
-
case "ZodEnum": {
|
292
|
-
return def.values.map((opt) => `"${opt}"`).join(" | ");
|
293
|
-
}
|
294
326
|
case "ZodUnion": {
|
295
327
|
return def.options.map((opt) => processType(opt)).join(" | ");
|
296
328
|
}
|
@@ -319,8 +351,8 @@ function getZodTypeString(schema, defaultOptional = false) {
|
|
319
351
|
case "ZodUnknown": {
|
320
352
|
return "unknown";
|
321
353
|
}
|
322
|
-
case "ZodEnum
|
323
|
-
return def.values.map((opt) => `"${opt}"`).join(" | ");
|
354
|
+
case "ZodEnum": {
|
355
|
+
return "(" + def.values.map((opt) => `"${opt}"`).join(" | ") + ")";
|
324
356
|
}
|
325
357
|
case "ZodDefault": {
|
326
358
|
return processType(def.innerType);
|
@@ -446,7 +478,7 @@ var ActionHandler = class {
|
|
446
478
|
let args;
|
447
479
|
if (typeof req === "string") {
|
448
480
|
try {
|
449
|
-
args = Object.values(
|
481
|
+
args = Object.values(ejson4.parse(req));
|
450
482
|
} catch (error) {
|
451
483
|
throw new Error(`Invalid request body: ${error.message}`);
|
452
484
|
}
|
@@ -464,7 +496,7 @@ var ActionHandler = class {
|
|
464
496
|
}
|
465
497
|
});
|
466
498
|
}
|
467
|
-
const requestHash = hashText(
|
499
|
+
const requestHash = hashText(ejson4.stringify(args));
|
468
500
|
span.setAttribute("requestHash", requestHash);
|
469
501
|
return { args, requestHash };
|
470
502
|
} finally {
|
@@ -475,7 +507,7 @@ var ActionHandler = class {
|
|
475
507
|
const span = trace.getActiveSpan();
|
476
508
|
const { args, requestHash } = await this._validate(req);
|
477
509
|
const cacheHash = typeof this.metadata.cache === "function" ? this.metadata.cache(...args) : requestHash;
|
478
|
-
const cacheKey = `${this.moduleName}
|
510
|
+
const cacheKey = `${this.microservice.options.name}:cache:${this.moduleName}:${this.actionName}:${hashText(ejson4.stringify(cacheHash))}`;
|
479
511
|
span?.setAttribute("args", JSON.stringify(args));
|
480
512
|
if (!this.metadata.stream && this.metadata.cache && !this.microservice.options.disableCache) {
|
481
513
|
const cached = await this.microservice.cache.get(cacheKey);
|
@@ -530,7 +562,7 @@ var ActionHandler = class {
|
|
530
562
|
data: parsedResult,
|
531
563
|
expireAt: this.metadata.ttl ? now + this.metadata.ttl : void 0
|
532
564
|
},
|
533
|
-
|
565
|
+
this.metadata.ttl ?? 1e3
|
534
566
|
);
|
535
567
|
}
|
536
568
|
return parsedResult;
|
@@ -559,7 +591,7 @@ var WebSocketHandler = class {
|
|
559
591
|
this.sockets.add(ws);
|
560
592
|
}
|
561
593
|
async encodeMessage(message) {
|
562
|
-
const jsonStr =
|
594
|
+
const jsonStr = ejson4.stringify(message);
|
563
595
|
const data = new TextEncoder().encode(jsonStr);
|
564
596
|
const compressed = await brotli.compress(data, 6);
|
565
597
|
return compressed;
|
@@ -568,7 +600,7 @@ var WebSocketHandler = class {
|
|
568
600
|
try {
|
569
601
|
const decompressed = await brotli.decompress(new Uint8Array(data));
|
570
602
|
const jsonStr = new TextDecoder().decode(decompressed);
|
571
|
-
return
|
603
|
+
return ejson4.parse(jsonStr);
|
572
604
|
} catch (error) {
|
573
605
|
console.error("Error decoding message", error);
|
574
606
|
throw error;
|
@@ -629,7 +661,7 @@ var WebSocketHandler = class {
|
|
629
661
|
false
|
630
662
|
);
|
631
663
|
ws.send(
|
632
|
-
|
664
|
+
ejson4.stringify({
|
633
665
|
id: message.id,
|
634
666
|
success: false,
|
635
667
|
error: "Request timeout"
|
@@ -745,17 +777,24 @@ var Microservice = class {
|
|
745
777
|
this.options = {
|
746
778
|
prefix: "/api",
|
747
779
|
name: "microservice",
|
780
|
+
version: "0.0.1",
|
781
|
+
env: "default",
|
782
|
+
printError: false,
|
783
|
+
disableCache: false,
|
784
|
+
generateClient: false,
|
785
|
+
etcd: false,
|
786
|
+
events: {},
|
787
|
+
websocket: { enabled: false },
|
788
|
+
cacheAdapter: new MemoryCacheAdapter(),
|
789
|
+
plugins: [],
|
748
790
|
...options
|
749
791
|
};
|
792
|
+
this.cache = this.options.cacheAdapter;
|
750
793
|
this.fetch = this.app.request;
|
751
794
|
this.waitingInitialization = this.initialize();
|
752
795
|
ServiceContext.service = this;
|
753
796
|
}
|
754
797
|
async initialize() {
|
755
|
-
this.cache = new LRUCache({
|
756
|
-
max: 1e3,
|
757
|
-
ttl: 1e3 * 60 * 10
|
758
|
-
});
|
759
798
|
if (this.options.etcd) {
|
760
799
|
this.initEtcd(this.options.etcd);
|
761
800
|
}
|
@@ -766,6 +805,7 @@ var Microservice = class {
|
|
766
805
|
this.initStatsEventManager();
|
767
806
|
}
|
768
807
|
await this.registerService(true);
|
808
|
+
await this.initPlugins();
|
769
809
|
}
|
770
810
|
async initModules() {
|
771
811
|
for (const ModuleClass of this.options.modules) {
|
@@ -790,7 +830,7 @@ var Microservice = class {
|
|
790
830
|
);
|
791
831
|
this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
|
792
832
|
logger_default.info(
|
793
|
-
`[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
|
833
|
+
`[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
|
794
834
|
);
|
795
835
|
}
|
796
836
|
const schedules = getScheduleMetadata(ModuleClass.prototype);
|
@@ -1119,12 +1159,12 @@ Received SIGINT signal`);
|
|
1119
1159
|
try {
|
1120
1160
|
for await (const value of result) {
|
1121
1161
|
const response = { value, done: false };
|
1122
|
-
const chunk = encoder.encode(
|
1162
|
+
const chunk = encoder.encode(ejson4.stringify(response) + "\n");
|
1123
1163
|
controller.enqueue(chunk);
|
1124
1164
|
}
|
1125
1165
|
controller.enqueue(
|
1126
1166
|
encoder.encode(
|
1127
|
-
|
1167
|
+
ejson4.stringify({ done: true }) + "\n"
|
1128
1168
|
)
|
1129
1169
|
);
|
1130
1170
|
controller.close();
|
@@ -1134,7 +1174,7 @@ Received SIGINT signal`);
|
|
1134
1174
|
done: true
|
1135
1175
|
};
|
1136
1176
|
controller.enqueue(
|
1137
|
-
encoder.encode(
|
1177
|
+
encoder.encode(ejson4.stringify(response) + "\n")
|
1138
1178
|
);
|
1139
1179
|
controller.close();
|
1140
1180
|
}
|
@@ -1148,37 +1188,21 @@ Received SIGINT signal`);
|
|
1148
1188
|
}
|
1149
1189
|
});
|
1150
1190
|
}
|
1151
|
-
return ctx.text(
|
1191
|
+
return ctx.text(ejson4.stringify({ success: true, data: result }));
|
1152
1192
|
} catch (error) {
|
1153
1193
|
return ctx.json({ success: false, error: error.message });
|
1154
1194
|
}
|
1155
1195
|
};
|
1156
|
-
async
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
async getServices() {
|
1163
|
-
if (!this.etcdClient) {
|
1164
|
-
throw new Error("etcd is not configured");
|
1196
|
+
async initPlugins() {
|
1197
|
+
for (const plugin of this.options.plugins) {
|
1198
|
+
logger_default.info(`[ \u521D\u59CB\u5316\u63D2\u4EF6 ] ${plugin.constructor.name}`);
|
1199
|
+
await plugin.initialize(this).catch((e) => {
|
1200
|
+
logger_default.error(`[ \u521D\u59CB\u5316\u63D2\u4EF6\u5931\u8D25 ] ${plugin.constructor.name}: ${e}`);
|
1201
|
+
});
|
1165
1202
|
}
|
1166
|
-
const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
|
1167
|
-
const servicesKey = `${namespace}services`;
|
1168
|
-
const services = await this.etcdClient.getAll().prefix(servicesKey);
|
1169
|
-
return Object.values(services).map((value) => JSON.parse(value));
|
1170
1203
|
}
|
1171
|
-
|
1172
|
-
|
1173
|
-
*/
|
1174
|
-
async getServiceInstances(serviceName) {
|
1175
|
-
if (!this.etcdClient) {
|
1176
|
-
throw new Error("etcd is not configured");
|
1177
|
-
}
|
1178
|
-
const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
|
1179
|
-
const serviceKey = `${namespace}services/${serviceName}`;
|
1180
|
-
const instances = await this.etcdClient.getAll().prefix(serviceKey);
|
1181
|
-
return Object.values(instances).map((value) => JSON.parse(value));
|
1204
|
+
async init() {
|
1205
|
+
await this.waitingInitialization;
|
1182
1206
|
}
|
1183
1207
|
};
|
1184
1208
|
|
@@ -1204,4 +1228,4 @@ async function startCheck(checkers, pass) {
|
|
1204
1228
|
if (pass) await pass();
|
1205
1229
|
}
|
1206
1230
|
|
1207
|
-
export { Action, Microservice, Module, Schedule, ScheduleMode, ServiceContext, logger_default as logger, startCheck };
|
1231
|
+
export { Action, CacheAdapter, MemoryCacheAdapter, Microservice, Module, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, logger_default as logger, startCheck };
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "imean-service-engine",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.4.0",
|
4
4
|
"description": "microservice engine",
|
5
5
|
"keywords": [
|
6
6
|
"microservice",
|
@@ -39,6 +39,7 @@
|
|
39
39
|
"dependencies": {
|
40
40
|
"@hono/node-server": "^1.13.7",
|
41
41
|
"@hono/node-ws": "^1.0.6",
|
42
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
42
43
|
"dayjs": "^1.11.13",
|
43
44
|
"ejson": "^2.2.3",
|
44
45
|
"etcd3": "^1.1.2",
|
@@ -46,11 +47,13 @@
|
|
46
47
|
"hono": "^4.6.17",
|
47
48
|
"lru-cache": "^11.0.2",
|
48
49
|
"prettier": "^3.4.2",
|
50
|
+
"ulid": "^3.0.0",
|
49
51
|
"winston": "^3.17.0",
|
50
52
|
"zod": "^3.24.1"
|
51
53
|
},
|
52
54
|
"peerDependencies": {
|
53
|
-
"@opentelemetry/api": "^1.x"
|
55
|
+
"@opentelemetry/api": "^1.x",
|
56
|
+
"ioredis": "^5.6.0"
|
54
57
|
},
|
55
58
|
"devDependencies": {
|
56
59
|
"@opentelemetry/auto-instrumentations-node": "^0.55.3",
|
@@ -65,9 +68,11 @@
|
|
65
68
|
"@opentelemetry/winston-transport": "^0.10.0",
|
66
69
|
"@types/ejson": "^2.2.2",
|
67
70
|
"@types/fs-extra": "^11.0.4",
|
71
|
+
"@types/ioredis-mock": "^8.2.5",
|
68
72
|
"@types/node": "^20.0.0",
|
69
73
|
"@vitest/coverage-v8": "^3.0.4",
|
70
74
|
"imean-service-client": "^1.5.0",
|
75
|
+
"ioredis-mock": "^8.9.0",
|
71
76
|
"opentelemetry-instrumentation-fetch-node": "^1.2.3",
|
72
77
|
"tslib": "^2.8.1",
|
73
78
|
"tsup": "^8.0.1",
|