@yolo-croket-dev/core 1.7.25-slowquery.29 → 1.7.25-slowquery.3

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,3 +1,2 @@
1
1
  export * from './rpc-interceptor-scope.decorator';
2
2
  export * from './rpc-transform.decorator';
3
- export * from './abort-event.decorator';
@@ -16,4 +16,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./rpc-interceptor-scope.decorator"), exports);
18
18
  __exportStar(require("./rpc-transform.decorator"), exports);
19
- __exportStar(require("./abort-event.decorator"), exports);
@@ -4,5 +4,3 @@ export * from './amqp-publish.function';
4
4
  export * from './rewrite-file-value.function';
5
5
  export * from './amqp-call-manager.function';
6
6
  export * from './extract-redis-url.function';
7
- export * from './abort';
8
- export * from './abort-context';
@@ -20,5 +20,3 @@ __exportStar(require("./amqp-publish.function"), exports);
20
20
  __exportStar(require("./rewrite-file-value.function"), exports);
21
21
  __exportStar(require("./amqp-call-manager.function"), exports);
22
22
  __exportStar(require("./extract-redis-url.function"), exports);
23
- __exportStar(require("./abort"), exports);
24
- __exportStar(require("./abort-context"), exports);
@@ -188,7 +188,7 @@ let LoggingServiceInterceptor = class LoggingServiceInterceptor {
188
188
  }));
189
189
  }
190
190
  };
