@ecashlib/shared-message 1.0.0 → 1.0.1
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 +230 -80
- package/dist/cache/cache-interfaces.d.ts +48 -0
- package/dist/cache/cache-interfaces.d.ts.map +1 -0
- package/dist/cache/cache-interfaces.js +2 -0
- package/dist/cache/index.d.ts +3 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +18 -0
- package/dist/cache/message-cache.d.ts +76 -0
- package/dist/cache/message-cache.d.ts.map +1 -0
- package/dist/cache/message-cache.js +137 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/message/cached-message-service.d.ts +67 -0
- package/dist/message/cached-message-service.d.ts.map +1 -0
- package/dist/message/cached-message-service.js +105 -0
- package/dist/message/index.d.ts +1 -0
- package/dist/message/index.d.ts.map +1 -1
- package/dist/message/index.js +1 -0
- package/dist/message/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,15 +4,16 @@ Shared utilities for Ecash microservices - Message handling, i18n, error helpers
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **i18n Message System**: Get localized messages from database with caching
|
|
7
|
+
- **i18n Message System**: Get localized messages from database with **Redis caching**
|
|
8
8
|
- **Parameter Replacement**: Dynamic message templates with `{placeholder}` support
|
|
9
9
|
- **Language Detection**: Auto-detect language from request headers or query params
|
|
10
10
|
- **Error Helpers**: Throw errors with dynamic, localized messages
|
|
11
|
+
- **Framework Agnostic**: Works with any Redis implementation via `IRedisCache` interface
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
|
-
npm install @
|
|
16
|
+
npm install @ecashlib/shared-message
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
## Setup
|
|
@@ -33,149 +34,298 @@ Create messages in your `messages` collection:
|
|
|
33
34
|
}
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
### 2. Implement
|
|
37
|
-
|
|
38
|
-
Each microservice implements the message service:
|
|
37
|
+
### 2. Implement Database Service (WITHOUT cache)
|
|
39
38
|
|
|
40
39
|
```typescript
|
|
41
|
-
// ecash-marketplace/src/
|
|
42
|
-
import { MessageServicePort } from "@
|
|
40
|
+
// ecash-marketplace/src/infrastructure/adapters/outbound/message/message.repository.ts
|
|
41
|
+
import { MessageServicePort } from "@ecashlib/shared-message";
|
|
43
42
|
|
|
44
|
-
export class
|
|
43
|
+
export class MessageRepository implements MessageServicePort {
|
|
45
44
|
async getMessageContentByCode(code: string, language: string) {
|
|
46
|
-
//
|
|
47
|
-
const cached = await redis.get(`message:${code}:${language}`);
|
|
48
|
-
if (cached) return JSON.parse(cached);
|
|
49
|
-
|
|
45
|
+
// ONLY query DB - NO cache logic here
|
|
50
46
|
const message = await db.collection("messages").findOne({ code });
|
|
47
|
+
if (!message) return null;
|
|
48
|
+
|
|
51
49
|
return {
|
|
52
50
|
code: message.code,
|
|
53
51
|
classificationCode: message.classificationCode,
|
|
54
|
-
message: message.content[language]
|
|
52
|
+
message: message.content[language] || null
|
|
55
53
|
};
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
```
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
### 1. Get Language from Request
|
|
58
|
+
### 3. Implement IRedisCache Adapter (WITH your Redis wrapper)
|
|
63
59
|
|
|
64
60
|
```typescript
|
|
65
|
-
|
|
61
|
+
// src/infrastructure/adapters/outbound/cache/redis-cache.adapter.ts
|
|
62
|
+
import { IRedisCache } from "@ecashlib/shared-message";
|
|
63
|
+
|
|
64
|
+
export class RedisCacheAdapter implements IRedisCache {
|
|
65
|
+
constructor(private redis: FastifyInstance["redis"]) {}
|
|
66
|
+
|
|
67
|
+
async get(key: string): Promise<string | null> {
|
|
68
|
+
return await this.redis.get(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async set(key: string, value: string, ttl?: number): Promise<void> {
|
|
72
|
+
if (ttl) {
|
|
73
|
+
await this.redis.setex(key, ttl, value);
|
|
74
|
+
} else {
|
|
75
|
+
await this.redis.set(key, value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async mset(keyValues: Record<string, string>): Promise<void> {
|
|
80
|
+
const pipeline = this.redis.pipeline();
|
|
81
|
+
for (const [key, value] of Object.entries(keyValues)) {
|
|
82
|
+
pipeline.set(key, value);
|
|
83
|
+
}
|
|
84
|
+
await pipeline.exec();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async exists(key: string): Promise<boolean> {
|
|
88
|
+
const result = await this.redis.exists(key);
|
|
89
|
+
return result === 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async del(key: string): Promise<number> {
|
|
93
|
+
return await this.redis.del(key);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async delPattern(pattern: string): Promise<string[]> {
|
|
97
|
+
const keys = await this.redis.keys(pattern);
|
|
98
|
+
if (keys.length > 0) {
|
|
99
|
+
await this.redis.del(...keys);
|
|
100
|
+
}
|
|
101
|
+
return keys;
|
|
102
|
+
}
|
|
66
103
|
|
|
67
|
-
|
|
104
|
+
async hgetall(key: string): Promise<Record<string, string>> {
|
|
105
|
+
return await this.redis.hgetall(key);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async hset(key: string, field: string, value: string): Promise<void> {
|
|
109
|
+
await this.redis.hset(key, field, value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async hdel(key: string, field?: string): Promise<void> {
|
|
113
|
+
if (field) {
|
|
114
|
+
await this.redis.hdel(key, field);
|
|
115
|
+
} else {
|
|
116
|
+
await this.redis.hdel(key);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
68
120
|
```
|
|
69
121
|
|
|
70
|
-
###
|
|
122
|
+
### 4. Use CachedMessageService (WITH cache)
|
|
71
123
|
|
|
72
124
|
```typescript
|
|
73
|
-
import {
|
|
125
|
+
import { CachedMessageService } from "@ecashlib/shared-message";
|
|
126
|
+
import { RedisCacheAdapter } from "./infrastructure/adapters/outbound/cache/redis-cache.adapter";
|
|
127
|
+
import { MessageRepository } from "./infrastructure/adapters/outbound/message/message.repository";
|
|
128
|
+
|
|
129
|
+
// Setup Redis adapter
|
|
130
|
+
const redisAdapter = new RedisCacheAdapter(fastify.redis);
|
|
131
|
+
|
|
132
|
+
// Create cached service
|
|
133
|
+
const dbService = new MessageRepository(); // Your DB service
|
|
134
|
+
const cachedMessageService = new CachedMessageService(
|
|
135
|
+
redisAdapter,
|
|
136
|
+
dbService,
|
|
137
|
+
3600 // Cache TTL: 1 hour
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Cache Flow
|
|
74
142
|
|
|
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
143
|
```
|
|
144
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
145
|
+
│ getMessageContentByCode() │
|
|
146
|
+
└─────────────────────────────────────────────────────────────┘
|
|
147
|
+
│
|
|
148
|
+
┌─────────────┴──────────────┐
|
|
149
|
+
▼ ▼
|
|
150
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
151
|
+
│ Check Redis │ MISS │ Query MongoDB │
|
|
152
|
+
│ │ ────────> │ │
|
|
153
|
+
│ message:ECASH_ │ │ messages │
|
|
154
|
+
│ 00001:vi │ │ {code, ...} │
|
|
155
|
+
└─────────────────┘ └─────────────────┘
|
|
156
|
+
│ │
|
|
157
|
+
│ HIT │
|
|
158
|
+
▼ │
|
|
159
|
+
┌───────────────┐ │
|
|
160
|
+
│ Return data │ │
|
|
161
|
+
└───────────────┘ │
|
|
162
|
+
│
|
|
163
|
+
▼
|
|
164
|
+
┌─────────────────┐
|
|
165
|
+
│ Save to Redis │
|
|
166
|
+
│ message:ECASH_ │
|
|
167
|
+
│ 00001:vi │
|
|
168
|
+
│ TTL: 3600s │
|
|
169
|
+
└─────────────────┘
|
|
170
|
+
│
|
|
171
|
+
▼
|
|
172
|
+
┌─────────────────┐
|
|
173
|
+
│ Return data │
|
|
174
|
+
└─────────────────┘
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Usage
|
|
81
178
|
|
|
82
|
-
###
|
|
179
|
+
### 1. Get Message with Cache
|
|
83
180
|
|
|
84
181
|
```typescript
|
|
85
|
-
import {
|
|
182
|
+
import { CachedMessageService } from "@ecashlib/shared-message";
|
|
86
183
|
|
|
87
|
-
const message = await
|
|
88
|
-
request,
|
|
89
|
-
messageService,
|
|
184
|
+
const message = await cachedMessageService.getMessageContentByCode(
|
|
90
185
|
"ECASH_00001",
|
|
91
|
-
|
|
186
|
+
"vi"
|
|
92
187
|
);
|
|
188
|
+
// First call: Query DB → Save to Redis → Return
|
|
189
|
+
// Next call: Return from Redis (FAST!)
|
|
93
190
|
```
|
|
94
191
|
|
|
95
|
-
###
|
|
192
|
+
### 2. Get Message with Parameters
|
|
96
193
|
|
|
97
194
|
```typescript
|
|
98
|
-
import {
|
|
195
|
+
import { getMessage } from "@ecashlib/shared-message";
|
|
99
196
|
|
|
100
|
-
await
|
|
101
|
-
|
|
197
|
+
const message = await getMessage(
|
|
198
|
+
cachedMessageService,
|
|
199
|
+
"ECASH_00001",
|
|
102
200
|
"vi",
|
|
201
|
+
{ projectTypeCode: "APARTMENT", errors: "field required" }
|
|
202
|
+
);
|
|
203
|
+
// Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 3. Get Message from Request
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { getMessageFromRequest } from "@ecashlib/shared-message";
|
|
210
|
+
|
|
211
|
+
const message = await getMessageFromRequest(
|
|
212
|
+
request,
|
|
213
|
+
cachedMessageService,
|
|
103
214
|
"ECASH_00001",
|
|
104
|
-
{ projectTypeCode: "APARTMENT"
|
|
105
|
-
"COMM0400",
|
|
215
|
+
{ projectTypeCode: "APARTMENT" }
|
|
106
216
|
);
|
|
107
217
|
```
|
|
108
218
|
|
|
109
|
-
###
|
|
219
|
+
### 4. Invalidate Cache (after update)
|
|
110
220
|
|
|
111
221
|
```typescript
|
|
112
|
-
|
|
222
|
+
// Invalidate all languages for a message
|
|
223
|
+
await cachedMessageService.invalidate("ECASH_00001");
|
|
113
224
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
responseCode: "COMM0400",
|
|
117
|
-
params: { projectTypeCode: "APARTMENT" },
|
|
118
|
-
});
|
|
225
|
+
// Invalidate specific language
|
|
226
|
+
await cachedMessageService.invalidateLanguage("ECASH_00001", "vi");
|
|
119
227
|
|
|
120
|
-
|
|
228
|
+
// Set to cache manually (after create)
|
|
229
|
+
await cachedMessageService.setToCache("ECASH_00001", {
|
|
230
|
+
vi: "Nội dung tiếng Việt",
|
|
231
|
+
en: "English content"
|
|
232
|
+
});
|
|
121
233
|
```
|
|
122
234
|
|
|
123
|
-
|
|
235
|
+
### 5. Throw Message Error
|
|
124
236
|
|
|
125
|
-
|
|
237
|
+
```typescript
|
|
238
|
+
import { throwMessageError } from "@ecashlib/shared-message";
|
|
126
239
|
|
|
127
|
-
|
|
240
|
+
await throwMessageError(
|
|
241
|
+
cachedMessageService,
|
|
242
|
+
"vi",
|
|
243
|
+
"ECASH_00001",
|
|
244
|
+
{ projectTypeCode: "APARTMENT", errors: "field required" },
|
|
245
|
+
"COMM0400"
|
|
246
|
+
);
|
|
247
|
+
```
|
|
128
248
|
|
|
129
|
-
|
|
130
|
-
- **Returns**: Language code (`"vi"`, `"en"`, `"ko"`)
|
|
249
|
+
## API Reference
|
|
131
250
|
|
|
132
|
-
### `
|
|
251
|
+
### `CachedMessageService`
|
|
133
252
|
|
|
134
|
-
|
|
253
|
+
| Method | Description |
|
|
254
|
+
|--------|-------------|
|
|
255
|
+
| `getMessageContentByCode(code, language)` | Get message with cache logic |
|
|
256
|
+
| `getAllLanguages(code)` | Get all languages from cache |
|
|
257
|
+
| `invalidate(code)` | Delete all languages from cache |
|
|
258
|
+
| `invalidateLanguage(code, language)` | Delete specific language from cache |
|
|
259
|
+
| `setToCache(code, content)` | Manually set message to cache |
|
|
135
260
|
|
|
136
|
-
|
|
137
|
-
- **code**: Message code from database
|
|
138
|
-
- **language**: Target language
|
|
139
|
-
- **params**: Object with key-value pairs to replace `{key}` in template
|
|
261
|
+
### `MessageCache` (Low-level)
|
|
140
262
|
|
|
141
|
-
|
|
263
|
+
| Method | Description |
|
|
264
|
+
|--------|-------------|
|
|
265
|
+
| `get(code, language)` | Get from Redis |
|
|
266
|
+
| `getAll(code)` | Get all languages from Redis |
|
|
267
|
+
| `set(code, language, content, ttl?)` | Set to Redis with TTL |
|
|
268
|
+
| `setAll(code, content, ttl?)` | Set multiple languages to Redis |
|
|
269
|
+
| `delete(code)` | Delete all languages from Redis |
|
|
270
|
+
| `deleteLanguage(code, language)` | Delete specific language |
|
|
271
|
+
| `exists(code, language)` | Check if exists in cache |
|
|
272
|
+
| `flush()` | Delete ALL message cache (use with caution!) |
|
|
142
273
|
|
|
143
|
-
|
|
274
|
+
### `IRedisCache` Interface
|
|
144
275
|
|
|
145
|
-
|
|
276
|
+
Your microservice needs to implement this interface to work with the cache system:
|
|
146
277
|
|
|
147
|
-
|
|
278
|
+
```typescript
|
|
279
|
+
export interface IRedisCache {
|
|
280
|
+
get(key: string): Promise<string | null>;
|
|
281
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
282
|
+
mset(keyValues: Record<string, string>): Promise<void>;
|
|
283
|
+
exists(key: string): Promise<boolean>;
|
|
284
|
+
del(key: string): Promise<number>;
|
|
285
|
+
delPattern(pattern: string): Promise<string[]>;
|
|
286
|
+
hgetall(key: string): Promise<Record<string, string>>;
|
|
287
|
+
hset(key: string, field: string, value: string): Promise<void>;
|
|
288
|
+
hdel(key: string, field?: string): Promise<void>;
|
|
289
|
+
}
|
|
290
|
+
```
|
|
148
291
|
|
|
149
|
-
|
|
292
|
+
## Example: Full Setup in Microservice
|
|
150
293
|
|
|
151
|
-
|
|
294
|
+
```typescript
|
|
295
|
+
// 1. Setup
|
|
296
|
+
import { CachedMessageService, getMessage, throwMessageError } from "@ecashlib/shared-message";
|
|
297
|
+
import { RedisCacheAdapter } from "./infrastructure/adapters/outbound/cache/redis-cache.adapter";
|
|
298
|
+
import { MessageRepository } from "./infrastructure/adapters/outbound/message/message.repository";
|
|
152
299
|
|
|
153
|
-
|
|
300
|
+
const redisAdapter = new RedisCacheAdapter(fastify.redis);
|
|
301
|
+
const dbService = new MessageRepository(); // Your DB service
|
|
302
|
+
const messageService = new CachedMessageService(redisAdapter, dbService);
|
|
154
303
|
|
|
155
|
-
|
|
304
|
+
// 2. Use in controller/service
|
|
305
|
+
async function getErrorMessage(request: Request) {
|
|
306
|
+
const language = request.headers["accept-language"] || "vi";
|
|
156
307
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
308
|
+
const message = await messageService.getMessageContentByCode(
|
|
309
|
+
"ECASH_00001",
|
|
310
|
+
language
|
|
311
|
+
);
|
|
160
312
|
|
|
161
|
-
|
|
313
|
+
return message.message;
|
|
314
|
+
}
|
|
162
315
|
|
|
163
|
-
|
|
316
|
+
// 3. Use with parameters
|
|
317
|
+
async function throwValidationError(request: Request, projectTypeCode: string, errors: string) {
|
|
318
|
+
const language = request.headers["accept-language"] || "vi";
|
|
164
319
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"
|
|
169
|
-
|
|
170
|
-
|
|
320
|
+
await throwMessageError(
|
|
321
|
+
messageService,
|
|
322
|
+
language,
|
|
323
|
+
"ECASH_00001",
|
|
324
|
+
{ projectTypeCode, errors }
|
|
325
|
+
);
|
|
171
326
|
}
|
|
172
327
|
```
|
|
173
328
|
|
|
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
329
|
## Publishing
|
|
180
330
|
|
|
181
331
|
```bash
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Cache Interface
|
|
3
|
+
* Implement this in your microservice to use with fastify.redis
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
export interface IRedisCache {
|
|
7
|
+
/**
|
|
8
|
+
* Get string value from Redis
|
|
9
|
+
*/
|
|
10
|
+
get(key: string): Promise<string | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Set string value to Redis with optional TTL
|
|
13
|
+
*/
|
|
14
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Set multiple key-value pairs
|
|
17
|
+
*/
|
|
18
|
+
mset(keyValues: Record<string, string>): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Check if key exists
|
|
21
|
+
*/
|
|
22
|
+
exists(key: string): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Delete key
|
|
25
|
+
*/
|
|
26
|
+
del(key: string): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Delete all keys matching pattern
|
|
29
|
+
*/
|
|
30
|
+
delPattern(pattern: string): Promise<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Get all hash fields for a key (HGETALL)
|
|
33
|
+
*/
|
|
34
|
+
hgetall(key: string): Promise<Record<string, string>>;
|
|
35
|
+
/**
|
|
36
|
+
* Set hash field (HSET)
|
|
37
|
+
*/
|
|
38
|
+
hset(key: string, field: string, value: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Delete hash field (HDEL)
|
|
41
|
+
*/
|
|
42
|
+
hdel(key: string, field: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Delete all hash fields (HDEL)
|
|
45
|
+
*/
|
|
46
|
+
hdel(key: string): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=cache-interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-interfaces.d.ts","sourceRoot":"","sources":["../../src/cache/cache-interfaces.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvD;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE/C;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtD;;OAEG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/D;;OAEG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD;;OAEG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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-cache"), exports);
|
|
18
|
+
__exportStar(require("./cache-interfaces"), exports);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { IRedisCache } from "./cache-interfaces";
|
|
2
|
+
/**
|
|
3
|
+
* Redis Cache Adapter for Message System
|
|
4
|
+
* Handles caching of messages with TTL support
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { MessageCache, IRedisCache } from "@ecashlib/shared-message";
|
|
8
|
+
*
|
|
9
|
+
* class YourRedisAdapter implements IRedisCache {
|
|
10
|
+
* // implement methods using your redis wrapper
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* const adapter = new YourRedisAdapter();
|
|
14
|
+
* const cache = new MessageCache(adapter, 3600);
|
|
15
|
+
*/
|
|
16
|
+
export declare class MessageCache {
|
|
17
|
+
private readonly redis;
|
|
18
|
+
private readonly defaultTTL;
|
|
19
|
+
constructor(redis: IRedisCache, defaultTTL?: number);
|
|
20
|
+
/**
|
|
21
|
+
* Get message content from cache
|
|
22
|
+
* @param code - Message code (e.g., "ECASH_00001")
|
|
23
|
+
* @param language - Language code (e.g., "vi", "en")
|
|
24
|
+
* @returns Message content or null if not found
|
|
25
|
+
*/
|
|
26
|
+
get(code: string, language: string): Promise<string | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Get all languages for a message code from cache
|
|
29
|
+
* @param code - Message code
|
|
30
|
+
* @returns Object with all language translations
|
|
31
|
+
*/
|
|
32
|
+
getAll(code: string): Promise<Record<string, string> | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Set message content to cache with TTL
|
|
35
|
+
* @param code - Message code
|
|
36
|
+
* @param language - Language code
|
|
37
|
+
* @param content - Message content
|
|
38
|
+
* @param ttl - Time to live in seconds (optional, uses default)
|
|
39
|
+
*/
|
|
40
|
+
set(code: string, language: string, content: string, ttl?: number): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Set all languages for a message code
|
|
43
|
+
* @param code - Message code
|
|
44
|
+
* @param content - Object with all language translations
|
|
45
|
+
* @param ttl - Time to live in seconds (optional)
|
|
46
|
+
*/
|
|
47
|
+
setAll(code: string, content: Record<string, string>, ttl?: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Delete message from cache (all languages)
|
|
50
|
+
* @param code - Message code
|
|
51
|
+
*/
|
|
52
|
+
delete(code: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Delete specific language from cache
|
|
55
|
+
* @param code - Message code
|
|
56
|
+
* @param language - Language code
|
|
57
|
+
*/
|
|
58
|
+
deleteLanguage(code: string, language: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Clear all message cache (use with caution!)
|
|
61
|
+
*/
|
|
62
|
+
flush(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if message exists in cache
|
|
65
|
+
*/
|
|
66
|
+
exists(code: string, language: string): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Helper to delete keys by pattern
|
|
69
|
+
*/
|
|
70
|
+
private delPattern;
|
|
71
|
+
/**
|
|
72
|
+
* Generate cache key
|
|
73
|
+
*/
|
|
74
|
+
private getKey;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=message-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-cache.d.ts","sourceRoot":"","sources":["../../src/cache/message-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,KAAK,EAAE,WAAW,EAAE,UAAU,GAAE,MAAa;IAKzD;;;;;OAKG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMjE;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAuBlE;;;;;;OAMG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvF;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBxF;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC;;;;OAIG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnE;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK9D;;OAEG;YACW,UAAU;IAUxB;;OAEG;IACH,OAAO,CAAC,MAAM;CAGf"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageCache = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Redis Cache Adapter for Message System
|
|
6
|
+
* Handles caching of messages with TTL support
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { MessageCache, IRedisCache } from "@ecashlib/shared-message";
|
|
10
|
+
*
|
|
11
|
+
* class YourRedisAdapter implements IRedisCache {
|
|
12
|
+
* // implement methods using your redis wrapper
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* const adapter = new YourRedisAdapter();
|
|
16
|
+
* const cache = new MessageCache(adapter, 3600);
|
|
17
|
+
*/
|
|
18
|
+
class MessageCache {
|
|
19
|
+
constructor(redis, defaultTTL = 3600) {
|
|
20
|
+
this.redis = redis;
|
|
21
|
+
this.defaultTTL = defaultTTL;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get message content from cache
|
|
25
|
+
* @param code - Message code (e.g., "ECASH_00001")
|
|
26
|
+
* @param language - Language code (e.g., "vi", "en")
|
|
27
|
+
* @returns Message content or null if not found
|
|
28
|
+
*/
|
|
29
|
+
async get(code, language) {
|
|
30
|
+
const key = this.getKey(code, language);
|
|
31
|
+
const cached = await this.redis.get(key);
|
|
32
|
+
return cached;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all languages for a message code from cache
|
|
36
|
+
* @param code - Message code
|
|
37
|
+
* @returns Object with all language translations
|
|
38
|
+
*/
|
|
39
|
+
async getAll(code) {
|
|
40
|
+
const pattern = `message:${code}:*`;
|
|
41
|
+
const keys = await this.delPattern(pattern);
|
|
42
|
+
if (keys.length === 0) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
const value = await this.redis.get(key);
|
|
48
|
+
if (value) {
|
|
49
|
+
// Extract language from key: "message:ECASH_00001:vi" -> "vi"
|
|
50
|
+
const language = key.split(":").pop();
|
|
51
|
+
if (language) {
|
|
52
|
+
result[language] = value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Set message content to cache with TTL
|
|
60
|
+
* @param code - Message code
|
|
61
|
+
* @param language - Language code
|
|
62
|
+
* @param content - Message content
|
|
63
|
+
* @param ttl - Time to live in seconds (optional, uses default)
|
|
64
|
+
*/
|
|
65
|
+
async set(code, language, content, ttl) {
|
|
66
|
+
const key = this.getKey(code, language);
|
|
67
|
+
await this.redis.set(key, content, ttl ?? this.defaultTTL);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Set all languages for a message code
|
|
71
|
+
* @param code - Message code
|
|
72
|
+
* @param content - Object with all language translations
|
|
73
|
+
* @param ttl - Time to live in seconds (optional)
|
|
74
|
+
*/
|
|
75
|
+
async setAll(code, content, ttl) {
|
|
76
|
+
const expireTtl = ttl ?? this.defaultTTL;
|
|
77
|
+
const keyValues = {};
|
|
78
|
+
for (const [language, message] of Object.entries(content)) {
|
|
79
|
+
const key = this.getKey(code, language);
|
|
80
|
+
keyValues[key] = message;
|
|
81
|
+
}
|
|
82
|
+
await this.redis.mset(keyValues);
|
|
83
|
+
// Set TTL for each key individually since mset doesn't support TTL
|
|
84
|
+
for (const key of Object.keys(keyValues)) {
|
|
85
|
+
await this.redis.set(key, keyValues[key], expireTtl);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Delete message from cache (all languages)
|
|
90
|
+
* @param code - Message code
|
|
91
|
+
*/
|
|
92
|
+
async delete(code) {
|
|
93
|
+
const pattern = `message:${code}:*`;
|
|
94
|
+
await this.delPattern(pattern);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Delete specific language from cache
|
|
98
|
+
* @param code - Message code
|
|
99
|
+
* @param language - Language code
|
|
100
|
+
*/
|
|
101
|
+
async deleteLanguage(code, language) {
|
|
102
|
+
const key = this.getKey(code, language);
|
|
103
|
+
await this.redis.del(key);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Clear all message cache (use with caution!)
|
|
107
|
+
*/
|
|
108
|
+
async flush() {
|
|
109
|
+
const pattern = "message:*";
|
|
110
|
+
await this.delPattern(pattern);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check if message exists in cache
|
|
114
|
+
*/
|
|
115
|
+
async exists(code, language) {
|
|
116
|
+
const key = this.getKey(code, language);
|
|
117
|
+
return await this.redis.exists(key);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Helper to delete keys by pattern
|
|
121
|
+
*/
|
|
122
|
+
async delPattern(pattern) {
|
|
123
|
+
// This requires the adapter to implement delPattern properly
|
|
124
|
+
// For fastify.redis, keys() returns array, then we call del()
|
|
125
|
+
const keys = [];
|
|
126
|
+
// We need to scan for keys matching pattern
|
|
127
|
+
// Since IRedisCache.delPattern returns the deleted keys, we use it
|
|
128
|
+
return await this.redis.delPattern(pattern);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate cache key
|
|
132
|
+
*/
|
|
133
|
+
getKey(code, language) {
|
|
134
|
+
return `message:${code}:${language}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.MessageCache = MessageCache;
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { IRedisCache } from "../cache/cache-interfaces";
|
|
2
|
+
import { MessageServicePort, MessageContent } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Cached Message Service
|
|
5
|
+
*
|
|
6
|
+
* Logic:
|
|
7
|
+
* 1. Check Redis cache FIRST
|
|
8
|
+
* 2. If miss → Query DB via MessageServicePort
|
|
9
|
+
* 3. Save result to Redis cache
|
|
10
|
+
* 4. Return cached data
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { CachedMessageService, IRedisCache } from "@ecashlib/shared-message";
|
|
15
|
+
*
|
|
16
|
+
* // Your Redis adapter implementing IRedisCache
|
|
17
|
+
* class RedisAdapter implements IRedisCache {
|
|
18
|
+
* constructor(private redis: FastifyInstance["redis"]) {}
|
|
19
|
+
* // implement methods...
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* const adapter = new RedisAdapter(fastify.redis);
|
|
23
|
+
* const cachedService = new CachedMessageService(adapter, dbService, 3600);
|
|
24
|
+
*
|
|
25
|
+
* const message = await cachedService.getMessageContentByCode("ECASH_00001", "vi");
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class CachedMessageService implements MessageServicePort {
|
|
29
|
+
private readonly cache;
|
|
30
|
+
private readonly dbService;
|
|
31
|
+
/**
|
|
32
|
+
* @param redis - IRedisCache implementation (your Redis wrapper)
|
|
33
|
+
* @param dbService - Database service implementing MessageServicePort
|
|
34
|
+
* @param cacheTTL - Cache TTL in seconds (default: 3600)
|
|
35
|
+
*/
|
|
36
|
+
constructor(redis: IRedisCache, dbService: MessageServicePort, cacheTTL?: number);
|
|
37
|
+
/**
|
|
38
|
+
* Get message content with cache logic:
|
|
39
|
+
* 1. Check Redis cache
|
|
40
|
+
* 2. If miss → Query DB
|
|
41
|
+
* 3. Save to cache
|
|
42
|
+
* 4. Return result
|
|
43
|
+
*/
|
|
44
|
+
getMessageContentByCode(code: string, language: string): Promise<MessageContent | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Get all languages for a message code (with cache)
|
|
47
|
+
*/
|
|
48
|
+
getAllLanguages(code: string): Promise<Record<string, string> | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Invalidate cache for a message (call after update)
|
|
51
|
+
*/
|
|
52
|
+
invalidate(code: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Invalidate cache for a specific language (call after update)
|
|
55
|
+
*/
|
|
56
|
+
invalidateLanguage(code: string, language: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Set message to cache directly (use after create)
|
|
59
|
+
*/
|
|
60
|
+
setToCache(code: string, content: Record<string, string>): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Extract classification code from message code
|
|
63
|
+
* e.g., "ECASH_00001" -> "ECASH"
|
|
64
|
+
*/
|
|
65
|
+
private extractClassificationCode;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=cached-message-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-message-service.d.ts","sourceRoot":"","sources":["../../src/message/cached-message-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,oBAAqB,YAAW,kBAAkB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAE/C;;;;OAIG;gBACS,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,QAAQ,CAAC,EAAE,MAAM;IAKhF;;;;;;OAMG;IACG,uBAAuB,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAuBjC;;OAEG;IACG,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAW3E;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C;;OAEG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;OAEG;IACG,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;IAIhB;;;OAGG;IACH,OAAO,CAAC,yBAAyB;CAIlC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CachedMessageService = void 0;
|
|
4
|
+
const message_cache_1 = require("../cache/message-cache");
|
|
5
|
+
/**
|
|
6
|
+
* Cached Message Service
|
|
7
|
+
*
|
|
8
|
+
* Logic:
|
|
9
|
+
* 1. Check Redis cache FIRST
|
|
10
|
+
* 2. If miss → Query DB via MessageServicePort
|
|
11
|
+
* 3. Save result to Redis cache
|
|
12
|
+
* 4. Return cached data
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { CachedMessageService, IRedisCache } from "@ecashlib/shared-message";
|
|
17
|
+
*
|
|
18
|
+
* // Your Redis adapter implementing IRedisCache
|
|
19
|
+
* class RedisAdapter implements IRedisCache {
|
|
20
|
+
* constructor(private redis: FastifyInstance["redis"]) {}
|
|
21
|
+
* // implement methods...
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* const adapter = new RedisAdapter(fastify.redis);
|
|
25
|
+
* const cachedService = new CachedMessageService(adapter, dbService, 3600);
|
|
26
|
+
*
|
|
27
|
+
* const message = await cachedService.getMessageContentByCode("ECASH_00001", "vi");
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
class CachedMessageService {
|
|
31
|
+
/**
|
|
32
|
+
* @param redis - IRedisCache implementation (your Redis wrapper)
|
|
33
|
+
* @param dbService - Database service implementing MessageServicePort
|
|
34
|
+
* @param cacheTTL - Cache TTL in seconds (default: 3600)
|
|
35
|
+
*/
|
|
36
|
+
constructor(redis, dbService, cacheTTL) {
|
|
37
|
+
this.cache = new message_cache_1.MessageCache(redis, cacheTTL);
|
|
38
|
+
this.dbService = dbService;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get message content with cache logic:
|
|
42
|
+
* 1. Check Redis cache
|
|
43
|
+
* 2. If miss → Query DB
|
|
44
|
+
* 3. Save to cache
|
|
45
|
+
* 4. Return result
|
|
46
|
+
*/
|
|
47
|
+
async getMessageContentByCode(code, language) {
|
|
48
|
+
// 1. Check Redis cache FIRST
|
|
49
|
+
const cached = await this.cache.get(code, language);
|
|
50
|
+
if (cached) {
|
|
51
|
+
return {
|
|
52
|
+
code,
|
|
53
|
+
classificationCode: this.extractClassificationCode(code),
|
|
54
|
+
message: cached
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// 2. Cache miss → Query DB
|
|
58
|
+
const dbResult = await this.dbService.getMessageContentByCode(code, language);
|
|
59
|
+
if (!dbResult) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
// 3. Save to cache
|
|
63
|
+
await this.cache.set(code, language, dbResult.message);
|
|
64
|
+
return dbResult;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get all languages for a message code (with cache)
|
|
68
|
+
*/
|
|
69
|
+
async getAllLanguages(code) {
|
|
70
|
+
const cached = await this.cache.getAll(code);
|
|
71
|
+
if (cached) {
|
|
72
|
+
return cached;
|
|
73
|
+
}
|
|
74
|
+
// Cache miss - would need to query DB
|
|
75
|
+
// This is optional, can be implemented if needed
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Invalidate cache for a message (call after update)
|
|
80
|
+
*/
|
|
81
|
+
async invalidate(code) {
|
|
82
|
+
await this.cache.delete(code);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Invalidate cache for a specific language (call after update)
|
|
86
|
+
*/
|
|
87
|
+
async invalidateLanguage(code, language) {
|
|
88
|
+
await this.cache.deleteLanguage(code, language);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set message to cache directly (use after create)
|
|
92
|
+
*/
|
|
93
|
+
async setToCache(code, content) {
|
|
94
|
+
await this.cache.setAll(code, content);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Extract classification code from message code
|
|
98
|
+
* e.g., "ECASH_00001" -> "ECASH"
|
|
99
|
+
*/
|
|
100
|
+
extractClassificationCode(code) {
|
|
101
|
+
const parts = code.split("_");
|
|
102
|
+
return parts.slice(0, -1).join("_");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.CachedMessageService = CachedMessageService;
|
package/dist/message/index.d.ts
CHANGED
|
@@ -1 +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"}
|
|
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;AAChC,cAAc,0BAA0B,CAAC"}
|
package/dist/message/index.js
CHANGED
|
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./types"), exports);
|
|
18
18
|
__exportStar(require("./message-helper"), exports);
|
|
19
19
|
__exportStar(require("./message-error"), exports);
|
|
20
|
+
__exportStar(require("./cached-message-service"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/message/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/message/types.ts"],"names":[],"mappings":"AAAA;;;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"}
|
package/package.json
CHANGED