@yolo-croket-dev/core 1.7.30 → 1.7.31
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/functions/abort/abort-context.d.ts +8 -22
- package/functions/abort/abort-context.js +36 -24
- package/functions/abort/abort.js +20 -12
- package/functions/abort/context-promise-patch.d.ts +6 -0
- package/functions/abort/context-promise-patch.js +50 -0
- package/interceptors/rpc.interceptor.d.ts +1 -0
- package/interceptors/rpc.interceptor.js +11 -2
- package/modules/graceful-shutdown/graceful-shutdown.module.js +29 -6
- package/modules/graceful-shutdown/graceful-shutdown.service.d.ts +1 -1
- package/modules/graceful-shutdown/graceful-shutdown.service.js +0 -4
- package/package.json +1 -1
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* AbortSignal을 AsyncLocalStorage를 통해 전역적으로 관리하는 컨텍스트
|
|
4
|
-
*/
|
|
5
2
|
export declare class AbortContext {
|
|
6
3
|
private static asyncLocalStorage;
|
|
7
|
-
/**
|
|
8
|
-
* AbortSignal과 함께 컨텍스트를 실행
|
|
9
|
-
* @param abortSignal 중단 신호
|
|
10
|
-
* @param requestId 요청 ID (디버깅용)
|
|
11
|
-
* @param callback 실행할 콜백 함수
|
|
12
|
-
* @returns 실행 결과
|
|
13
|
-
*/
|
|
14
4
|
static run<T>(abortSignal: AbortSignal, requestId: string, callback: () => T): T;
|
|
15
|
-
/**
|
|
16
|
-
* 현재 컨텍스트의 AbortSignal을 가져옴
|
|
17
|
-
* @returns 현재 AbortSignal 또는 null
|
|
18
|
-
*/
|
|
19
5
|
static getCurrentAbortSignal(): AbortSignal | null;
|
|
20
|
-
/**
|
|
21
|
-
* 현재 컨텍스트의 요청 ID를 가져옴
|
|
22
|
-
* @returns 현재 요청 ID 또는 null
|
|
23
|
-
*/
|
|
24
6
|
static getCurrentRequestId(): string | null;
|
|
25
|
-
/**
|
|
26
|
-
* 현재 컨텍스트 정보를 가져옴
|
|
27
|
-
* @returns 현재 컨텍스트 또는 null
|
|
28
|
-
*/
|
|
29
7
|
static getCurrentContext(): {
|
|
30
8
|
abortSignal: AbortSignal;
|
|
31
9
|
requestId: string;
|
|
32
10
|
} | null;
|
|
11
|
+
/**
|
|
12
|
+
* 컨텍스트가 유효한지 확인
|
|
13
|
+
*/
|
|
14
|
+
static isContextValid(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* 컨텍스트 디버깅 정보
|
|
17
|
+
*/
|
|
18
|
+
static getDebugInfo(): any;
|
|
33
19
|
}
|
|
@@ -2,45 +2,57 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AbortContext = void 0;
|
|
4
4
|
const async_hooks_1 = require("async_hooks");
|
|
5
|
-
/**
|
|
6
|
-
* AbortSignal을 AsyncLocalStorage를 통해 전역적으로 관리하는 컨텍스트
|
|
7
|
-
*/
|
|
8
5
|
class AbortContext {
|
|
9
|
-
/**
|
|
10
|
-
* AbortSignal과 함께 컨텍스트를 실행
|
|
11
|
-
* @param abortSignal 중단 신호
|
|
12
|
-
* @param requestId 요청 ID (디버깅용)
|
|
13
|
-
* @param callback 실행할 콜백 함수
|
|
14
|
-
* @returns 실행 결과
|
|
15
|
-
*/
|
|
16
6
|
static run(abortSignal, requestId, callback) {
|
|
17
|
-
|
|
7
|
+
const currentContext = this.asyncLocalStorage.getStore();
|
|
8
|
+
const newContext = {
|
|
18
9
|
abortSignal,
|
|
19
10
|
requestId,
|
|
20
|
-
|
|
11
|
+
createdAt: Date.now(),
|
|
12
|
+
parentContext: currentContext || undefined,
|
|
13
|
+
};
|
|
14
|
+
return this.asyncLocalStorage.run(newContext, callback);
|
|
21
15
|
}
|
|
22
|
-
/**
|
|
23
|
-
* 현재 컨텍스트의 AbortSignal을 가져옴
|
|
24
|
-
* @returns 현재 AbortSignal 또는 null
|
|
25
|
-
*/
|
|
26
16
|
static getCurrentAbortSignal() {
|
|
27
17
|
const context = this.asyncLocalStorage.getStore();
|
|
28
18
|
return (context === null || context === void 0 ? void 0 : context.abortSignal) || null;
|
|
29
19
|
}
|
|
30
|
-
/**
|
|
31
|
-
* 현재 컨텍스트의 요청 ID를 가져옴
|
|
32
|
-
* @returns 현재 요청 ID 또는 null
|
|
33
|
-
*/
|
|
34
20
|
static getCurrentRequestId() {
|
|
35
21
|
const context = this.asyncLocalStorage.getStore();
|
|
36
22
|
return (context === null || context === void 0 ? void 0 : context.requestId) || null;
|
|
37
23
|
}
|
|
24
|
+
static getCurrentContext() {
|
|
25
|
+
const context = this.asyncLocalStorage.getStore();
|
|
26
|
+
if (context) {
|
|
27
|
+
return {
|
|
28
|
+
abortSignal: context.abortSignal,
|
|
29
|
+
requestId: context.requestId,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
38
34
|
/**
|
|
39
|
-
*
|
|
40
|
-
* @returns 현재 컨텍스트 또는 null
|
|
35
|
+
* 컨텍스트가 유효한지 확인
|
|
41
36
|
*/
|
|
42
|
-
static
|
|
43
|
-
|
|
37
|
+
static isContextValid() {
|
|
38
|
+
const context = this.asyncLocalStorage.getStore();
|
|
39
|
+
return !!((context === null || context === void 0 ? void 0 : context.abortSignal) && (context === null || context === void 0 ? void 0 : context.requestId));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 컨텍스트 디버깅 정보
|
|
43
|
+
*/
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
static getDebugInfo() {
|
|
46
|
+
var _a;
|
|
47
|
+
const context = this.asyncLocalStorage.getStore();
|
|
48
|
+
return {
|
|
49
|
+
hasContext: !!context,
|
|
50
|
+
requestId: context === null || context === void 0 ? void 0 : context.requestId,
|
|
51
|
+
hasSignal: !!(context === null || context === void 0 ? void 0 : context.abortSignal),
|
|
52
|
+
isAborted: (_a = context === null || context === void 0 ? void 0 : context.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted,
|
|
53
|
+
createdAt: context === null || context === void 0 ? void 0 : context.createdAt,
|
|
54
|
+
age: context ? Date.now() - context.createdAt : 0,
|
|
55
|
+
};
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
exports.AbortContext = AbortContext;
|
package/functions/abort/abort.js
CHANGED
|
@@ -39,30 +39,38 @@ exports.throwIfAborted = throwIfAborted;
|
|
|
39
39
|
*/
|
|
40
40
|
function withAbortSignal(promise, abortSignal) {
|
|
41
41
|
const signal = abortSignal || getCurrentAbortSignal();
|
|
42
|
+
const currentContext = abort_context_1.AbortContext.getCurrentContext();
|
|
42
43
|
if (!signal) {
|
|
43
44
|
return promise;
|
|
44
45
|
}
|
|
45
46
|
return new Promise((resolve, reject) => {
|
|
46
|
-
// 이미 중단된 경우
|
|
47
47
|
if (signal.aborted) {
|
|
48
48
|
reject(new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다'));
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
// 중단 이벤트 리스너 등록
|
|
52
51
|
const abortHandler = () => {
|
|
53
52
|
reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
|
|
54
53
|
};
|
|
55
54
|
signal.addEventListener('abort', abortHandler, { once: true });
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
// Promise 실행 - 컨텍스트 전파 보장
|
|
56
|
+
const processPromise = async () => {
|
|
57
|
+
try {
|
|
58
|
+
const result = await promise;
|
|
59
|
+
signal.removeEventListener('abort', abortHandler);
|
|
60
|
+
resolve(result);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
signal.removeEventListener('abort', abortHandler);
|
|
64
|
+
reject(error);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
// 현재 컨텍스트가 있으면 전파
|
|
68
|
+
if (currentContext) {
|
|
69
|
+
abort_context_1.AbortContext.run(currentContext.abortSignal, currentContext.requestId, processPromise);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
processPromise();
|
|
73
|
+
}
|
|
66
74
|
});
|
|
67
75
|
}
|
|
68
76
|
exports.withAbortSignal = withAbortSignal;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContextPromisePatch = void 0;
|
|
4
|
+
const abort_context_1 = require("./abort-context");
|
|
5
|
+
class ContextPromisePatch {
|
|
6
|
+
static patch() {
|
|
7
|
+
if (this.isPatched) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
this.originalPromise = global.Promise;
|
|
11
|
+
// Promise 생성자 패치 - 컨텍스트가 있을 때만 동작
|
|
12
|
+
global.Promise = new Proxy(global.Promise, {
|
|
13
|
+
construct(target, args) {
|
|
14
|
+
const currentContext = abort_context_1.AbortContext.getCurrentContext();
|
|
15
|
+
// 컨텍스트가 없으면 원본 Promise 사용
|
|
16
|
+
if (!currentContext) {
|
|
17
|
+
return Reflect.construct(target, args);
|
|
18
|
+
}
|
|
19
|
+
const abortSignal = currentContext.abortSignal;
|
|
20
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
21
|
+
return Promise.reject(new Error('REQUEST_ABORTED: Promise가 중단되었습니다'));
|
|
22
|
+
}
|
|
23
|
+
// eslint-disable-next-line new-cap
|
|
24
|
+
const promise = Reflect.construct(target, args);
|
|
25
|
+
// 컨텍스트가 있는 경우에만 중단 확인 추가
|
|
26
|
+
return promise.then((result) => {
|
|
27
|
+
if (abortSignal.aborted) {
|
|
28
|
+
throw new Error('REQUEST_ABORTED: Promise 실행 중 중단되었습니다');
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}, (error) => {
|
|
32
|
+
if (abortSignal.aborted) {
|
|
33
|
+
throw new Error('REQUEST_ABORTED: Promise 실행 중 중단되었습니다');
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
this.isPatched = true;
|
|
40
|
+
}
|
|
41
|
+
static unpatch() {
|
|
42
|
+
if (!this.isPatched) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
global.Promise = this.originalPromise;
|
|
46
|
+
this.isPatched = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.ContextPromisePatch = ContextPromisePatch;
|
|
50
|
+
ContextPromisePatch.isPatched = false;
|
|
@@ -7,6 +7,7 @@ export declare class RPCInterceptor implements NestInterceptor {
|
|
|
7
7
|
private readonly gracefulShutdownService;
|
|
8
8
|
protected readonly DEFAULT_TIMEOUT: number;
|
|
9
9
|
protected readonly DEFAULT_TIMEOUT_MESSAGE = "\uC694\uCCAD\uC774 \uD0C0\uC784\uC544\uC6C3\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
10
|
+
private static isPatched;
|
|
10
11
|
constructor(reflector: Reflector, gracefulShutdownService: GracefulShutdownService);
|
|
11
12
|
private maxLengthOverShorten;
|
|
12
13
|
/** 메시지가 재전송되었을때에, 재시도 Count가 명시되어 있지 않을때에 처리 할지 여부를 결정하는 함수 */
|
|
@@ -34,6 +34,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
34
34
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
|
+
var RPCInterceptor_1;
|
|
37
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
39
|
exports.RPCInterceptor = void 0;
|
|
39
40
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -46,11 +47,12 @@ const msa_logger_1 = __importStar(require("@yolo-croket-dev/msa-logger"));
|
|
|
46
47
|
const amqplib_1 = __importDefault(require("amqplib"));
|
|
47
48
|
const functions_1 = require("../functions");
|
|
48
49
|
const exceptions_1 = require("../exceptions");
|
|
50
|
+
const context_promise_patch_1 = require("../functions/abort/context-promise-patch");
|
|
49
51
|
const graceful_shutdown_service_1 = require("../modules/graceful-shutdown/graceful-shutdown.service");
|
|
50
52
|
const msaLogger = new msa_logger_1.default(msa_logger_1.ServerTypeEnum.SERVICE);
|
|
51
53
|
// const MAX_CUT_LENGTH = 500;
|
|
52
54
|
const MAX_LENGTH = 300;
|
|
53
|
-
let RPCInterceptor = class RPCInterceptor {
|
|
55
|
+
let RPCInterceptor = RPCInterceptor_1 = class RPCInterceptor {
|
|
54
56
|
constructor(reflector, gracefulShutdownService) {
|
|
55
57
|
this.reflector = reflector;
|
|
56
58
|
this.gracefulShutdownService = gracefulShutdownService;
|
|
@@ -274,6 +276,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
274
276
|
}
|
|
275
277
|
const sendMessage = Buffer.from(JSON.stringify(abstractAmqpHandlerData));
|
|
276
278
|
if (!isAck) {
|
|
279
|
+
const currentContext = functions_1.AbortContext.getCurrentContext();
|
|
277
280
|
// 에러 전달
|
|
278
281
|
msaLogger.error('에러 응답 전송 처리', { traceId: execInterceptor.traceId }, {
|
|
279
282
|
error: {
|
|
@@ -285,6 +288,7 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
285
288
|
staticRetryCount,
|
|
286
289
|
},
|
|
287
290
|
messagePattern: method,
|
|
291
|
+
currentContext: currentContext && currentContext.requestId ? currentContext.requestId : null,
|
|
288
292
|
});
|
|
289
293
|
channel.sendToQueue(message.properties.replyTo, sendMessage, { correlationId: message.properties.correlationId });
|
|
290
294
|
}
|
|
@@ -319,6 +323,10 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
319
323
|
return new rxjs_1.Observable();
|
|
320
324
|
}
|
|
321
325
|
}
|
|
326
|
+
if (!RPCInterceptor_1.isPatched) {
|
|
327
|
+
context_promise_patch_1.ContextPromisePatch.patch();
|
|
328
|
+
RPCInterceptor_1.isPatched = true;
|
|
329
|
+
}
|
|
322
330
|
this.gracefulShutdownService.pushActiveInterceptors(execInterceptor);
|
|
323
331
|
// eslint-disable-next-line consistent-return
|
|
324
332
|
return functions_1.AbortContext.run(abortController.signal, execInterceptor.traceId, () => next
|
|
@@ -358,7 +366,8 @@ let RPCInterceptor = class RPCInterceptor {
|
|
|
358
366
|
}
|
|
359
367
|
};
|
|
360
368
|
exports.RPCInterceptor = RPCInterceptor;
|
|
361
|
-
|
|
369
|
+
RPCInterceptor.isPatched = false;
|
|
370
|
+
exports.RPCInterceptor = RPCInterceptor = RPCInterceptor_1 = __decorate([
|
|
362
371
|
(0, common_1.Injectable)(),
|
|
363
372
|
__metadata("design:paramtypes", [core_1.Reflector,
|
|
364
373
|
graceful_shutdown_service_1.GracefulShutdownService])
|
|
@@ -1,28 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
2
18
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
19
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
20
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
21
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
22
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
23
|
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
|
+
if (mod && mod.__esModule) return mod;
|
|
26
|
+
var result = {};
|
|
27
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
28
|
+
__setModuleDefault(result, mod);
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
8
31
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
32
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
33
|
};
|
|
11
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
35
|
exports.GracefulShutdownModule = void 0;
|
|
13
36
|
const common_1 = require("@nestjs/common");
|
|
37
|
+
const msa_logger_1 = __importStar(require("@yolo-croket-dev/msa-logger"));
|
|
14
38
|
const graceful_shutdown_service_1 = require("./graceful-shutdown.service");
|
|
39
|
+
const msaLogger = new msa_logger_1.default(msa_logger_1.ServerTypeEnum.SERVICE);
|
|
15
40
|
let GracefulShutdownModule = class GracefulShutdownModule {
|
|
16
41
|
constructor(gracefulShutdownService) {
|
|
17
42
|
this.gracefulShutdownService = gracefulShutdownService;
|
|
18
43
|
}
|
|
19
44
|
async onModuleDestroy() {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
}
|
|
45
|
+
// onApplicationShutdown에서 모든 activeInterceptors 완료를 기다림
|
|
46
|
+
// 이렇게 하면 onModuleDestroy가 호출되기 전에 모든 이벤트가 처리됨
|
|
47
|
+
await this.gracefulShutdownService.waitForActiveInterceptors();
|
|
48
|
+
msaLogger.info('GracefulShutdownService.onModuleDestroy: all interceptors finished, shutting down', { traceId: 'ON_MODULE_DESTROY' });
|
|
26
49
|
}
|
|
27
50
|
};
|
|
28
51
|
exports.GracefulShutdownModule = GracefulShutdownModule;
|
|
@@ -18,6 +18,6 @@ export declare class GracefulShutdownService implements OnApplicationShutdown, O
|
|
|
18
18
|
getShutdownStatus(): boolean;
|
|
19
19
|
onApplicationShutdown(signal?: string): Promise<void>;
|
|
20
20
|
onModuleDestroy(): Promise<void>;
|
|
21
|
-
|
|
21
|
+
waitForActiveInterceptors(): Promise<void>;
|
|
22
22
|
}
|
|
23
23
|
export {};
|
|
@@ -80,10 +80,6 @@ let GracefulShutdownService = class GracefulShutdownService {
|
|
|
80
80
|
this.shutdownSignal = signal;
|
|
81
81
|
this.isApplicationExit = true;
|
|
82
82
|
msaLogger.debug(`GracefulShutdownService.onApplicationShutdown signal=${signal}`, { traceId: 'ON_APPLICATION_SHUTDOWN' });
|
|
83
|
-
// onApplicationShutdown에서 모든 activeInterceptors 완료를 기다림
|
|
84
|
-
// 이렇게 하면 onModuleDestroy가 호출되기 전에 모든 이벤트가 처리됨
|
|
85
|
-
await this.waitForActiveInterceptors();
|
|
86
|
-
msaLogger.info('GracefulShutdownService.onApplicationShutdown: all interceptors finished, shutting down', { traceId: 'ON_APPLICATION_SHUTDOWN' });
|
|
87
83
|
// 모든 이벤트 처리 완료 후 종료
|
|
88
84
|
process.exit(0);
|
|
89
85
|
}
|