@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.
@@ -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
- return this.asyncLocalStorage.run({
7
+ const currentContext = this.asyncLocalStorage.getStore();
8
+ const newContext = {
18
9
  abortSignal,
19
10
  requestId,
20
- }, callback);
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 getCurrentContext() {
43
- return this.asyncLocalStorage.getStore() || null;
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;
@@ -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
- // 원본 Promise 처리
57
- promise
58
- .then((result) => {
59
- signal.removeEventListener('abort', abortHandler);
60
- resolve(result);
61
- })
62
- .catch((error) => {
63
- signal.removeEventListener('abort', abortHandler);
64
- reject(error);
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,6 @@
1
+ export declare class ContextPromisePatch {
2
+ private static originalPromise;
3
+ private static isPatched;
4
+ static patch(): void;
5
+ static unpatch(): void;
6
+ }
@@ -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
- exports.RPCInterceptor = RPCInterceptor = __decorate([
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
- // GracefulShutdownService가 완료될 때까지 대기
21
- while (this.gracefulShutdownService.getActiveInterceptorsCount() > 0) {
22
- await new Promise(resolve => {
23
- setTimeout(() => resolve(), 100);
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
- private waitForActiveInterceptors;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolo-croket-dev/core",
3
- "version": "1.7.30",
3
+ "version": "1.7.31",
4
4
  "author": "Yolo Co., Ltd.",
5
5
  "description": "Yolo commonization module",
6
6
  "main": "index.js",