imean-service-engine 1.1.0 → 1.2.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/README.md +10 -13
- package/dist/mod.cjs +91 -76
- package/dist/mod.d.cts +2 -0
- package/dist/mod.d.ts +2 -0
- package/dist/mod.js +91 -76
- package/package.json +84 -75
package/README.md
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
# Microservice Framework
|
1
|
+
# Microservice Framework
|
2
2
|
|
3
3
|
一个轻量级的 TypeScript 微服务框架。提供了类型安全、自动客户端生成、请求重试等特性。
|
4
4
|
|
5
|
-
[](https://jsr.io/@imean/service-engine)
|
6
|
-
|
7
5
|
## 特性
|
8
6
|
|
9
7
|
- 📝 完全的 TypeScript 支持
|
@@ -14,7 +12,6 @@
|
|
14
12
|
- 🌟 优雅的装饰器 API
|
15
13
|
- 🚦 优雅停机支持
|
16
14
|
- 📡 生成基于 fetch 的客户端代码,可以在 Deno 、Node.js、Bun 以及浏览器中使用
|
17
|
-
- 🌟 服务间调用可以利用 Deno 的分布式模块引入快速集成生成的客户端
|
18
15
|
- 🌟 支持 Stream 流传输,客户端使用 AsyncIterator 迭代
|
19
16
|
- 🌟 服务引擎支持通过 WebSocket 进行实时通信,相比 HTTP 请求具有以下优势:
|
20
17
|
- 保持长连接,减少连接建立的开销
|
@@ -30,7 +27,7 @@
|
|
30
27
|
## 安装
|
31
28
|
|
32
29
|
```typescript
|
33
|
-
import { Action, Microservice, Module } from "
|
30
|
+
import { Action, Microservice, Module } from "imean-service-engine";
|
34
31
|
```
|
35
32
|
|
36
33
|
## 快速开始
|
@@ -126,7 +123,7 @@ TypeScript 客户端代码。
|
|
126
123
|
|
127
124
|
```typescript
|
128
125
|
const client = new MicroserviceClient({
|
129
|
-
baseUrl: "http://localhost:3000
|
126
|
+
baseUrl: "http://localhost:3000",
|
130
127
|
});
|
131
128
|
// 创建用户
|
132
129
|
const user = await client.users.createUser("张三", 25);
|
@@ -230,7 +227,7 @@ interface ClientOptions {
|
|
230
227
|
|
231
228
|
```typescript
|
232
229
|
// main.ts
|
233
|
-
import { startCheck } from "
|
230
|
+
import { startCheck } from "imean-service-engine";
|
234
231
|
|
235
232
|
// 数据库连接检查
|
236
233
|
async function checkDatabase() {
|
@@ -326,17 +323,17 @@ your-service/
|
|
326
323
|
// config/index.ts
|
327
324
|
export const config = {
|
328
325
|
database: {
|
329
|
-
host:
|
330
|
-
port: parseInt(
|
326
|
+
host: process.env.DB_HOST || "localhost",
|
327
|
+
port: parseInt(process.env.DB_PORT || "5432"),
|
331
328
|
// ...
|
332
329
|
},
|
333
330
|
redis: {
|
334
|
-
url:
|
331
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
335
332
|
// ...
|
336
333
|
},
|
337
334
|
service: {
|
338
|
-
port: parseInt(
|
339
|
-
prefix:
|
335
|
+
port: parseInt(process.env.PORT || "3000"),
|
336
|
+
prefix: process.env.API_PREFIX || "/api",
|
340
337
|
},
|
341
338
|
};
|
342
339
|
|
@@ -550,7 +547,7 @@ const client = new MicroserviceClient({
|
|
550
547
|
|
551
548
|
### Node.js 环境使用 WebSocket
|
552
549
|
|
553
|
-
|
550
|
+
最新Node.js已经提供了 WebSocket 实现,可以直接使用。如果在较低 Node.js 环境下,可以使用 `isomorphic-ws` 包来提供 WebSocket 实现:
|
554
551
|
|
555
552
|
```typescript
|
556
553
|
import WebSocket from "isomorphic-ws";
|
package/dist/mod.cjs
CHANGED
@@ -7,10 +7,9 @@ var etcd3 = require('etcd3');
|
|
7
7
|
var fs = require('fs-extra');
|
8
8
|
var hono = require('hono');
|
9
9
|
var lruCache = require('lru-cache');
|
10
|
+
var api = require('@opentelemetry/api');
|
10
11
|
var winston = require('winston');
|
11
12
|
var prettier = require('prettier');
|
12
|
-
var api = require('@opentelemetry/api');
|
13
|
-
var apiLogs = require('@opentelemetry/api-logs');
|
14
13
|
var crypto2 = require('crypto');
|
15
14
|
var zlib = require('zlib');
|
16
15
|
var nodeWs = require('@hono/node-ws');
|
@@ -110,6 +109,7 @@ var logger = winston__default.default.createLogger({
|
|
110
109
|
var logger_default = logger;
|
111
110
|
|
112
111
|
// decorators/schedule.ts
|
112
|
+
var tracer = api.trace.getTracer("scheduler");
|
113
113
|
var SCHEDULE_METADATA = Symbol("schedule:metadata");
|
114
114
|
function Schedule(options) {
|
115
115
|
return function(_originalMethod, context) {
|
@@ -162,20 +162,44 @@ var Scheduler = class {
|
|
162
162
|
});
|
163
163
|
campaign.on("elected", () => {
|
164
164
|
this.isLeader.set(serviceId, true);
|
165
|
-
this.startTimer(serviceId, metadata, method);
|
165
|
+
this.startTimer(serviceId, metadata, moduleName, method);
|
166
166
|
logger_default.info(`become leader for ${moduleName}.${methodName}`);
|
167
167
|
});
|
168
168
|
}
|
169
169
|
/**
|
170
170
|
* 启动定时器
|
171
171
|
*/
|
172
|
-
startTimer(serviceId, metadata, method) {
|
172
|
+
startTimer(serviceId, metadata, moduleName, method) {
|
173
173
|
this.stopTimer(serviceId);
|
174
|
+
const wrappedMethod = async () => {
|
175
|
+
tracer.startActiveSpan(
|
176
|
+
`ScheduleTask ${moduleName}.${metadata.name}`,
|
177
|
+
{ root: true },
|
178
|
+
async (span) => {
|
179
|
+
span.setAttribute("serviceId", serviceId);
|
180
|
+
span.setAttribute("methodName", metadata.name);
|
181
|
+
span.setAttribute("moduleName", moduleName);
|
182
|
+
span.setAttribute("interval", metadata.interval);
|
183
|
+
span.setAttribute("mode", metadata.mode);
|
184
|
+
try {
|
185
|
+
await method();
|
186
|
+
} catch (error) {
|
187
|
+
span.setStatus({
|
188
|
+
code: api.SpanStatusCode.ERROR,
|
189
|
+
message: error.message
|
190
|
+
});
|
191
|
+
} finally {
|
192
|
+
span.setStatus({ code: api.SpanStatusCode.OK });
|
193
|
+
span.end();
|
194
|
+
}
|
195
|
+
}
|
196
|
+
);
|
197
|
+
};
|
174
198
|
if (metadata.mode === "FIXED_DELAY" /* FIXED_DELAY */) {
|
175
199
|
const runTask = async () => {
|
176
200
|
if (!this.isLeader.get(serviceId)) return;
|
177
201
|
try {
|
178
|
-
await
|
202
|
+
await wrappedMethod();
|
179
203
|
} finally {
|
180
204
|
this.timers.set(serviceId, setTimeout(runTask, metadata.interval));
|
181
205
|
}
|
@@ -186,7 +210,7 @@ var Scheduler = class {
|
|
186
210
|
serviceId,
|
187
211
|
setInterval(async () => {
|
188
212
|
if (!this.isLeader.get(serviceId)) return;
|
189
|
-
await
|
213
|
+
await wrappedMethod();
|
190
214
|
}, metadata.interval)
|
191
215
|
);
|
192
216
|
}
|
@@ -389,8 +413,7 @@ var brotli = {
|
|
389
413
|
};
|
390
414
|
|
391
415
|
// core/handler.ts
|
392
|
-
var
|
393
|
-
var logger2 = apiLogs.logs.getLogger("action-handler");
|
416
|
+
var tracer2 = api.trace.getTracer("action-handler");
|
394
417
|
var ActionHandler = class {
|
395
418
|
constructor(moduleInstance, actionName, metadata, microservice, moduleName) {
|
396
419
|
this.moduleInstance = moduleInstance;
|
@@ -400,46 +423,59 @@ var ActionHandler = class {
|
|
400
423
|
this.moduleName = moduleName;
|
401
424
|
}
|
402
425
|
async handle(req) {
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
} catch (error) {
|
414
|
-
throw new Error(`Invalid request body: ${error.message}`);
|
426
|
+
return await tracer2.startActiveSpan(
|
427
|
+
`handle ${this.moduleName}.${this.actionName}`,
|
428
|
+
async (span) => {
|
429
|
+
span.setAttribute("module", this.moduleName);
|
430
|
+
span.setAttribute("action", this.actionName);
|
431
|
+
try {
|
432
|
+
return await this._handle(req);
|
433
|
+
} finally {
|
434
|
+
span.end();
|
435
|
+
}
|
415
436
|
}
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
437
|
+
);
|
438
|
+
}
|
439
|
+
async _validate(req) {
|
440
|
+
const span = tracer2.startSpan("validate");
|
441
|
+
try {
|
442
|
+
let args;
|
443
|
+
if (typeof req === "string") {
|
421
444
|
try {
|
422
|
-
|
445
|
+
args = Object.values(ejson3__default.default.parse(req));
|
423
446
|
} catch (error) {
|
424
|
-
throw new Error(
|
425
|
-
`Invalid argument ${index}: ${error.message}`
|
426
|
-
);
|
447
|
+
throw new Error(`Invalid request body: ${error.message}`);
|
427
448
|
}
|
428
|
-
}
|
449
|
+
} else {
|
450
|
+
args = req;
|
451
|
+
}
|
452
|
+
if (this.metadata.params) {
|
453
|
+
args = args.map((arg, index) => {
|
454
|
+
try {
|
455
|
+
return this.metadata.params[index].parse(arg);
|
456
|
+
} catch (error) {
|
457
|
+
throw new Error(
|
458
|
+
`Invalid argument ${index}: ${error.message}`
|
459
|
+
);
|
460
|
+
}
|
461
|
+
});
|
462
|
+
}
|
463
|
+
const requestHash = hashText(ejson3__default.default.stringify(args));
|
464
|
+
span.setAttribute("requestHash", requestHash);
|
465
|
+
return { args, requestHash };
|
466
|
+
} finally {
|
467
|
+
span.end();
|
429
468
|
}
|
430
|
-
|
469
|
+
}
|
470
|
+
async _handle(req) {
|
471
|
+
const span = api.trace.getActiveSpan();
|
472
|
+
const { args, requestHash } = await this._validate(req);
|
431
473
|
if (!this.metadata.stream && this.metadata.cache && !this.microservice.options.disableCache) {
|
432
474
|
const cacheKey = `${this.moduleName}.${this.actionName}.${requestHash}`;
|
433
475
|
const cached = await this.microservice.cache.get(cacheKey);
|
434
476
|
const now = Date.now();
|
435
477
|
if (cached !== null && (!this.metadata.ttl || cached?.expireAt > now)) {
|
436
|
-
|
437
|
-
this.moduleName,
|
438
|
-
this.actionName,
|
439
|
-
0,
|
440
|
-
true,
|
441
|
-
true
|
442
|
-
);
|
478
|
+
span?.setAttribute("cacheHit", true);
|
443
479
|
return cached.data;
|
444
480
|
}
|
445
481
|
}
|
@@ -449,11 +485,11 @@ var ActionHandler = class {
|
|
449
485
|
args
|
450
486
|
);
|
451
487
|
if (this.metadata.stream) {
|
488
|
+
span?.setAttribute("stream", true);
|
452
489
|
if (!isAsyncIterable(result)) {
|
453
490
|
throw new Error("Stream action must return AsyncIterator");
|
454
491
|
}
|
455
492
|
let count = 0;
|
456
|
-
const self = this;
|
457
493
|
return {
|
458
494
|
[Symbol.asyncIterator]() {
|
459
495
|
const iterator = result[Symbol.asyncIterator]();
|
@@ -461,24 +497,22 @@ var ActionHandler = class {
|
|
461
497
|
async next() {
|
462
498
|
try {
|
463
499
|
const { value, done } = await iterator.next();
|
500
|
+
span?.addEvent("stream.next");
|
464
501
|
if (!done) count++;
|
465
502
|
if (done) {
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
self.actionName,
|
470
|
-
responseTime / count,
|
471
|
-
true
|
472
|
-
);
|
503
|
+
span?.setAttribute("streamCount", count);
|
504
|
+
span?.addEvent("stream.end");
|
505
|
+
span?.end();
|
473
506
|
}
|
474
507
|
return { value, done };
|
475
508
|
} catch (error) {
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
);
|
509
|
+
span?.addEvent("stream.error", { error: error.message });
|
510
|
+
span?.recordException(error);
|
511
|
+
span?.setStatus({
|
512
|
+
code: api.SpanStatusCode.ERROR,
|
513
|
+
message: error.message
|
514
|
+
});
|
515
|
+
span?.end();
|
482
516
|
throw error;
|
483
517
|
}
|
484
518
|
}
|
@@ -506,36 +540,17 @@ var ActionHandler = class {
|
|
506
540
|
{ ttl: this.metadata.ttl }
|
507
541
|
);
|
508
542
|
}
|
509
|
-
|
510
|
-
|
511
|
-
this.actionName,
|
512
|
-
0,
|
513
|
-
true
|
514
|
-
);
|
543
|
+
span?.setStatus({ code: api.SpanStatusCode.OK, message: "success" });
|
544
|
+
span?.end();
|
515
545
|
return parsedResult;
|
516
546
|
} catch (error) {
|
517
547
|
if (this.metadata.printError !== false && this.microservice.options.printError !== false) {
|
518
548
|
console.error(`Error in ${this.moduleName}.${this.actionName}:`, error);
|
519
549
|
}
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
service: {
|
524
|
-
id: this.microservice.serviceId,
|
525
|
-
name: this.microservice.options.name,
|
526
|
-
version: this.microservice.options.version,
|
527
|
-
env: this.microservice.options.env
|
528
|
-
},
|
529
|
-
module: this.moduleName,
|
530
|
-
action: this.actionName,
|
531
|
-
params: args,
|
532
|
-
time: Date.now(),
|
533
|
-
error: error.message
|
534
|
-
});
|
535
|
-
}
|
550
|
+
span?.recordException(error);
|
551
|
+
span?.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
|
552
|
+
span?.end();
|
536
553
|
throw error;
|
537
|
-
} finally {
|
538
|
-
span.end();
|
539
554
|
}
|
540
555
|
}
|
541
556
|
};
|
package/dist/mod.d.cts
CHANGED
@@ -149,6 +149,8 @@ declare class ActionHandler {
|
|
149
149
|
private moduleName;
|
150
150
|
constructor(moduleInstance: any, actionName: string, metadata: ActionMetadata, microservice: Microservice, moduleName: string);
|
151
151
|
handle(req: string | any[]): Promise<any>;
|
152
|
+
private _validate;
|
153
|
+
private _handle;
|
152
154
|
}
|
153
155
|
|
154
156
|
declare const ServiceContext: {
|
package/dist/mod.d.ts
CHANGED
@@ -149,6 +149,8 @@ declare class ActionHandler {
|
|
149
149
|
private moduleName;
|
150
150
|
constructor(moduleInstance: any, actionName: string, metadata: ActionMetadata, microservice: Microservice, moduleName: string);
|
151
151
|
handle(req: string | any[]): Promise<any>;
|
152
|
+
private _validate;
|
153
|
+
private _handle;
|
152
154
|
}
|
153
155
|
|
154
156
|
declare const ServiceContext: {
|
package/dist/mod.js
CHANGED
@@ -6,10 +6,9 @@ import { Etcd3 } from 'etcd3';
|
|
6
6
|
import fs from 'fs-extra';
|
7
7
|
import { Hono } from 'hono';
|
8
8
|
import { LRUCache } from 'lru-cache';
|
9
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
9
10
|
import winston, { format } from 'winston';
|
10
11
|
import prettier from 'prettier';
|
11
|
-
import { trace } from '@opentelemetry/api';
|
12
|
-
import { logs } from '@opentelemetry/api-logs';
|
13
12
|
import crypto2 from 'node:crypto';
|
14
13
|
import { brotliDecompress } from 'node:zlib';
|
15
14
|
import { brotliCompress, constants } from 'zlib';
|
@@ -101,6 +100,7 @@ var logger = winston.createLogger({
|
|
101
100
|
var logger_default = logger;
|
102
101
|
|
103
102
|
// decorators/schedule.ts
|
103
|
+
var tracer = trace.getTracer("scheduler");
|
104
104
|
var SCHEDULE_METADATA = Symbol("schedule:metadata");
|
105
105
|
function Schedule(options) {
|
106
106
|
return function(_originalMethod, context) {
|
@@ -153,20 +153,44 @@ var Scheduler = class {
|
|
153
153
|
});
|
154
154
|
campaign.on("elected", () => {
|
155
155
|
this.isLeader.set(serviceId, true);
|
156
|
-
this.startTimer(serviceId, metadata, method);
|
156
|
+
this.startTimer(serviceId, metadata, moduleName, method);
|
157
157
|
logger_default.info(`become leader for ${moduleName}.${methodName}`);
|
158
158
|
});
|
159
159
|
}
|
160
160
|
/**
|
161
161
|
* 启动定时器
|
162
162
|
*/
|
163
|
-
startTimer(serviceId, metadata, method) {
|
163
|
+
startTimer(serviceId, metadata, moduleName, method) {
|
164
164
|
this.stopTimer(serviceId);
|
165
|
+
const wrappedMethod = async () => {
|
166
|
+
tracer.startActiveSpan(
|
167
|
+
`ScheduleTask ${moduleName}.${metadata.name}`,
|
168
|
+
{ root: true },
|
169
|
+
async (span) => {
|
170
|
+
span.setAttribute("serviceId", serviceId);
|
171
|
+
span.setAttribute("methodName", metadata.name);
|
172
|
+
span.setAttribute("moduleName", moduleName);
|
173
|
+
span.setAttribute("interval", metadata.interval);
|
174
|
+
span.setAttribute("mode", metadata.mode);
|
175
|
+
try {
|
176
|
+
await method();
|
177
|
+
} catch (error) {
|
178
|
+
span.setStatus({
|
179
|
+
code: SpanStatusCode.ERROR,
|
180
|
+
message: error.message
|
181
|
+
});
|
182
|
+
} finally {
|
183
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
184
|
+
span.end();
|
185
|
+
}
|
186
|
+
}
|
187
|
+
);
|
188
|
+
};
|
165
189
|
if (metadata.mode === "FIXED_DELAY" /* FIXED_DELAY */) {
|
166
190
|
const runTask = async () => {
|
167
191
|
if (!this.isLeader.get(serviceId)) return;
|
168
192
|
try {
|
169
|
-
await
|
193
|
+
await wrappedMethod();
|
170
194
|
} finally {
|
171
195
|
this.timers.set(serviceId, setTimeout(runTask, metadata.interval));
|
172
196
|
}
|
@@ -177,7 +201,7 @@ var Scheduler = class {
|
|
177
201
|
serviceId,
|
178
202
|
setInterval(async () => {
|
179
203
|
if (!this.isLeader.get(serviceId)) return;
|
180
|
-
await
|
204
|
+
await wrappedMethod();
|
181
205
|
}, metadata.interval)
|
182
206
|
);
|
183
207
|
}
|
@@ -380,8 +404,7 @@ var brotli = {
|
|
380
404
|
};
|
381
405
|
|
382
406
|
// core/handler.ts
|
383
|
-
var
|
384
|
-
var logger2 = logs.getLogger("action-handler");
|
407
|
+
var tracer2 = trace.getTracer("action-handler");
|
385
408
|
var ActionHandler = class {
|
386
409
|
constructor(moduleInstance, actionName, metadata, microservice, moduleName) {
|
387
410
|
this.moduleInstance = moduleInstance;
|
@@ -391,46 +414,59 @@ var ActionHandler = class {
|
|
391
414
|
this.moduleName = moduleName;
|
392
415
|
}
|
393
416
|
async handle(req) {
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
} catch (error) {
|
405
|
-
throw new Error(`Invalid request body: ${error.message}`);
|
417
|
+
return await tracer2.startActiveSpan(
|
418
|
+
`handle ${this.moduleName}.${this.actionName}`,
|
419
|
+
async (span) => {
|
420
|
+
span.setAttribute("module", this.moduleName);
|
421
|
+
span.setAttribute("action", this.actionName);
|
422
|
+
try {
|
423
|
+
return await this._handle(req);
|
424
|
+
} finally {
|
425
|
+
span.end();
|
426
|
+
}
|
406
427
|
}
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
428
|
+
);
|
429
|
+
}
|
430
|
+
async _validate(req) {
|
431
|
+
const span = tracer2.startSpan("validate");
|
432
|
+
try {
|
433
|
+
let args;
|
434
|
+
if (typeof req === "string") {
|
412
435
|
try {
|
413
|
-
|
436
|
+
args = Object.values(ejson3.parse(req));
|
414
437
|
} catch (error) {
|
415
|
-
throw new Error(
|
416
|
-
`Invalid argument ${index}: ${error.message}`
|
417
|
-
);
|
438
|
+
throw new Error(`Invalid request body: ${error.message}`);
|
418
439
|
}
|
419
|
-
}
|
440
|
+
} else {
|
441
|
+
args = req;
|
442
|
+
}
|
443
|
+
if (this.metadata.params) {
|
444
|
+
args = args.map((arg, index) => {
|
445
|
+
try {
|
446
|
+
return this.metadata.params[index].parse(arg);
|
447
|
+
} catch (error) {
|
448
|
+
throw new Error(
|
449
|
+
`Invalid argument ${index}: ${error.message}`
|
450
|
+
);
|
451
|
+
}
|
452
|
+
});
|
453
|
+
}
|
454
|
+
const requestHash = hashText(ejson3.stringify(args));
|
455
|
+
span.setAttribute("requestHash", requestHash);
|
456
|
+
return { args, requestHash };
|
457
|
+
} finally {
|
458
|
+
span.end();
|
420
459
|
}
|
421
|
-
|
460
|
+
}
|
461
|
+
async _handle(req) {
|
462
|
+
const span = trace.getActiveSpan();
|
463
|
+
const { args, requestHash } = await this._validate(req);
|
422
464
|
if (!this.metadata.stream && this.metadata.cache && !this.microservice.options.disableCache) {
|
423
465
|
const cacheKey = `${this.moduleName}.${this.actionName}.${requestHash}`;
|
424
466
|
const cached = await this.microservice.cache.get(cacheKey);
|
425
467
|
const now = Date.now();
|
426
468
|
if (cached !== null && (!this.metadata.ttl || cached?.expireAt > now)) {
|
427
|
-
|
428
|
-
this.moduleName,
|
429
|
-
this.actionName,
|
430
|
-
0,
|
431
|
-
true,
|
432
|
-
true
|
433
|
-
);
|
469
|
+
span?.setAttribute("cacheHit", true);
|
434
470
|
return cached.data;
|
435
471
|
}
|
436
472
|
}
|
@@ -440,11 +476,11 @@ var ActionHandler = class {
|
|
440
476
|
args
|
441
477
|
);
|
442
478
|
if (this.metadata.stream) {
|
479
|
+
span?.setAttribute("stream", true);
|
443
480
|
if (!isAsyncIterable(result)) {
|
444
481
|
throw new Error("Stream action must return AsyncIterator");
|
445
482
|
}
|
446
483
|
let count = 0;
|
447
|
-
const self = this;
|
448
484
|
return {
|
449
485
|
[Symbol.asyncIterator]() {
|
450
486
|
const iterator = result[Symbol.asyncIterator]();
|
@@ -452,24 +488,22 @@ var ActionHandler = class {
|
|
452
488
|
async next() {
|
453
489
|
try {
|
454
490
|
const { value, done } = await iterator.next();
|
491
|
+
span?.addEvent("stream.next");
|
455
492
|
if (!done) count++;
|
456
493
|
if (done) {
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
self.actionName,
|
461
|
-
responseTime / count,
|
462
|
-
true
|
463
|
-
);
|
494
|
+
span?.setAttribute("streamCount", count);
|
495
|
+
span?.addEvent("stream.end");
|
496
|
+
span?.end();
|
464
497
|
}
|
465
498
|
return { value, done };
|
466
499
|
} catch (error) {
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
);
|
500
|
+
span?.addEvent("stream.error", { error: error.message });
|
501
|
+
span?.recordException(error);
|
502
|
+
span?.setStatus({
|
503
|
+
code: SpanStatusCode.ERROR,
|
504
|
+
message: error.message
|
505
|
+
});
|
506
|
+
span?.end();
|
473
507
|
throw error;
|
474
508
|
}
|
475
509
|
}
|
@@ -497,36 +531,17 @@ var ActionHandler = class {
|
|
497
531
|
{ ttl: this.metadata.ttl }
|
498
532
|
);
|
499
533
|
}
|
500
|
-
|
501
|
-
|
502
|
-
this.actionName,
|
503
|
-
0,
|
504
|
-
true
|
505
|
-
);
|
534
|
+
span?.setStatus({ code: SpanStatusCode.OK, message: "success" });
|
535
|
+
span?.end();
|
506
536
|
return parsedResult;
|
507
537
|
} catch (error) {
|
508
538
|
if (this.metadata.printError !== false && this.microservice.options.printError !== false) {
|
509
539
|
console.error(`Error in ${this.moduleName}.${this.actionName}:`, error);
|
510
540
|
}
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
service: {
|
515
|
-
id: this.microservice.serviceId,
|
516
|
-
name: this.microservice.options.name,
|
517
|
-
version: this.microservice.options.version,
|
518
|
-
env: this.microservice.options.env
|
519
|
-
},
|
520
|
-
module: this.moduleName,
|
521
|
-
action: this.actionName,
|
522
|
-
params: args,
|
523
|
-
time: Date.now(),
|
524
|
-
error: error.message
|
525
|
-
});
|
526
|
-
}
|
541
|
+
span?.recordException(error);
|
542
|
+
span?.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
543
|
+
span?.end();
|
527
544
|
throw error;
|
528
|
-
} finally {
|
529
|
-
span.end();
|
530
545
|
}
|
531
546
|
}
|
532
547
|
};
|
package/package.json
CHANGED
@@ -1,75 +1,84 @@
|
|
1
|
-
{
|
2
|
-
"name": "imean-service-engine",
|
3
|
-
"version": "1.
|
4
|
-
"description": "microservice engine",
|
5
|
-
"keywords": [
|
6
|
-
"microservice",
|
7
|
-
"websocket",
|
8
|
-
"http",
|
9
|
-
"node"
|
10
|
-
],
|
11
|
-
"author": "imean",
|
12
|
-
"type": "module",
|
13
|
-
"license": "MIT",
|
14
|
-
"repository": {
|
15
|
-
"type": "git",
|
16
|
-
"url": "git+https://git.imean.tech/imean/imean-microservice-framework.git"
|
17
|
-
},
|
18
|
-
"main": "dist/mod.js",
|
19
|
-
"module": "dist/mod.js",
|
20
|
-
"types": "dist/mod.d.ts",
|
21
|
-
"exports": {
|
22
|
-
".": {
|
23
|
-
"types": "./dist/mod.d.ts",
|
24
|
-
"import": "./dist/mod.js",
|
25
|
-
"require": "./dist/mod.cjs"
|
26
|
-
}
|
27
|
-
},
|
28
|
-
"files": [
|
29
|
-
"dist",
|
30
|
-
"README.md",
|
31
|
-
"LICENSE"
|
32
|
-
],
|
33
|
-
"scripts": {
|
34
|
-
"dev": "tsx dev/index.ts",
|
35
|
-
"build": "tsup",
|
36
|
-
"lint": "deno lint",
|
37
|
-
"fmt": "deno fmt",
|
38
|
-
"test": "vitest",
|
39
|
-
"coverage": "vitest --coverage",
|
40
|
-
"prepublishOnly": "npm run build && npm run test"
|
41
|
-
},
|
42
|
-
"dependencies": {
|
43
|
-
"@hono/node-server": "^1.13.7",
|
44
|
-
"@hono/node-ws": "^1.0.6",
|
45
|
-
"
|
46
|
-
"
|
47
|
-
"
|
48
|
-
"
|
49
|
-
"
|
50
|
-
"
|
51
|
-
"
|
52
|
-
"
|
53
|
-
"
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
"@
|
62
|
-
"@
|
63
|
-
"@
|
64
|
-
"
|
65
|
-
"
|
66
|
-
"
|
67
|
-
"
|
68
|
-
"
|
69
|
-
"
|
70
|
-
"
|
71
|
-
|
72
|
-
|
73
|
-
"node": "
|
74
|
-
|
75
|
-
|
1
|
+
{
|
2
|
+
"name": "imean-service-engine",
|
3
|
+
"version": "1.2.0",
|
4
|
+
"description": "microservice engine",
|
5
|
+
"keywords": [
|
6
|
+
"microservice",
|
7
|
+
"websocket",
|
8
|
+
"http",
|
9
|
+
"node"
|
10
|
+
],
|
11
|
+
"author": "imean",
|
12
|
+
"type": "module",
|
13
|
+
"license": "MIT",
|
14
|
+
"repository": {
|
15
|
+
"type": "git",
|
16
|
+
"url": "git+https://git.imean.tech/imean/imean-microservice-framework.git"
|
17
|
+
},
|
18
|
+
"main": "dist/mod.js",
|
19
|
+
"module": "dist/mod.js",
|
20
|
+
"types": "dist/mod.d.ts",
|
21
|
+
"exports": {
|
22
|
+
".": {
|
23
|
+
"types": "./dist/mod.d.ts",
|
24
|
+
"import": "./dist/mod.js",
|
25
|
+
"require": "./dist/mod.cjs"
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"files": [
|
29
|
+
"dist",
|
30
|
+
"README.md",
|
31
|
+
"LICENSE"
|
32
|
+
],
|
33
|
+
"scripts": {
|
34
|
+
"dev": "tsx dev/index.ts",
|
35
|
+
"build": "tsup",
|
36
|
+
"lint": "deno lint",
|
37
|
+
"fmt": "deno fmt",
|
38
|
+
"test": "vitest",
|
39
|
+
"coverage": "vitest --coverage",
|
40
|
+
"prepublishOnly": "npm run build && npm run test"
|
41
|
+
},
|
42
|
+
"dependencies": {
|
43
|
+
"@hono/node-server": "^1.13.7",
|
44
|
+
"@hono/node-ws": "^1.0.6",
|
45
|
+
"dayjs": "^1.11.13",
|
46
|
+
"ejson": "^2.2.3",
|
47
|
+
"etcd3": "^1.1.2",
|
48
|
+
"fs-extra": "^11.3.0",
|
49
|
+
"hono": "^4.6.17",
|
50
|
+
"lru-cache": "^11.0.2",
|
51
|
+
"prettier": "^3.4.2",
|
52
|
+
"winston": "^3.17.0",
|
53
|
+
"zod": "^3.24.1"
|
54
|
+
},
|
55
|
+
"peerDependencies": {
|
56
|
+
"@opentelemetry/api": "^1.x"
|
57
|
+
},
|
58
|
+
"devDependencies": {
|
59
|
+
"@opentelemetry/auto-instrumentations-node": "^0.55.3",
|
60
|
+
"@opentelemetry/exporter-logs-otlp-proto": "^0.57.1",
|
61
|
+
"@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
|
62
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.57.1",
|
63
|
+
"@opentelemetry/instrumentation-winston": "^0.44.0",
|
64
|
+
"@opentelemetry/sdk-logs": "^0.57.1",
|
65
|
+
"@opentelemetry/sdk-metrics": "^1.30.1",
|
66
|
+
"@opentelemetry/sdk-node": "^0.57.1",
|
67
|
+
"@opentelemetry/sdk-trace-node": "^1.30.1",
|
68
|
+
"@opentelemetry/winston-transport": "^0.10.0",
|
69
|
+
"@types/ejson": "^2.2.2",
|
70
|
+
"@types/fs-extra": "^11.0.4",
|
71
|
+
"@types/node": "^20.0.0",
|
72
|
+
"imean-service-client": "^1.5.0",
|
73
|
+
"opentelemetry-instrumentation-fetch-node": "^1.2.3",
|
74
|
+
"tslib": "^2.8.1",
|
75
|
+
"tsup": "^8.0.1",
|
76
|
+
"tsx": "^4.19.2",
|
77
|
+
"typescript": "^5.3.3",
|
78
|
+
"vite-tsconfig-paths": "^5.1.4",
|
79
|
+
"vitest": "^3.0.3"
|
80
|
+
},
|
81
|
+
"engines": {
|
82
|
+
"node": ">=20"
|
83
|
+
}
|
84
|
+
}
|