@ecashlib/shared-message
v1.0.3
Published
Shared utilities for Ecash microservices - Message handling, i18n, error helpers
Maintainers
Readme
@ecash/shared
Shared utilities for Ecash microservices - Message handling, i18n, error helpers.
Features
- i18n Message System: Get localized messages from database with Redis caching
- Parameter Replacement: Dynamic message templates with
{placeholder}support - Language Detection: Auto-detect language from request headers or query params
- Error Helpers: Throw errors with dynamic, localized messages
- Framework Agnostic: Works with any Redis implementation via
IRedisCacheinterface
Installation
npm install @ecashlib/shared-messageSetup
1. Prepare Database Messages
Create messages in your messages collection:
{
"code": "ECASH_00001",
"classificationCode": "ECASH",
"content": {
"vi": "Metadata không hợp lệ theo schema của project type '{projectTypeCode}': {errors}",
"en": "Metadata is invalid according to project type '{projectTypeCode}' schema: {errors}",
"ko": "프로젝트 유형 '{projectTypeCode}'의 스키마에 따라 메타데이터가 유효하지 않습니다: {errors}"
}
}2. Implement Database Service (WITHOUT cache)
// ecash-marketplace/src/infrastructure/adapters/outbound/message/message.repository.ts
import { MessageServicePort } from "@ecashlib/shared-message";
export class MessageRepository implements MessageServicePort {
async getMessageContentByCode(code: string, language: string) {
// ONLY query DB - NO cache logic here
const message = await db.collection("messages").findOne({ code });
if (!message) return null;
return {
code: message.code,
classificationCode: message.classificationCode,
message: message.content[language] || null
};
}
}3. Implement IRedisCache Adapter (WITH your Redis wrapper)
// src/infrastructure/adapters/outbound/cache/redis-cache.adapter.ts
import { IRedisCache } from "@ecashlib/shared-message";
export class RedisCacheAdapter implements IRedisCache {
constructor(private redis: FastifyInstance["redis"]) {}
async get(key: string): Promise<string | null> {
return await this.redis.get(key);
}
async set(key: string, value: string, ttl?: number): Promise<void> {
if (ttl) {
await this.redis.setex(key, ttl, value);
} else {
await this.redis.set(key, value);
}
}
async mset(keyValues: Record<string, string>): Promise<void> {
const pipeline = this.redis.pipeline();
for (const [key, value] of Object.entries(keyValues)) {
pipeline.set(key, value);
}
await pipeline.exec();
}
async exists(key: string): Promise<boolean> {
const result = await this.redis.exists(key);
return result === 1;
}
async del(key: string): Promise<number> {
return await this.redis.del(key);
}
async delPattern(pattern: string): Promise<string[]> {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
return keys;
}
async hgetall(key: string): Promise<Record<string, string>> {
return await this.redis.hgetall(key);
}
async hset(key: string, field: string, value: string): Promise<void> {
await this.redis.hset(key, field, value);
}
async hdel(key: string, field?: string): Promise<void> {
if (field) {
await this.redis.hdel(key, field);
} else {
await this.redis.hdel(key);
}
}
}4. Use CachedMessageService (WITH cache)
import { CachedMessageService } from "@ecashlib/shared-message";
import { RedisCacheAdapter } from "./infrastructure/adapters/outbound/cache/redis-cache.adapter";
import { MessageRepository } from "./infrastructure/adapters/outbound/message/message.repository";
// Setup Redis adapter
const redisAdapter = new RedisCacheAdapter(fastify.redis);
// Create cached service
const dbService = new MessageRepository(); // Your DB service
const cachedMessageService = new CachedMessageService(
redisAdapter,
dbService,
3600 // Cache TTL: 1 hour
);Cache Flow
┌─────────────────────────────────────────────────────────────┐
│ getMessageContentByCode() │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────┴──────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Check Redis │ MISS │ Query MongoDB │
│ │ ────────> │ │
│ message:ECASH_ │ │ messages │
│ 00001:vi │ │ {code, ...} │
└─────────────────┘ └─────────────────┘
│ │
│ HIT │
▼ │
┌───────────────┐ │
│ Return data │ │
└───────────────┘ │
│
▼
┌─────────────────┐
│ Save to Redis │
│ message:ECASH_ │
│ 00001:vi │
│ TTL: 3600s │
└─────────────────┘
│
▼
┌─────────────────┐
│ Return data │
└─────────────────┘Usage
1. Get Message with Cache
import { CachedMessageService } from "@ecashlib/shared-message";
const message = await cachedMessageService.getMessageContentByCode(
"ECASH_00001",
"vi"
);
// First call: Query DB → Save to Redis → Return
// Next call: Return from Redis (FAST!)2. Get Message with Parameters
import { getMessage } from "@ecashlib/shared-message";
const message = await getMessage(
cachedMessageService,
"ECASH_00001",
"vi",
{ projectTypeCode: "APARTMENT", errors: "field required" }
);
// Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"3. Get Message from Request
import { getMessageFromRequest } from "@ecashlib/shared-message";
const message = await getMessageFromRequest(
request,
cachedMessageService,
"ECASH_00001",
{ projectTypeCode: "APARTMENT" }
);4. Invalidate Cache (after update)
// Invalidate all languages for a message
await cachedMessageService.invalidate("ECASH_00001");
// Invalidate specific language
await cachedMessageService.invalidateLanguage("ECASH_00001", "vi");
// Set to cache manually (after create)
await cachedMessageService.setToCache("ECASH_00001", {
vi: "Nội dung tiếng Việt",
en: "English content"
});5. Throw Message Error
import { throwMessageError } from "@ecashlib/shared-message";
await throwMessageError(
cachedMessageService,
"vi",
"ECASH_00001",
{ projectTypeCode: "APARTMENT", errors: "field required" },
"COMM0400"
);API Reference
CachedMessageService
| Method | Description |
|--------|-------------|
| getMessageContentByCode(code, language) | Get message with cache logic |
| getAllLanguages(code) | Get all languages from cache |
| invalidate(code) | Delete all languages from cache |
| invalidateLanguage(code, language) | Delete specific language from cache |
| setToCache(code, content) | Manually set message to cache |
MessageCache (Low-level)
| Method | Description |
|--------|-------------|
| get(code, language) | Get from Redis |
| getAll(code) | Get all languages from Redis |
| set(code, language, content, ttl?) | Set to Redis with TTL |
| setAll(code, content, ttl?) | Set multiple languages to Redis |
| delete(code) | Delete all languages from Redis |
| deleteLanguage(code, language) | Delete specific language |
| exists(code, language) | Check if exists in cache |
| flush() | Delete ALL message cache (use with caution!) |
IRedisCache Interface
Your microservice needs to implement this interface to work with the cache system:
export interface IRedisCache {
get(key: string): Promise<string | null>;
set(key: string, value: string, ttl?: number): Promise<void>;
mset(keyValues: Record<string, string>): Promise<void>;
exists(key: string): Promise<boolean>;
del(key: string): Promise<number>;
delPattern(pattern: string): Promise<string[]>;
hgetall(key: string): Promise<Record<string, string>>;
hset(key: string, field: string, value: string): Promise<void>;
hdel(key: string, field?: string): Promise<void>;
}Example: Full Setup in Microservice
// 1. Setup
import { CachedMessageService, getMessage, throwMessageError } from "@ecashlib/shared-message";
import { RedisCacheAdapter } from "./infrastructure/adapters/outbound/cache/redis-cache.adapter";
import { MessageRepository } from "./infrastructure/adapters/outbound/message/message.repository";
const redisAdapter = new RedisCacheAdapter(fastify.redis);
const dbService = new MessageRepository(); // Your DB service
const messageService = new CachedMessageService(redisAdapter, dbService);
// 2. Use in controller/service
async function getErrorMessage(request: Request) {
const language = request.headers["accept-language"] || "vi";
const message = await messageService.getMessageContentByCode(
"ECASH_00001",
language
);
return message.message;
}
// 3. Use with parameters
async function throwValidationError(request: Request, projectTypeCode: string, errors: string) {
const language = request.headers["accept-language"] || "vi";
await throwMessageError(
messageService,
language,
"ECASH_00001",
{ projectTypeCode, errors }
);
}Publishing
cd ecash-shared
npm run build
npm publishLicense
PRIVATE - Ecash internal use only.