191
- exports.LoggingServiceInterceptor = LoggingServiceInterceptor;
192
- exports.LoggingServiceInterceptor = LoggingServiceInterceptor = __decorate([
191
+ LoggingServiceInterceptor = __decorate([
193
192
  (0, common_1.Injectable)()
194
193
  ], LoggingServiceInterceptor);
194
+ exports.LoggingServiceInterceptor = LoggingServiceInterceptor;
@@ -5,8 +5,6 @@ import { GracefulShutdownService } from '../modules/graceful-shutdown/graceful-s
5
5
  export declare class RPCInterceptor implements NestInterceptor {
6
6
  private readonly reflector;
7
7
  private readonly gracefulShutdownService;
8
- protected readonly DEFAULT_TIMEOUT = 1000;
9
- protected readonly DEFAULT_TIMEOUT_MESSAGE = "\uC694\uCCAD\uC774 \uD0C0\uC784\uC544\uC6C3\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
10
8
  constructor(reflector: Reflector, gracefulShutdownService: GracefulShutdownService);
11
9
  private maxLengthOverShorten;
12
10
  /** 메시지가 재전송되었을때에, 재시도 Count가 명시되어 있지 않을때에 처리 할지 여부를 결정하는 함수 */
@@ -44,9 +44,11 @@ const rxjs_1 = require("rxjs");
44
44
  const operators_1 = require("rxjs/operators");
45
45
  const msa_logger_1 = __importStar(require("@yolo-croket-dev/msa-logger"));
46
46
  const amqplib_1 = __importDefault(require("amqplib"));
47
+ // import Logger from '@yolo-croket-dev/logger';
47
48
  const functions_1 = require("../functions");
48
49
  const exceptions_1 = require("../exceptions");
49
50
  const graceful_shutdown_service_1 = require("../modules/graceful-shutdown/graceful-shutdown.service");
51
+ // const logger = Logger.createLogger('RPCInterceptor');
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;
@@ -54,9 +56,6 @@ let RPCInterceptor = class RPCInterceptor {
54
56
  constructor(reflector, gracefulShutdownService) {
55
57
  this.reflector = reflector;
56
58
  this.gracefulShutdownService = gracefulShutdownService;
57
- this.DEFAULT_TIMEOUT = 1000; // 29분 타임아웃
58
- // protected readonly DEFAULT_TIMEOUT = 1000 * 60 * 29; // 29분 타임아웃
59
- this.DEFAULT_TIMEOUT_MESSAGE = '요청이 타임아웃되었습니다.';
60
59
  }
61
60
  maxLengthOverShorten(data) {
62
61
  if (typeof data !== 'string') {
@@ -147,12 +146,48 @@ let RPCInterceptor = class RPCInterceptor {
147
146
  }
148
147
  return obj;
149
148
  }
149
+ // private keyCutter(data: string) {
150
+ // try {
151
+ // if (data) {
152
+ // data = JSON.parse(data);
153
+ // } else {
154
+ // return data;
155
+ // }
156
+ // } catch (err) {
157
+ // return data;
158
+ // }
159
+ // for (const key of Object.keys(data)) {
160
+ // const type = typeof data[key as any];
161
+ // if (type === 'string') {
162
+ // if (data[key as any].length > MAX_CUT_LENGTH) {
163
+ // (data[key as any] as any) = `${data[key as any].slice(0, MAX_CUT_LENGTH)}...`;
164
+ // }
165
+ // } else if (type === 'object') {
166
+ // if (data[key as any] === null) {
167
+ // (data[key as any] as any) = null;
168
+ // } else {
169
+ // (data[key as any] as any) = this.keyCutter(data[key as any]);
170
+ // }
171
+ // }
172
+ // }
173
+ // return data;
174
+ // }
150
175
  async intercept(context, next) {
151
176
  var _a, _b, _c, _d, _e;
152
177
  const options = this.reflector.get('rpcInterceptorScope', context.getHandler());
153
178
  const args = context.getArgs();
154
179
  const arg = args[0];
155
180
  const rmq = args[1];
181
+ // 메시지에서 AbortController 가져오기
182
+ const abortController = rmq.args[0].abortController;
183
+ // const messageId = (rmq as any).messageId;
184
+ // if (!abortController) {
185
+ // // AbortController가 없으면 기본 처리
186
+ // return next.handle().pipe(
187
+ // timeout(1000 * 60 * 29),
188
+ // // ... 기존 로직
189
+ // );
190
+ // }
156
191
  const channel = rmq.getChannelRef();
157
192
  const message = rmq.getMessage();
158
193
  const now = Date.now();
@@ -169,7 +204,6 @@ let RPCInterceptor = class RPCInterceptor {
169
204
  correlationId,
170
205
  isMessageAck: false,
171
206
  };
172
- const abortController = new AbortController();
173
207
  /** consume 메시지 전부 cancel */
174
208
  if (this.gracefulShutdownService.isApplicationExit) {
175
209
  msaLogger.debug('RPCInterceptor isApplicationExit called', { traceId: 'IS_APPLICATION_EXIT' });
@@ -230,6 +264,13 @@ let RPCInterceptor = class RPCInterceptor {
230
264
  errMessage,
231
265
  errAction,
232
266
  };
267
+ // const isTimeoutError = err instanceof Error && err.name === 'TimeoutError';
268
+ // if (isTimeoutError) {
269
+ // channel.nack(message, false, true);
270
+ // message.properties.replyTo = undefined; /** replyTo가 존재하면 sendToQueue가 호출되므로, 삭제해주기 */
271
+ // execInterceptor.isMessageAck = true;
272
+ // return EMPTY;
273
+ // }
233
274
  if (!Number.isNaN(staticRetryCount)
234
275
  && staticRetryCount
235
276
  && err.isRetry !== false) {
@@ -262,6 +303,11 @@ let RPCInterceptor = class RPCInterceptor {
262
303
  const messageContent = message.content.toString();
263
304
  /** 파일 객체 처리 */
264
305
  (0, functions_1.reWriteFileValue)((_a = messageContent === null || messageContent === void 0 ? void 0 : messageContent.data) === null || _a === void 0 ? void 0 : _a.file);
306
+ // logger.debug(`<= (error:${traceId}): ${JSON.stringify(sendMessage.toString())}`, {
307
+ // ...logParam,
308
+ // duration,
309
+ // isError: true,
310
+ // });
265
311
  if (!noAck && !isAck) {
266
312
  channel.ack(message);
267
313
  execInterceptor.isMessageAck = true;
@@ -290,32 +336,39 @@ let RPCInterceptor = class RPCInterceptor {
290
336
  return new rxjs_1.Observable();
291
337
  }
292
338
  }
339
+ if (content.pattern === 'UPLOAD_FILE') {
340
+ if (content.data.payload.file) {
341
+ content.data.payload.file = `${content.data.payload.file.slice(0, 30)}...`;
342
+ }
343
+ // logger.debug(`=> (${traceId}): ${JSON.stringify(this.keyCutter(JSON.stringify(content)))}`);
344
+ }
345
+ else {
346
+ // logger.debug(`=> (${traceId}): ${JSON.stringify(this.keyCutter(JSON.stringify(content)))}`);
347
+ }
293
348
  this.gracefulShutdownService.pushActiveInterceptors(execInterceptor);
294
349
  // eslint-disable-next-line consistent-return
295
- return functions_1.AbortContext.run(abortController.signal, execInterceptor.traceId, () => next
350
+ return next
296
351
  .handle()
297
- .pipe((0, operators_1.timeout)({
298
- each: this.DEFAULT_TIMEOUT,
299
- with: () => {
300
- console.log('RPCInterceptor timeout 발생');
301
- msaLogger.error('RPCInterceptor timeout 발생', { traceId: execInterceptor.traceId }, { message: this.DEFAULT_TIMEOUT_MESSAGE });
302
- abortController.abort();
303
- return (0, rxjs_1.throwError)(() => new Error(this.DEFAULT_TIMEOUT_MESSAGE));
304
- },
305
- }), (0, operators_1.catchError)((error) => {
306
- // eslint-disable-next-line no-console
307
- console.log('AbortInterceptor: 에러 발생', {
308
- error: error.message,
309
- isAborted: abortController.signal.aborted,
310
- });
311
- // abort된 경우와 일반 에러를 구분
312
- if (abortController.signal.aborted) {
313
- // 추가되어야 하는가?
314
- return (0, rxjs_1.throwError)(() => new Error(`REQUEST_ABORTED: ${this.DEFAULT_TIMEOUT_MESSAGE}`));
315
- }
316
- return (0, rxjs_1.throwError)(() => error);
317
- }), (0, operators_1.tap)({
352
+ .pipe(
353
+ // AbortController 신호 감지
354
+ (0, operators_1.takeUntil)(new rxjs_1.Observable(subscriber => {
355
+ const abortHandler = () => {
356
+ console.log('RPC Interceptor abortHandler called');
357
+ subscriber.next();
358
+ subscriber.complete();
359
+ };
360
+ console.log('RPC Interceptor abortHandler addEventListener');
361
+ abortController.signal.addEventListener('abort', abortHandler, { once: true });
362
+ // 정리 함수
363
+ return () => {
364
+ abortController.signal.removeEventListener('abort', abortHandler);
365
+ };
366
+ })),
367
+ // timeout(1000 * 60 * 29), // 30분 타임아웃
368
+ (0, operators_1.timeout)(1000 * 30), // 30분 타임아웃
369
+ (0, operators_1.tap)({
318
370
  next: async (val) => {
371
+ console.log('RPC Interceptor next called');
319
372
  val.isError = false;
320
373
  // const duration = Date.now() - now;
321
374
  // logger.debug(`<= (success:${traceId}): ${val.message}`, {
@@ -339,9 +392,12 @@ let RPCInterceptor = class RPCInterceptor {
339
392
  // eslint-disable-next-line consistent-return
340
393
  (0, operators_1.catchError)(async (err) => {
341
394
  // timeout 에러 처리
342
- console.log('RPCInterceptor catchError 발생', err);
343
- const isTimeoutError = err instanceof Error && (err.name === 'TimeoutError' || err.message === this.DEFAULT_TIMEOUT_MESSAGE);
395
+ const isTimeoutError = err instanceof Error && err.name === 'TimeoutError';
344
396
  if (isTimeoutError) {
397
+ if (abortController) {
398
+ abortController.abort();
399
+ throw new Error('Message aborted due to timeout');
400
+ }
345
401
  msaLogger.error('[타임 아웃] 메시지 재전송 처리', { traceId: execInterceptor.traceId }, {
346
402
  error: {
347
403
  message: err.message,
@@ -371,12 +427,12 @@ let RPCInterceptor = class RPCInterceptor {
371
427
  }
372
428
  }), (0, operators_1.finalize)(() => {
373
429
  this.gracefulShutdownService.popActiveInterceptors(execInterceptor);
374
- })));
430
+ }));
375
431
  }
376
432
  };
377
- exports.RPCInterceptor = RPCInterceptor;
378
- exports.RPCInterceptor = RPCInterceptor = __decorate([
433
+ RPCInterceptor = __decorate([
379
434
  (0, common_1.Injectable)(),
380
435
  __metadata("design:paramtypes", [core_1.Reflector,
381
436
  graceful_shutdown_service_1.GracefulShutdownService])
382
437
  ], RPCInterceptor);
438
+ exports.RPCInterceptor = RPCInterceptor;
@@ -18,10 +18,9 @@ var ErrActionTypeEnum;
18
18
  ErrActionTypeEnum["LEGACY_SHOW_TOAST"] = "LEGACY_SHOW_TOAST";
19
19
  ErrActionTypeEnum["FORCE_UPDATE"] = "FORCE_UPDATE";
20
20
  ErrActionTypeEnum["UPDATE_ACCESS_TOKEN"] = "UPDATE_ACCESS_TOKEN";
21
- })(ErrActionTypeEnum || (exports.ErrActionTypeEnum = ErrActionTypeEnum = {}));
21
+ })(ErrActionTypeEnum = exports.ErrActionTypeEnum || (exports.ErrActionTypeEnum = {}));
22
22
  class ErrAction {
23
23
  }
24
- exports.ErrAction = ErrAction;
25
24
  __decorate([
26
25
  (0, swagger_1.ApiProperty)({
27
26
  description: '액션 타입',
@@ -58,9 +57,9 @@ __decorate([
58
57
  }),
59
58
  __metadata("design:type", String)
60
59
  ], ErrAction.prototype, "webViewUrl", void 0);
60
+ exports.ErrAction = ErrAction;
61
61
  class ErrInfo {
62
62
  }
63
- exports.ErrInfo = ErrInfo;
64
63
  __decorate([
65
64
  (0, swagger_1.ApiProperty)({
66
65
  description: '에러 종류 식별자',
@@ -82,3 +81,4 @@ __decorate([
82
81
  }),
83
82
  __metadata("design:type", ErrAction)
84
83
  ], ErrInfo.prototype, "action", void 0);
84
+ exports.ErrInfo = ErrInfo;
@@ -25,8 +25,7 @@ let GracefulShutdownModule = class GracefulShutdownModule {
25
25
  }
26
26
  }
27
27
  };
28
- exports.GracefulShutdownModule = GracefulShutdownModule;
29
- exports.GracefulShutdownModule = GracefulShutdownModule = __decorate([
28
+ GracefulShutdownModule = __decorate([
30
29
  (0, common_1.Global)(),
31
30
  (0, common_1.Module)({
32
31
  providers: [graceful_shutdown_service_1.GracefulShutdownService],
@@ -34,3 +33,4 @@ exports.GracefulShutdownModule = GracefulShutdownModule = __decorate([
34
33
  }),
35
34
  __metadata("design:paramtypes", [graceful_shutdown_service_1.GracefulShutdownService])
36
35
  ], GracefulShutdownModule);
36
+ exports.GracefulShutdownModule = GracefulShutdownModule;
@@ -1,7 +1,7 @@
1
1
  import { RmqOptions, ServerRMQ } from '@nestjs/microservices';
2
2
  import { Channel } from 'amqp-connection-manager';
3
3
  import { ConsumeMessage } from 'amqplib';
4
- export type GracefulRmqOptions = RmqOptions['options'] & {
4
+ export declare type GracefulRmqOptions = RmqOptions['options'] & {
5
5
  waitingEndingHandlersTimeoutMs?: number;
6
6
  waitingEndingHandlersIntervalMs?: number;
7
7
  };
@@ -6,6 +6,8 @@ const node_util_1 = require("node:util");
6
6
  const sleep = (0, node_util_1.promisify)(setTimeout);
7
7
  const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS = 5000;
8
8
  const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS = 500;
9
+ // const RMQ_DEFAULT_MESSAGE_TIMEOUT_MS = 1000 * 60 * 29; // 29분
10
+ const RMQ_DEFAULT_MESSAGE_TIMEOUT_MS = 1000 * 2; // 2분
9
11
  class GracefulServerRMQ extends microservices_1.ServerRMQ {
10
12
  constructor(options) {
11
13
  super(options);
@@ -36,10 +38,58 @@ class GracefulServerRMQ extends microservices_1.ServerRMQ {
36
38
  }
37
39
  }
38
40
  async handleMessage(message, channel) {
41
+ const messageId = message.properties.messageId || message.properties.correlationId || `msg_${Date.now()}_${Math.random()}`;
42
+ // AbortController 생성
43
+ const abortController = new AbortController();
44
+ // 타임아웃 설정
45
+ const timeoutId = setTimeout(() => {
46
+ console.log(`Message timeout: ${messageId}`);
47
+ abortController.abort();
48
+ }, RMQ_DEFAULT_MESSAGE_TIMEOUT_MS);
49
+ console.log('timeout 설정', timeoutId);
39
50
  this.runningMessages += 1;
40
- return super.handleMessage(message, channel).finally(() => {
51
+ try {
52
+ // AbortController를 메시지에 추가
53
+ // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
54
+ message.abortController = abortController;
55
+ // eslint-disable-next-line no-param-reassign
56
+ message.messageId = messageId;
57
+ // abort 상태 확인
58
+ if (abortController.signal.aborted) {
59
+ console.log(`Message already aborted: ${messageId}`);
60
+ this.runningMessages -= 1;
61
+ return;
62
+ }
63
+ // Promise.race를 사용하여 abort와 handleMessage 경쟁
64
+ const handleMessagePromise = super.handleMessage(message, channel);
65
+ const abortPromise = new Promise((_, reject) => {
66
+ // 1초마다 abort 상태를 확인
67
+ const interval = setInterval(() => {
68
+ if (abortController.signal.aborted) {
69
+ reject(new Error(`Message aborted: ${messageId}`));
70
+ clearInterval(interval);
71
+ }
72
+ }, 1000);
73
+ });
74
+ // 둘 중 하나가 먼저 완료되면 그 결과를 반환
75
+ await Promise.race([
76
+ handleMessagePromise,
77
+ abortPromise,
78
+ ]);
79
+ }
80
+ catch (error) {
81
+ if (error.message.includes('Message aborted')) {
82
+ console.log(`Message processing aborted: ${messageId}`);
83
+ // abort로 인한 에러는 정상적으로 처리된 것으로 간주
84
+ }
85
+ else {
86
+ throw error;
87
+ }
88
+ }
89
+ finally {
41
90
  this.runningMessages -= 1;
42
- });
91
+ clearTimeout(timeoutId);
92
+ }
43
93
  }
44
94
  async close() {
45
95
  this.closing = true;
@@ -137,7 +137,7 @@ let GracefulShutdownService = class GracefulShutdownService {
137
137
  });
138
138
  }
139
139
  };
140
- exports.GracefulShutdownService = GracefulShutdownService;
141
- exports.GracefulShutdownService = GracefulShutdownService = __decorate([
140
+ GracefulShutdownService = __decorate([
142
141
  (0, common_1.Injectable)()
143
142
  ], GracefulShutdownService);
143
+ exports.GracefulShutdownService = GracefulShutdownService;
@@ -15,10 +15,10 @@ const redisProvider = {
15
15
  };
16
16
  let RedisModule = class RedisModule {
17
17
  };
18
- exports.RedisModule = RedisModule;
19
- exports.RedisModule = RedisModule = __decorate([
18
+ RedisModule = __decorate([
20
19
  (0, common_1.Module)({
21
20
  providers: [redisProvider],
22
21
  exports: [redisProvider],
23
22
  })
24
23
  ], RedisModule);
24
+ exports.RedisModule = RedisModule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolo-croket-dev/core",
3
- "version": "1.7.25-slowquery.29",
3
+ "version": "1.7.25-slowquery.3",
4
4
  "author": "Yolo Co., Ltd.",
5
5
  "description": "Yolo commonization module",
6
6
  "main": "index.js",
@@ -31,8 +31,8 @@ let ValidationPipe = class ValidationPipe {
31
31
  return value;
32
32
  }
33
33
  };
34
- exports.ValidationPipe = ValidationPipe;
35
- exports.ValidationPipe = ValidationPipe = __decorate([
34
+ ValidationPipe = __decorate([
36
35
  (0, common_1.Injectable)(),
37
36
  __metadata("design:paramtypes", [Object])
38
37
  ], ValidationPipe);
38
+ exports.ValidationPipe = ValidationPipe;
@@ -1,4 +0,0 @@
1
- /**
2
- * 이벤트 기반으로 AbortSignal을 감지하는 데코레이터 (Interval 없음)
3
- */
4
- export declare function AbortEvent(): (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AbortEvent = void 0;
4
- // src/modules/rpc-testing/decorators/abort-event.decorator.ts
5
- const abort_1 = require("../functions/abort");
6
- /**
7
- * 이벤트 기반으로 AbortSignal을 감지하는 데코레이터 (Interval 없음)
8
- */
9
- function AbortEvent() {
10
- return function (target, propertyKey, descriptor) {
11
- const originalMethod = descriptor.value;
12
- // eslint-disable-next-line no-param-reassign
13
- descriptor.value = async function (...args) {
14
- const abortSignal = (0, abort_1.getCurrentAbortSignal)();
15
- if (!abortSignal) {
16
- return originalMethod.apply(this, args);
17
- }
18
- if (abortSignal.aborted) {
19
- throw new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다');
20
- }
21
- // 중단 확인을 위한 인터벌
22
- const intervalId = setInterval(() => {
23
- if (abortSignal.aborted) {
24
- clearInterval(intervalId);
25
- throw new Error('REQUEST_ABORTED: 작업이 중단되었습니다');
26
- }
27
- }, 1000);
28
- try {
29
- const result = await originalMethod.apply(this, args);
30
- clearInterval(intervalId);
31
- return result;
32
- }
33
- catch (error) {
34
- clearInterval(intervalId);
35
- throw error;
36
- }
37
- };
38
- return descriptor;
39
- };
40
- }
41
- exports.AbortEvent = AbortEvent;
@@ -1,33 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * AbortSignal을 AsyncLocalStorage를 통해 전역적으로 관리하는 컨텍스트
4
- */
5
- export declare class AbortContext {
6
- private static asyncLocalStorage;
7
- /**
8
- * AbortSignal과 함께 컨텍스트를 실행
9
- * @param abortSignal 중단 신호
10
- * @param requestId 요청 ID (디버깅용)
11
- * @param callback 실행할 콜백 함수
12
- * @returns 실행 결과
13
- */
14
- static run<T>(abortSignal: AbortSignal, requestId: string, callback: () => T): T;
15
- /**
16
- * 현재 컨텍스트의 AbortSignal을 가져옴
17
- * @returns 현재 AbortSignal 또는 null
18
- */
19
- static getCurrentAbortSignal(): AbortSignal | null;
20
- /**
21
- * 현재 컨텍스트의 요청 ID를 가져옴
22
- * @returns 현재 요청 ID 또는 null
23
- */
24
- static getCurrentRequestId(): string | null;
25
- /**
26
- * 현재 컨텍스트 정보를 가져옴
27
- * @returns 현재 컨텍스트 또는 null
28
- */
29
- static getCurrentContext(): {
30
- abortSignal: AbortSignal;
31
- requestId: string;
32
- } | null;
33
- }
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AbortContext = void 0;
4
- const async_hooks_1 = require("async_hooks");
5
- /**
6
- * AbortSignal을 AsyncLocalStorage를 통해 전역적으로 관리하는 컨텍스트
7
- */
8
- class AbortContext {
9
- /**
10
- * AbortSignal과 함께 컨텍스트를 실행
11
- * @param abortSignal 중단 신호
12
- * @param requestId 요청 ID (디버깅용)
13
- * @param callback 실행할 콜백 함수
14
- * @returns 실행 결과
15
- */
16
- static run(abortSignal, requestId, callback) {
17
- return this.asyncLocalStorage.run({
18
- abortSignal,
19
- requestId,
20
- }, callback);
21
- }
22
- /**
23
- * 현재 컨텍스트의 AbortSignal을 가져옴
24
- * @returns 현재 AbortSignal 또는 null
25
- */
26
- static getCurrentAbortSignal() {
27
- const context = this.asyncLocalStorage.getStore();
28
- return (context === null || context === void 0 ? void 0 : context.abortSignal) || null;
29
- }
30
- /**
31
- * 현재 컨텍스트의 요청 ID를 가져옴
32
- * @returns 현재 요청 ID 또는 null
33
- */
34
- static getCurrentRequestId() {
35
- const context = this.asyncLocalStorage.getStore();
36
- return (context === null || context === void 0 ? void 0 : context.requestId) || null;
37
- }
38
- /**
39
- * 현재 컨텍스트 정보를 가져옴
40
- * @returns 현재 컨텍스트 또는 null
41
- */
42
- static getCurrentContext() {
43
- return this.asyncLocalStorage.getStore() || null;
44
- }
45
- }
46
- exports.AbortContext = AbortContext;
47
- AbortContext.asyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
@@ -1,60 +0,0 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- /**
4
- * 현재 컨텍스트의 AbortSignal을 가져오는 전역 함수
5
- * @returns 현재 AbortSignal 또는 null
6
- */
7
- export declare function getCurrentAbortSignal(): AbortSignal | null;
8
- /**
9
- * 현재 AbortSignal이 중단되었는지 확인
10
- * @returns 중단 여부
11
- */
12
- export declare function isAborted(): boolean;
13
- /**
14
- * AbortSignal이 중단되었을 때 에러를 던지는 함수
15
- * @param message 에러 메시지
16
- * @throws 중단된 경우 에러 발생
17
- */
18
- export declare function throwIfAborted(message?: string): void;
19
- /**
20
- * AbortSignal을 감지하여 중단 가능한 Promise를 생성
21
- * @param promise 원본 Promise
22
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
23
- * @returns 중단 가능한 Promise
24
- */
25
- export declare function withAbortSignal<T>(promise: Promise<T>, abortSignal?: AbortSignal): Promise<T>;
26
- /**
27
- * AbortSignal을 감지하여 중단 가능한 delay
28
- * @param ms 지연 시간 (밀리초)
29
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
30
- * @returns 중단 가능한 Promise
31
- */
32
- export declare function delayWithAbort(ms: number, abortSignal?: AbortSignal): Promise<void>;
33
- /**
34
- * AbortSignal을 감지하여 중단 가능한 setTimeout
35
- * @param callback 실행할 콜백 함수
36
- * @param delay 지연 시간 (밀리초)
37
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
38
- * @returns 타이머 ID
39
- */
40
- export declare function setTimeoutWithAbort(callback: () => void, delay: number, abortSignal?: AbortSignal): NodeJS.Timeout;
41
- /**
42
- * AbortSignal을 감지하여 중단 가능한 반복 작업
43
- * @param task 반복할 작업 함수
44
- * @param interval 반복 간격 (밀리초)
45
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
46
- * @returns 중단 가능한 Promise
47
- */
48
- export declare function repeatWithAbort(task: () => Promise<void> | void, interval: number, abortSignal?: AbortSignal): Promise<void>;
49
- /**
50
- * 현재 컨텍스트 정보를 로깅하는 디버그 함수
51
- * @param message 로그 메시지
52
- */
53
- export declare function logAbortContext(message: string): void;
54
- /**
55
- * 이벤트 기반으로 중단 가능한 Promise 실행
56
- * @param promise 원본 Promise
57
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
58
- * @returns 중단 가능한 Promise
59
- */
60
- export declare function withAbortSignalEventBased<T>(promise: Promise<T>, abortSignal?: AbortSignal): Promise<T>;
@@ -1,246 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withAbortSignalEventBased = exports.logAbortContext = exports.repeatWithAbort = exports.setTimeoutWithAbort = exports.delayWithAbort = exports.withAbortSignal = exports.throwIfAborted = exports.isAborted = exports.getCurrentAbortSignal = void 0;
4
- const abort_context_1 = require("./abort-context");
5
- /**
6
- * 현재 컨텍스트의 AbortSignal을 가져오는 전역 함수
7
- * @returns 현재 AbortSignal 또는 null
8
- */
9
- function getCurrentAbortSignal() {
10
- return abort_context_1.AbortContext.getCurrentAbortSignal();
11
- }
12
- exports.getCurrentAbortSignal = getCurrentAbortSignal;
13
- /**
14
- * 현재 AbortSignal이 중단되었는지 확인
15
- * @returns 중단 여부
16
- */
17
- function isAborted() {
18
- var _a;
19
- const signal = getCurrentAbortSignal();
20
- return (_a = signal === null || signal === void 0 ? void 0 : signal.aborted) !== null && _a !== void 0 ? _a : false;
21
- }
22
- exports.isAborted = isAborted;
23
- /**
24
- * AbortSignal이 중단되었을 때 에러를 던지는 함수
25
- * @param message 에러 메시지
26
- * @throws 중단된 경우 에러 발생
27
- */
28
- function throwIfAborted(message = '작업이 중단되었습니다') {
29
- if (isAborted()) {
30
- throw new Error(`REQUEST_ABORTED: ${message}`);
31
- }
32
- }
33
- exports.throwIfAborted = throwIfAborted;
34
- /**
35
- * AbortSignal을 감지하여 중단 가능한 Promise를 생성
36
- * @param promise 원본 Promise
37
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
38
- * @returns 중단 가능한 Promise
39
- */
40
- function withAbortSignal(promise, abortSignal) {
41
- const signal = abortSignal || getCurrentAbortSignal();
42
- if (!signal) {
43
- return promise;
44
- }
45
- return new Promise((resolve, reject) => {
46
- // 이미 중단된 경우
47
- if (signal.aborted) {
48
- reject(new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다'));
49
- return;
50
- }
51
- // 중단 이벤트 리스너 등록
52
- const abortHandler = () => {
53
- reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
54
- };
55
- 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
- });
66
- });
67
- }
68
- exports.withAbortSignal = withAbortSignal;
69
- /**
70
- * AbortSignal을 감지하여 중단 가능한 delay
71
- * @param ms 지연 시간 (밀리초)
72
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
73
- * @returns 중단 가능한 Promise
74
- */
75
- function delayWithAbort(ms, abortSignal) {
76
- const signal = abortSignal || getCurrentAbortSignal();
77
- return new Promise((resolve, reject) => {
78
- if (!signal) {
79
- setTimeout(resolve, ms);
80
- return;
81
- }
82
- // 이미 중단된 경우
83
- if (signal.aborted) {
84
- reject(new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다'));
85
- return;
86
- }
87
- const timerId = setTimeout(() => {
88
- if (!signal.aborted) {
89
- resolve();
90
- }
91
- }, ms);
92
- // 중단 이벤트 리스너 등록
93
- const abortHandler = () => {
94
- clearTimeout(timerId);
95
- reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
96
- };
97
- signal.addEventListener('abort', abortHandler, { once: true });
98
- });
99
- }
100
- exports.delayWithAbort = delayWithAbort;
101
- /**
102
- * AbortSignal을 감지하여 중단 가능한 setTimeout
103
- * @param callback 실행할 콜백 함수
104
- * @param delay 지연 시간 (밀리초)
105
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
106
- * @returns 타이머 ID
107
- */
108
- function setTimeoutWithAbort(callback, delay, abortSignal) {
109
- const signal = abortSignal || getCurrentAbortSignal();
110
- if (!signal) {
111
- return setTimeout(callback, delay);
112
- }
113
- // 이미 중단된 경우
114
- if (signal.aborted) {
115
- throw new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다');
116
- }
117
- const timerId = setTimeout(() => {
118
- if (!signal.aborted) {
119
- callback();
120
- }
121
- }, delay);
122
- // 중단 이벤트 리스너 등록
123
- const abortHandler = () => {
124
- clearTimeout(timerId);
125
- };
126
- signal.addEventListener('abort', abortHandler, { once: true });
127
- return timerId;
128
- }
129
- exports.setTimeoutWithAbort = setTimeoutWithAbort;
130
- /**
131
- * AbortSignal을 감지하여 중단 가능한 반복 작업
132
- * @param task 반복할 작업 함수
133
- * @param interval 반복 간격 (밀리초)
134
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
135
- * @returns 중단 가능한 Promise
136
- */
137
- function repeatWithAbort(task, interval, abortSignal) {
138
- const signal = abortSignal || getCurrentAbortSignal();
139
- return new Promise((resolve, reject) => {
140
- if (!signal) {
141
- // abortSignal이 없으면 일반적인 반복 실행
142
- const runTask = async () => {
143
- try {
144
- await task();
145
- setTimeout(runTask, interval);
146
- }
147
- catch (error) {
148
- reject(error);
149
- }
150
- };
151
- runTask();
152
- return;
153
- }
154
- // 이미 중단된 경우
155
- if (signal.aborted) {
156
- reject(new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다'));
157
- return;
158
- }
159
- const runTask = async () => {
160
- if (signal.aborted) {
161
- reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
162
- return;
163
- }
164
- try {
165
- await task();
166
- if (!signal.aborted) {
167
- setTimeout(runTask, interval);
168
- }
169
- else {
170
- reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
171
- }
172
- }
173
- catch (error) {
174
- reject(error);
175
- }
176
- };
177
- // 중단 이벤트 리스너 등록
178
- const abortHandler = () => {
179
- reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
180
- };
181
- signal.addEventListener('abort', abortHandler, { once: true });
182
- runTask();
183
- });
184
- }
185
- exports.repeatWithAbort = repeatWithAbort;
186
- /**
187
- * 현재 컨텍스트 정보를 로깅하는 디버그 함수
188
- * @param message 로그 메시지
189
- */
190
- function logAbortContext(message) {
191
- var _a;
192
- const context = abort_context_1.AbortContext.getCurrentContext();
193
- // eslint-disable-next-line no-console
194
- console.log(`[AbortContext] ${message}`, {
195
- requestId: context === null || context === void 0 ? void 0 : context.requestId,
196
- isAborted: (_a = context === null || context === void 0 ? void 0 : context.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted,
197
- hasSignal: !!(context === null || context === void 0 ? void 0 : context.abortSignal),
198
- });
199
- }
200
- exports.logAbortContext = logAbortContext;
201
- /**
202
- * 이벤트 기반으로 중단 가능한 Promise 실행
203
- * @param promise 원본 Promise
204
- * @param abortSignal 중단 신호 (선택사항, 없으면 현재 컨텍스트에서 가져옴)
205
- * @returns 중단 가능한 Promise
206
- */
207
- function withAbortSignalEventBased(promise, abortSignal) {
208
- const signal = abortSignal || getCurrentAbortSignal();
209
- if (!signal) {
210
- return promise;
211
- }
212
- return new Promise((resolve, reject) => {
213
- // 이미 중단된 경우
214
- if (signal.aborted) {
215
- reject(new Error('REQUEST_ABORTED: 작업이 이미 중단되었습니다'));
216
- return;
217
- }
218
- let isCompleted = false;
219
- // abort 이벤트 리스너 (즉시 반응)
220
- const abortHandler = () => {
221
- if (!isCompleted) {
222
- isCompleted = true;
223
- reject(new Error('REQUEST_ABORTED: 작업이 중단되었습니다'));
224
- }
225
- };
226
- // 이벤트 리스너 등록
227
- signal.addEventListener('abort', abortHandler, { once: true });
228
- // 원본 Promise 처리
229
- promise
230
- .then((result) => {
231
- if (!isCompleted) {
232
- isCompleted = true;
233
- signal.removeEventListener('abort', abortHandler);
234
- resolve(result);
235
- }
236
- })
237
- .catch((error) => {
238
- if (!isCompleted) {
239
- isCompleted = true;
240
- signal.removeEventListener('abort', abortHandler);
241
- reject(error);
242
- }
243
- });
244
- });
245
- }
246
- exports.withAbortSignalEventBased = withAbortSignalEventBased;