@yolo-croket-dev/core 1.9.13 → 1.10.0-jun.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/filters/grpc-exception.filter.js +1 -0
- package/interfaces/msa-exception.d.ts +0 -10
- package/interfaces/msa-exception.js +1 -6
- package/modules/graceful-shutdown/graceful-shutdown.rmq.d.ts +3 -9
- package/modules/graceful-shutdown/graceful-shutdown.rmq.js +2 -51
- package/package.json +5 -2
- package/util.js +4 -2
- package/CHANGELOG.md +0 -7
- package/tests/graceful-shutdown-rmq.test.cjs +0 -102
|
@@ -60,6 +60,7 @@ let GrpcExceptionFilter = class GrpcExceptionFilter extends microservices_1.Base
|
|
|
60
60
|
message: msaException.errMessage || exception.message,
|
|
61
61
|
status: httpStatus,
|
|
62
62
|
action: msaException.errAction,
|
|
63
|
+
errOption: msaException.errOption,
|
|
63
64
|
};
|
|
64
65
|
metadata.set('error', Buffer.from(JSON.stringify(errInfo)).toString('base64'));
|
|
65
66
|
metadata.set('status', httpStatus.toString());
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { HttpException } from '@nestjs/common';
|
|
2
2
|
import { LogLevelEnum } from './log-level.enum';
|
|
3
3
|
import type { ErrAction } from './exception';
|
|
4
|
-
export declare enum MsaExceptionMessageAckBehaviorEnum {
|
|
5
|
-
ACK = "ACK",
|
|
6
|
-
NACK = "NACK"
|
|
7
|
-
}
|
|
8
4
|
export interface MsaExceptionOption {
|
|
9
5
|
/**
|
|
10
6
|
* 로그 레벨
|
|
@@ -12,12 +8,6 @@ export interface MsaExceptionOption {
|
|
|
12
8
|
* - 기본값은 ERROR 입니다.
|
|
13
9
|
* */
|
|
14
10
|
logErrorLevel?: LogLevelEnum;
|
|
15
|
-
/**
|
|
16
|
-
* 메시지 consumer 실패 시 ACK/NACK override 옵션
|
|
17
|
-
* - 현재는 amqp-rabbit-mq subscribe consumer에서만 해석합니다.
|
|
18
|
-
* - option이 없으면 consumer handler의 기본 errorBehavior를 따릅니다.
|
|
19
|
-
*/
|
|
20
|
-
messageAckBehavior?: MsaExceptionMessageAckBehaviorEnum;
|
|
21
11
|
}
|
|
22
12
|
/**
|
|
23
13
|
* @author jun
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isMsaException =
|
|
3
|
+
exports.isMsaException = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
|
-
var MsaExceptionMessageAckBehaviorEnum;
|
|
6
|
-
(function (MsaExceptionMessageAckBehaviorEnum) {
|
|
7
|
-
MsaExceptionMessageAckBehaviorEnum["ACK"] = "ACK";
|
|
8
|
-
MsaExceptionMessageAckBehaviorEnum["NACK"] = "NACK";
|
|
9
|
-
})(MsaExceptionMessageAckBehaviorEnum || (exports.MsaExceptionMessageAckBehaviorEnum = MsaExceptionMessageAckBehaviorEnum = {}));
|
|
10
5
|
/**
|
|
11
6
|
* @description MsaException 타입 가드
|
|
12
7
|
* @param error 검사할 에러 객체
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import { ServerRMQ } from '@nestjs/microservices';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import type { ConsumeMessage } from 'amqplib';
|
|
1
|
+
import { RmqOptions, ServerRMQ } from '@nestjs/microservices';
|
|
2
|
+
import { Channel } from 'amqp-connection-manager';
|
|
3
|
+
import { ConsumeMessage } from 'amqplib';
|
|
5
4
|
export type GracefulRmqOptions = RmqOptions['options'] & {
|
|
6
5
|
waitingEndingHandlersTimeoutMs?: number;
|
|
7
6
|
waitingEndingHandlersIntervalMs?: number;
|
|
8
7
|
};
|
|
9
|
-
/**
|
|
10
|
-
* @author jun
|
|
11
|
-
* @description RabbitMQ 연결 식별 정보와 graceful shutdown을 제공하는 RMQ 서버
|
|
12
|
-
* @process 연결 옵션에 추적 가능한 connection_name을 추가한 뒤 메시지 종료를 보장한다.
|
|
13
|
-
*/
|
|
14
8
|
export declare class GracefulServerRMQ extends ServerRMQ {
|
|
15
9
|
protected readonly options: GracefulRmqOptions;
|
|
16
10
|
protected runningMessages: number;
|
|
@@ -1,67 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.GracefulServerRMQ = void 0;
|
|
7
4
|
const microservices_1 = require("@nestjs/microservices");
|
|
8
|
-
const os_1 = __importDefault(require("os"));
|
|
9
5
|
const node_util_1 = require("node:util");
|
|
10
6
|
const sleep = (0, node_util_1.promisify)(setTimeout);
|
|
11
7
|
const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS = 5000;
|
|
12
8
|
const RMQ_DEFAULT_WAITING_ENDING_HANDLERS_INTERVAL_MS = 500;
|
|
13
|
-
const RMQ_UNKNOWN_CONNECTION_IDENTITY = 'unknown';
|
|
14
|
-
function createRmqConnectionName(queue) {
|
|
15
|
-
const environment = process.env.ENV || process.env.NODE_ENV || RMQ_UNKNOWN_CONNECTION_IDENTITY;
|
|
16
|
-
const owner = process.env.RMQ_OWNER || process.env.USER || process.env.USERNAME || RMQ_UNKNOWN_CONNECTION_IDENTITY;
|
|
17
|
-
return [
|
|
18
|
-
queue || RMQ_UNKNOWN_CONNECTION_IDENTITY,
|
|
19
|
-
environment,
|
|
20
|
-
owner,
|
|
21
|
-
os_1.default.hostname(),
|
|
22
|
-
`pid-${process.pid}`,
|
|
23
|
-
].join('/');
|
|
24
|
-
}
|
|
25
|
-
function addRmqConnectionName(options) {
|
|
26
|
-
var _a;
|
|
27
|
-
const connectionOptions = (_a = options.socketOptions) === null || _a === void 0 ? void 0 : _a.connectionOptions;
|
|
28
|
-
const configuredClientProperties = connectionOptions === null || connectionOptions === void 0 ? void 0 : connectionOptions.clientProperties;
|
|
29
|
-
const clientProperties = configuredClientProperties
|
|
30
|
-
&& typeof configuredClientProperties === 'object'
|
|
31
|
-
&& !Array.isArray(configuredClientProperties)
|
|
32
|
-
? configuredClientProperties
|
|
33
|
-
: {};
|
|
34
|
-
const configuredConnectionName = clientProperties.connection_name;
|
|
35
|
-
const connectionName = typeof configuredConnectionName === 'string' && configuredConnectionName.trim()
|
|
36
|
-
? configuredConnectionName
|
|
37
|
-
: createRmqConnectionName(options.queue);
|
|
38
|
-
return {
|
|
39
|
-
...options,
|
|
40
|
-
socketOptions: {
|
|
41
|
-
...options.socketOptions,
|
|
42
|
-
connectionOptions: {
|
|
43
|
-
...connectionOptions,
|
|
44
|
-
clientProperties: {
|
|
45
|
-
...clientProperties,
|
|
46
|
-
connection_name: connectionName,
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* @author jun
|
|
54
|
-
* @description RabbitMQ 연결 식별 정보와 graceful shutdown을 제공하는 RMQ 서버
|
|
55
|
-
* @process 연결 옵션에 추적 가능한 connection_name을 추가한 뒤 메시지 종료를 보장한다.
|
|
56
|
-
*/
|
|
57
9
|
class GracefulServerRMQ extends microservices_1.ServerRMQ {
|
|
58
10
|
constructor(options) {
|
|
59
|
-
|
|
60
|
-
|
|
11
|
+
super(options);
|
|
12
|
+
this.options = options;
|
|
61
13
|
this.runningMessages = 0;
|
|
62
14
|
this.closing = false;
|
|
63
15
|
this.consumerTag = null;
|
|
64
|
-
this.options = optionsWithConnectionName;
|
|
65
16
|
this.waitingEndingHandlersTimeoutMs = this.getOptionsProp(this.options, 'waitingEndingHandlersTimeoutMs')
|
|
66
17
|
|| RMQ_DEFAULT_WAITING_ENDING_HANDLERS_TIMEOUT_MS;
|
|
67
18
|
this.waitingEndingHandlersIntervalMs = this.getOptionsProp(this.options, 'waitingEndingHandlersIntervalMs')
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolo-croket-dev/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0-jun.0",
|
|
4
4
|
"author": "Yolo Co., Ltd.",
|
|
5
5
|
"description": "Yolo commonization module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"lint": "eslint src/**/*.ts",
|
|
10
|
-
"test": "yarn build && node --test tests/graceful-shutdown-rmq.test.cjs",
|
|
11
10
|
"fix": "eslint --fix src/**/*.ts",
|
|
12
11
|
"clean": "tsc --build --clean",
|
|
13
12
|
"prepublishOnly": "npm run build",
|
|
@@ -16,7 +15,9 @@
|
|
|
16
15
|
"license": "UNLICENSED",
|
|
17
16
|
"peerDependencies": {
|
|
18
17
|
"@nestjs/common": "^11.0.0",
|
|
18
|
+
"@nestjs/microservices": "^11.0.0",
|
|
19
19
|
"@nestjs/swagger": "^11.0.0",
|
|
20
|
+
"@grpc/grpc-js": "^1.14.3",
|
|
20
21
|
"class-transformer": "^0.5.1",
|
|
21
22
|
"class-validator": "^0.14.0",
|
|
22
23
|
"dd-trace": "^5.0.0"
|
|
@@ -32,7 +33,9 @@
|
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@nestjs/common": "^11.1.8",
|
|
36
|
+
"@nestjs/microservices": "^11.1.8",
|
|
35
37
|
"@nestjs/swagger": "^11.2.1",
|
|
38
|
+
"@grpc/grpc-js": "^1.14.3",
|
|
36
39
|
"@types/node": "^24.9.1",
|
|
37
40
|
"@types/randomstring": "^1.1.8",
|
|
38
41
|
"@types/uuid": "^8.3.4"
|
package/util.js
CHANGED
|
@@ -14,8 +14,10 @@ exports.generateRandom = generateRandom;
|
|
|
14
14
|
// ex) localhost:50051 => 50051
|
|
15
15
|
function getPortFromHost(host) {
|
|
16
16
|
const specificHost = host.includes('://') ? host.split('://')[1] : host;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const port = specificHost.split(':')[1];
|
|
18
|
+
if (port) {
|
|
19
|
+
const parsedPort = parseInt(port, 10);
|
|
20
|
+
return Number.isNaN(parsedPort) ? undefined : parsedPort;
|
|
19
21
|
}
|
|
20
22
|
return undefined;
|
|
21
23
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.9.12-beta.1
|
|
4
|
-
|
|
5
|
-
- `GracefulServerRMQ`가 RabbitMQ 연결에 queue, environment, owner, hostname, process ID 기반 `connection_name`을 자동으로 추가합니다.
|
|
6
|
-
- 호출자가 지정한 `connectionOptions`, `clientProperties`, 유효한 문자열 `connection_name`은 그대로 보존합니다.
|
|
7
|
-
- 문자열이 아닌 `connection_name`은 런타임 오류 없이 자동 생성값으로 대체합니다.
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert/strict');
|
|
2
|
-
const os = require('node:os');
|
|
3
|
-
const test = require('node:test');
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
GracefulServerRMQ,
|
|
7
|
-
} = require('../modules/graceful-shutdown/graceful-shutdown.rmq.js');
|
|
8
|
-
|
|
9
|
-
const CONNECTION_NAME_ENV_KEYS = [
|
|
10
|
-
'ENV',
|
|
11
|
-
'NODE_ENV',
|
|
12
|
-
'RMQ_OWNER',
|
|
13
|
-
'USER',
|
|
14
|
-
'USERNAME',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
function getClientProperties(options) {
|
|
18
|
-
const server = new GracefulServerRMQ(options);
|
|
19
|
-
return server.options.socketOptions.connectionOptions.clientProperties;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function runWithEnvironment(environment, callback) {
|
|
23
|
-
const originalEnvironment = Object.fromEntries(
|
|
24
|
-
CONNECTION_NAME_ENV_KEYS.map((key) => [key, process.env[key]]),
|
|
25
|
-
);
|
|
26
|
-
try {
|
|
27
|
-
for (const key of CONNECTION_NAME_ENV_KEYS) {
|
|
28
|
-
delete process.env[key];
|
|
29
|
-
}
|
|
30
|
-
Object.assign(process.env, environment);
|
|
31
|
-
callback();
|
|
32
|
-
} finally {
|
|
33
|
-
for (const key of CONNECTION_NAME_ENV_KEYS) {
|
|
34
|
-
const originalValue = originalEnvironment[key];
|
|
35
|
-
if (originalValue === undefined) {
|
|
36
|
-
delete process.env[key];
|
|
37
|
-
} else {
|
|
38
|
-
process.env[key] = originalValue;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
test('자동 connection_name에 큐와 실행 주체 정보를 포함한다', () => {
|
|
45
|
-
runWithEnvironment({ ENV: 'release', RMQ_OWNER: 'jun' }, () => {
|
|
46
|
-
const actualClientProperties = getClientProperties({ queue: 'STORE_ITEM_SERVER' });
|
|
47
|
-
const expectedConnectionName = `STORE_ITEM_SERVER/release/jun/${os.hostname()}/pid-${process.pid}`;
|
|
48
|
-
assert.equal(actualClientProperties.connection_name, expectedConnectionName);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('기존 connectionOptions와 clientProperties를 보존한다', () => {
|
|
53
|
-
runWithEnvironment({ NODE_ENV: 'production', USER: 'deploy' }, () => {
|
|
54
|
-
const inputOptions = {
|
|
55
|
-
queue: 'USER_SERVICE',
|
|
56
|
-
socketOptions: {
|
|
57
|
-
noDelay: true,
|
|
58
|
-
connectionOptions: {
|
|
59
|
-
locale: 'ko_KR',
|
|
60
|
-
clientProperties: {
|
|
61
|
-
product: 'croket',
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
const server = new GracefulServerRMQ(inputOptions);
|
|
67
|
-
assert.equal(server.options.socketOptions.noDelay, true);
|
|
68
|
-
assert.equal(server.options.socketOptions.connectionOptions.locale, 'ko_KR');
|
|
69
|
-
assert.equal(server.options.socketOptions.connectionOptions.clientProperties.product, 'croket');
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('명시적으로 설정한 문자열 connection_name을 보존한다', () => {
|
|
74
|
-
const actualClientProperties = getClientProperties({
|
|
75
|
-
queue: 'USER_SERVICE',
|
|
76
|
-
socketOptions: {
|
|
77
|
-
connectionOptions: {
|
|
78
|
-
clientProperties: {
|
|
79
|
-
connection_name: 'custom-connection-name',
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
assert.equal(actualClientProperties.connection_name, 'custom-connection-name');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('문자열이 아닌 connection_name은 자동 생성값으로 대체한다', () => {
|
|
88
|
-
runWithEnvironment({ ENV: 'release', RMQ_OWNER: 'jun' }, () => {
|
|
89
|
-
const actualClientProperties = getClientProperties({
|
|
90
|
-
queue: 'STORE_ITEM_SERVER',
|
|
91
|
-
socketOptions: {
|
|
92
|
-
connectionOptions: {
|
|
93
|
-
clientProperties: {
|
|
94
|
-
connection_name: 123,
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
const expectedConnectionName = `STORE_ITEM_SERVER/release/jun/${os.hostname()}/pid-${process.pid}`;
|
|
100
|
-
assert.equal(actualClientProperties.connection_name, expectedConnectionName);
|
|
101
|
-
});
|
|
102
|
-
});
|