@ecashlib/shared-message 1.0.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 ADDED
@@ -0,0 +1,189 @@
1
+ # @ecash/shared
2
+
3
+ Shared utilities for Ecash microservices - Message handling, i18n, error helpers.
4
+
5
+ ## Features
6
+
7
+ - **i18n Message System**: Get localized messages from database with caching
8
+ - **Parameter Replacement**: Dynamic message templates with `{placeholder}` support
9
+ - **Language Detection**: Auto-detect language from request headers or query params
10
+ - **Error Helpers**: Throw errors with dynamic, localized messages
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @ecash/shared
16
+ ```
17
+
18
+ ## Setup
19
+
20
+ ### 1. Prepare Database Messages
21
+
22
+ Create messages in your `messages` collection:
23
+
24
+ ```json
25
+ {
26
+ "code": "ECASH_00001",
27
+ "classificationCode": "ECASH",
28
+ "content": {
29
+ "vi": "Metadata không hợp lệ theo schema của project type '{projectTypeCode}': {errors}",
30
+ "en": "Metadata is invalid according to project type '{projectTypeCode}' schema: {errors}",
31
+ "ko": "프로젝트 유형 '{projectTypeCode}'의 스키마에 따라 메타데이터가 유효하지 않습니다: {errors}"
32
+ }
33
+ }
34
+ ```
35
+
36
+ ### 2. Implement MessageServicePort
37
+
38
+ Each microservice implements the message service:
39
+
40
+ ```typescript
41
+ // ecash-marketplace/src/application/services/message/get-message-content.service.ts
42
+ import { MessageServicePort } from "@ecash/shared";
43
+
44
+ export class GetMessageContentService implements MessageServicePort {
45
+ async getMessageContentByCode(code: string, language: string) {
46
+ // Your implementation - query DB/Redis
47
+ const cached = await redis.get(`message:${code}:${language}`);
48
+ if (cached) return JSON.parse(cached);
49
+
50
+ const message = await db.collection("messages").findOne({ code });
51
+ return {
52
+ code: message.code,
53
+ classificationCode: message.classificationCode,
54
+ message: message.content[language],
55
+ };
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ ### 1. Get Language from Request
63
+
64
+ ```typescript
65
+ import { getLanguageFromRequest } from "@ecash/shared";
66
+
67
+ const language = getLanguageFromRequest(request); // "vi", "en", "ko"
68
+ ```
69
+
70
+ ### 2. Get Localized Message
71
+
72
+ ```typescript
73
+ import { getMessage } from "@ecash/shared";
74
+
75
+ const message = await getMessage(messageService, "ECASH_00001", "vi", {
76
+ projectTypeCode: "APARTMENT",
77
+ errors: "field required",
78
+ });
79
+ // Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
80
+ ```
81
+
82
+ ### 3. Get Message from Request (Shorthand)
83
+
84
+ ```typescript
85
+ import { getMessageFromRequest } from "@ecash/shared";
86
+
87
+ const message = await getMessageFromRequest(
88
+ request,
89
+ messageService,
90
+ "ECASH_00001",
91
+ { projectTypeCode: "APARTMENT" },
92
+ );
93
+ ```
94
+
95
+ ### 4. Throw Message Error
96
+
97
+ ```typescript
98
+ import { throwMessageError } from "@ecash/shared";
99
+
100
+ await throwMessageError(
101
+ messageService,
102
+ "vi",
103
+ "ECASH_00001",
104
+ { projectTypeCode: "APARTMENT", errors: "field required" },
105
+ "COMM0400",
106
+ );
107
+ ```
108
+
109
+ ### 5. Create Message Error (without throwing)
110
+
111
+ ```typescript
112
+ import { createMessageError } from "@ecash/shared";
113
+
114
+ const error = await createMessageError(messageService, "vi", {
115
+ messageCode: "ECASH_00001",
116
+ responseCode: "COMM0400",
117
+ params: { projectTypeCode: "APARTMENT" },
118
+ });
119
+
120
+ throw error;
121
+ ```
122
+
123
+ ## API Reference
124
+
125
+ ### `getLanguageFromRequest(request, defaultLang?)`
126
+
127
+ Extract language from request headers or query params.
128
+
129
+ - **Priority**: `Accept-Language` header > `?lang=` query > `defaultLang`
130
+ - **Returns**: Language code (`"vi"`, `"en"`, `"ko"`)
131
+
132
+ ### `getMessage(messageService, code, language, params?)`
133
+
134
+ Get localized message with parameter replacement.
135
+
136
+ - **messageService**: Service implementing `MessageServicePort`
137
+ - **code**: Message code from database
138
+ - **language**: Target language
139
+ - **params**: Object with key-value pairs to replace `{key}` in template
140
+
141
+ ### `getMessageFromRequest(request, messageService, code, params?)`
142
+
143
+ Shorthand that extracts language from request and gets message.
144
+
145
+ ### `throwMessageError(messageService, language, messageCode, params?, responseCode?, statusCode?)`
146
+
147
+ Throw error with localized message from database.
148
+
149
+ ### `createMessageError(messageService, language, options)`
150
+
151
+ Create `MessageError` object without throwing.
152
+
153
+ ## Language Detection
154
+
155
+ The library detects language in this order:
156
+
157
+ 1. **Header**: `Accept-Language: vi-VN, vi;q=0.9` → `"vi"`
158
+ 2. **Query**: `?lang=en` → `"en"`
159
+ 3. **Default**: `"vi"`
160
+
161
+ ## Message Template Syntax
162
+
163
+ Use `{key}` placeholders in your message templates:
164
+
165
+ ```json
166
+ {
167
+ "content": {
168
+ "vi": "Xin chào {name}, bạn có {count} tin nhắn mới",
169
+ "en": "Hello {name}, you have {count} new messages"
170
+ }
171
+ }
172
+ ```
173
+
174
+ ```typescript
175
+ getMessage(messageService, "MSG_00001", "vi", { name: "John", count: 5 });
176
+ // "Xin chào John, bạn có 5 tin nhắn mới"
177
+ ```
178
+
179
+ ## Publishing
180
+
181
+ ```bash
182
+ cd ecash-shared
183
+ npm run build
184
+ npm publish
185
+ ```
186
+
187
+ ## License
188
+
189
+ PRIVATE - Ecash internal use only.
@@ -0,0 +1,2 @@
1
+ export * from "./message";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./message"), exports);
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export * from "./message-helper";
3
+ export * from "./message-error";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/message/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,19 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./message-helper"), exports);
19
+ __exportStar(require("./message-error"), exports);
@@ -0,0 +1,53 @@
1
+ import { MessageErrorOptions, MessageServicePort } from "./types";
2
+ /**
3
+ * Error class for message-based errors
4
+ */
5
+ export declare class MessageError extends Error {
6
+ readonly code: string;
7
+ readonly statusCode: number;
8
+ readonly messageCode: string;
9
+ constructor(code: string, message: string, statusCode?: number, messageCode?: string);
10
+ }
11
+ /**
12
+ * Create a message error with localized message from DB
13
+ * Use this to throw errors with dynamic, multi-language messages
14
+ *
15
+ * @param messageService - Message service from DB
16
+ * @param language - Language code
17
+ * @param options - Error options
18
+ * @returns MessageError object
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * throw await createMessageError(
23
+ * fastify.getMessageContentByCodeService,
24
+ * "vi",
25
+ * {
26
+ * messageCode: "ECASH_00001",
27
+ * responseCode: "COMM0400",
28
+ * params: { projectTypeCode: "APARTMENT" }
29
+ * }
30
+ * );
31
+ * ```
32
+ */
33
+ export declare function createMessageError(messageService: MessageServicePort, language: string, options: MessageErrorOptions): Promise<MessageError>;
34
+ /**
35
+ * Create and throw a message error in one call
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * await throwMessageError(
40
+ * request,
41
+ * fastify.getMessageContentByCodeService,
42
+ * "ECASH_00001",
43
+ * { projectTypeCode: "APARTMENT" },
44
+ * "COMM0400"
45
+ * );
46
+ * ```
47
+ */
48
+ export declare function throwMessageError(request: Request, messageService: MessageServicePort, messageCode: string, params?: Record<string, string | number | undefined>, responseCode?: string, statusCode?: number): Promise<never>;
49
+ /**
50
+ * Simplified version - throw with language directly
51
+ */
52
+ export declare function throwMessageError(messageService: MessageServicePort, language: string, messageCode: string, params?: Record<string, string | number | undefined>, responseCode?: string, statusCode?: number): Promise<never>;
53
+ //# sourceMappingURL=message-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-error.d.ts","sourceRoot":"","sources":["../../src/message/message-error.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElE;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,WAAW,EAAE,MAAM,CAAC;gBAGlC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAY,EACxB,WAAW,GAAE,MAAW;CAQ3B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,kBAAkB,EAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAgBvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,kBAAkB,EAClC,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,EACpD,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC,CAAC;AAElB;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,cAAc,EAAE,kBAAkB,EAClC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,EACpD,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageError = void 0;
4
+ exports.createMessageError = createMessageError;
5
+ exports.throwMessageError = throwMessageError;
6
+ const message_helper_1 = require("./message-helper");
7
+ /**
8
+ * Error class for message-based errors
9
+ */
10
+ class MessageError extends Error {
11
+ constructor(code, message, statusCode = 400, messageCode = "") {
12
+ super(message);
13
+ this.name = "MessageError";
14
+ this.code = code;
15
+ this.statusCode = statusCode;
16
+ this.messageCode = messageCode;
17
+ }
18
+ }
19
+ exports.MessageError = MessageError;
20
+ /**
21
+ * Create a message error with localized message from DB
22
+ * Use this to throw errors with dynamic, multi-language messages
23
+ *
24
+ * @param messageService - Message service from DB
25
+ * @param language - Language code
26
+ * @param options - Error options
27
+ * @returns MessageError object
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * throw await createMessageError(
32
+ * fastify.getMessageContentByCodeService,
33
+ * "vi",
34
+ * {
35
+ * messageCode: "ECASH_00001",
36
+ * responseCode: "COMM0400",
37
+ * params: { projectTypeCode: "APARTMENT" }
38
+ * }
39
+ * );
40
+ * ```
41
+ */
42
+ async function createMessageError(messageService, language, options) {
43
+ const { messageCode, responseCode = "COMM0400", statusCode = 400, params, } = options;
44
+ const message = await (0, message_helper_1.getMessage)(messageService, messageCode, language, params);
45
+ return new MessageError(responseCode, message, statusCode, messageCode);
46
+ }
47
+ async function throwMessageError(arg1, arg2, arg3, arg4, arg5, arg6) {
48
+ let messageService;
49
+ let language;
50
+ let messageCode;
51
+ let params = {};
52
+ let responseCode = "COMM0400";
53
+ let statusCode = 400;
54
+ // Overload resolution
55
+ if (typeof arg2 === "string") {
56
+ // Simplified: (messageService, language, messageCode, params?, responseCode?, statusCode?)
57
+ messageService = arg1;
58
+ language = arg2;
59
+ messageCode = arg3;
60
+ params = arg4 || {};
61
+ responseCode = arg5 || "COMM0400";
62
+ statusCode = arg6 || 400;
63
+ }
64
+ else {
65
+ // Full: (request, messageService, messageCode, params?, responseCode?, statusCode?)
66
+ // Note: This would need getLanguageFromRequest, but to avoid circular dependency
67
+ // we'll use the simplified version primarily
68
+ throw new Error("Use simplified signature: (messageService, language, messageCode, params?, responseCode?, statusCode?)");
69
+ }
70
+ throw await createMessageError(messageService, language, {
71
+ messageCode,
72
+ responseCode,
73
+ statusCode,
74
+ params,
75
+ });
76
+ }
@@ -0,0 +1,63 @@
1
+ import { FastifyRequest } from "fastify";
2
+ import { MessageParams, MessageServicePort } from "./types";
3
+ /**
4
+ * Extract language from request
5
+ * Priority: Header Accept-Language > Query param ?lang > Default
6
+ *
7
+ * @param request - Fastify request object
8
+ * @param defaultLang - Default language (default: "vi")
9
+ * @returns Language code (vi, en, ko, etc.)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const lang = getLanguageFromRequest(request); // "vi"
14
+ * const lang = getLanguageFromRequest(request, "en"); // "en"
15
+ * ```
16
+ */
17
+ export declare function getLanguageFromRequest(request: FastifyRequest, defaultLang?: string): string;
18
+ /**
19
+ * Get message from service and replace parameters
20
+ *
21
+ * @param messageService - Message service from DB
22
+ * @param code - Message code (ECASH_00001)
23
+ * @param language - Language code (vi, en, ko)
24
+ * @param params - Parameters to replace {key} with value
25
+ * @returns Localized message with parameters replaced
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const message = await getMessage(
30
+ * fastify.getMessageContentByCodeService,
31
+ * "ECASH_00001",
32
+ * "vi",
33
+ * { projectTypeCode: "APARTMENT", errors: "field required" }
34
+ * );
35
+ * // Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
36
+ * ```
37
+ */
38
+ export declare function getMessage(messageService: MessageServicePort, code: string, language: string, params?: MessageParams): Promise<string>;
39
+ /**
40
+ * Replace {key} placeholders in message template with actual values
41
+ *
42
+ * @param template - Message template with {placeholders}
43
+ * @param params - Key-value pairs to replace
44
+ * @returns Formatted message
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * formatMessage("Hello {name}, you have {count} messages", { name: "John", count: 5 });
49
+ * // Returns: "Hello John, you have 5 messages"
50
+ * ```
51
+ */
52
+ export declare function formatMessage(template: string, params?: MessageParams): string;
53
+ /**
54
+ * Shorthand to get message from request directly
55
+ *
56
+ * @param request - Fastify request
57
+ * @param messageService - Message service
58
+ * @param code - Message code
59
+ * @param params - Parameters
60
+ * @returns Localized message
61
+ */
62
+ export declare function getMessageFromRequest(request: FastifyRequest, messageService: MessageServicePort, code: string, params?: MessageParams): Promise<string>;
63
+ //# sourceMappingURL=message-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-helper.d.ts","sourceRoot":"","sources":["../../src/message/message-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,WAAW,GAAE,MAAa,GACzB,MAAM,CAgBR;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAC9B,cAAc,EAAE,kBAAkB,EAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CAeR;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,kBAAkB,EAClC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CAGjB"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLanguageFromRequest = getLanguageFromRequest;
4
+ exports.getMessage = getMessage;
5
+ exports.formatMessage = formatMessage;
6
+ exports.getMessageFromRequest = getMessageFromRequest;
7
+ /**
8
+ * Extract language from request
9
+ * Priority: Header Accept-Language > Query param ?lang > Default
10
+ *
11
+ * @param request - Fastify request object
12
+ * @param defaultLang - Default language (default: "vi")
13
+ * @returns Language code (vi, en, ko, etc.)
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const lang = getLanguageFromRequest(request); // "vi"
18
+ * const lang = getLanguageFromRequest(request, "en"); // "en"
19
+ * ```
20
+ */
21
+ function getLanguageFromRequest(request, defaultLang = "vi") {
22
+ // 1. Header Accept-Language
23
+ const headerLang = request.headers["accept-language"];
24
+ if (headerLang) {
25
+ // Parse "vi-VN, vi;q=0.9, en;q=0.8" -> "vi"
26
+ return headerLang.split(",")[0].split("-")[0].trim();
27
+ }
28
+ // 2. Query param ?lang=vi
29
+ const queryLang = request.query?.lang;
30
+ if (queryLang && typeof queryLang === "string") {
31
+ return queryLang;
32
+ }
33
+ // 3. Default
34
+ return defaultLang;
35
+ }
36
+ /**
37
+ * Get message from service and replace parameters
38
+ *
39
+ * @param messageService - Message service from DB
40
+ * @param code - Message code (ECASH_00001)
41
+ * @param language - Language code (vi, en, ko)
42
+ * @param params - Parameters to replace {key} with value
43
+ * @returns Localized message with parameters replaced
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const message = await getMessage(
48
+ * fastify.getMessageContentByCodeService,
49
+ * "ECASH_00001",
50
+ * "vi",
51
+ * { projectTypeCode: "APARTMENT", errors: "field required" }
52
+ * );
53
+ * // Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
54
+ * ```
55
+ */
56
+ async function getMessage(messageService, code, language, params) {
57
+ try {
58
+ const messageData = await messageService.getMessageContentByCode(code, language);
59
+ if (messageData?.message) {
60
+ return formatMessage(messageData.message, params);
61
+ }
62
+ }
63
+ catch (error) {
64
+ // Log error but continue to fallback
65
+ console.error(`Error getting message ${code} in ${language}:`, error);
66
+ }
67
+ // Fallback to Vietnamese if available
68
+ if (language !== "vi") {
69
+ try {
70
+ const fallback = await messageService.getMessageContentByCode(code, "vi");
71
+ if (fallback?.message) {
72
+ return formatMessage(fallback.message, params);
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error(`Error getting fallback message ${code}:`, error);
77
+ }
78
+ }
79
+ // Final fallback - return code
80
+ return `[${code}]`;
81
+ }
82
+ /**
83
+ * Replace {key} placeholders in message template with actual values
84
+ *
85
+ * @param template - Message template with {placeholders}
86
+ * @param params - Key-value pairs to replace
87
+ * @returns Formatted message
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * formatMessage("Hello {name}, you have {count} messages", { name: "John", count: 5 });
92
+ * // Returns: "Hello John, you have 5 messages"
93
+ * ```
94
+ */
95
+ function formatMessage(template, params) {
96
+ if (!params || Object.keys(params).length === 0) {
97
+ return template;
98
+ }
99
+ let result = template;
100
+ Object.entries(params).forEach(([key, value]) => {
101
+ if (value !== undefined) {
102
+ const regex = new RegExp(`\\{${key}\\}`, "g");
103
+ result = result.replace(regex, String(value));
104
+ }
105
+ });
106
+ // Clean up any remaining {key} without values
107
+ return result.replace(/\{[^}]+\}/g, "?");
108
+ }
109
+ /**
110
+ * Shorthand to get message from request directly
111
+ *
112
+ * @param request - Fastify request
113
+ * @param messageService - Message service
114
+ * @param code - Message code
115
+ * @param params - Parameters
116
+ * @returns Localized message
117
+ */
118
+ async function getMessageFromRequest(request, messageService, code, params) {
119
+ const language = getLanguageFromRequest(request);
120
+ return getMessage(messageService, code, language, params);
121
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Parameters for message template replacement
3
+ * Example: { projectTypeCode: "APARTMENT", errors: "field required" }
4
+ */
5
+ export interface MessageParams {
6
+ [key: string]: string | number | undefined;
7
+ }
8
+ /**
9
+ * Message content from database
10
+ */
11
+ export interface MessageContent {
12
+ code: string;
13
+ classificationCode: string;
14
+ message: string;
15
+ }
16
+ /**
17
+ * Options for getting a message
18
+ */
19
+ export interface GetMessageOptions {
20
+ code: string;
21
+ language?: string;
22
+ params?: MessageParams;
23
+ }
24
+ /**
25
+ * Port interface for message service
26
+ * Each microservice implements this to connect to their DB
27
+ */
28
+ export interface MessageServicePort {
29
+ getMessageContentByCode(code: string, language: string): Promise<MessageContent | null>;
30
+ }
31
+ /**
32
+ * Error options for throwing message-based errors
33
+ */
34
+ export interface MessageErrorOptions {
35
+ messageCode: string;
36
+ responseCode?: string;
37
+ statusCode?: number;
38
+ params?: MessageParams;
39
+ }
40
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/message/types.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,uBAAuB,CACrB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@ecashlib/shared-message",
3
+ "version": "1.0.0",
4
+ "description": "Shared utilities for Ecash microservices - Message handling, i18n, error helpers",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "watch": "tsc --watch",
10
+ "prepublishOnly": "npm run build",
11
+ "clean": "rimraf dist"
12
+ },
13
+ "keywords": [
14
+ "ecash",
15
+ "ecashlib",
16
+ "shared",
17
+ "i18n",
18
+ "message",
19
+ "error-handling"
20
+ ],
21
+ "author": "Ecash Team",
22
+ "license": "PRIVATE",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "fastify": "^4.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.0.0",
31
+ "typescript": "^5.0.0",
32
+ "rimraf": "^5.0.0"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md"
37
+ ]
38
+ }