@alacard-project/config-sdk 1.0.9 → 1.1.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 +21 -38
- package/dist/clients/config.client.d.ts +4 -1
- package/dist/clients/config.client.js +107 -35
- package/dist/clients/vault.client.js +13 -8
- package/dist/generated/config.js +0 -6
- package/dist/utils/nest-helpers.d.ts +1 -4
- package/dist/utils/nest-helpers.js +18 -11
- package/eslint.config.mjs +16 -14
- package/package.json +13 -11
- package/src/clients/config.client.ts +113 -43
- package/src/clients/vault.client.ts +16 -11
- package/src/generated/config.ts +1 -8
- package/src/utils/nest-helpers.ts +19 -10
- package/test/config.client.spec.ts +93 -19
- package/test/vault.client.spec.ts +62 -0
- package/dist/config-client.d.ts +0 -32
- package/dist/config-client.js +0 -172
- package/dist/config.module.d.ts +0 -5
- package/dist/config.module.js +0 -75
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +0 -4
- package/dist/nest-helpers.d.ts +0 -16
- package/dist/nest-helpers.js +0 -57
- package/dist/proto/config.d.ts +0 -116
- package/dist/proto/config.js +0 -629
- package/dist/vault-client.d.ts +0 -21
- package/dist/vault-client.js +0 -72
package/README.md
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Alacard Config SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Основные возможности
|
|
6
|
-
|
|
7
|
-
- 🚀 **gRPC-First**: Высокая производительность и низкая задержка при получении конфигураций.
|
|
8
|
-
- 🛡️ **Строгая типизация**: Автоматическая генерация TypeScript-интерфейсов из Protocol Buffers (`ts-proto`).
|
|
9
|
-
- ⚡ **Hot-Reloading**: Мгновенное обновление параметров в памяти приложения при изменении в базе (через Kafka).
|
|
10
|
-
- 📂 **.env Fallback**: Поддержка локальных `.env.<environment>` файлов для разработки и как резервный вариант.
|
|
11
|
-
- 🏗️ **Instance-based**: Поддержка нескольких экземпляров клиента в одном процессе.
|
|
3
|
+
SDK для централизованного управления конфигурацией в микросервисах Alacard. Поддерживает интеграцию с HashiCorp Vault и встроенным Config Service.
|
|
12
4
|
|
|
13
5
|
## Установка
|
|
14
6
|
|
|
@@ -16,38 +8,29 @@
|
|
|
16
8
|
npm install @alacard-project/config-sdk
|
|
17
9
|
```
|
|
18
10
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
### Инициализация
|
|
22
|
-
|
|
23
|
-
Инициализируйте клиент при запуске вашего приложения (например, в `main.ts` или `bootstrap.ts`):
|
|
11
|
+
## Использование
|
|
24
12
|
|
|
25
13
|
```typescript
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log('Config loaded!');
|
|
39
|
-
}
|
|
14
|
+
import { ConfigModule } from '@alacard-project/config-sdk';
|
|
15
|
+
|
|
16
|
+
@Module({
|
|
17
|
+
imports: [
|
|
18
|
+
ConfigModule.forRoot({
|
|
19
|
+
vaultUrl: process.env.VAULT_URL,
|
|
20
|
+
vaultToken: process.env.VAULT_TOKEN,
|
|
21
|
+
serviceName: 'my-service'
|
|
22
|
+
})
|
|
23
|
+
]
|
|
24
|
+
})
|
|
25
|
+
export class AppModule {}
|
|
40
26
|
```
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Рекомендуется создать `ConfigService` обертку или использовать `ConfigModule` (если он реализован в вашем проекте), который внутри вызывает `ConfigClient.initialize`.
|
|
28
|
+
## Возможности
|
|
45
29
|
|
|
46
|
-
|
|
30
|
+
- Загрузка секретов из Vault.
|
|
31
|
+
- Резервное копирование и кэширование конфигурации.
|
|
32
|
+
- **Отказоустойчивость**: Если Vault недоступен, SDK может использовать локальные переменные окружения или кэш.
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
2. **gRPC Config Service**: SDK запрашивает финальный конфиг через gRPC.
|
|
50
|
-
3. **Kafka Watch**: Запускается подписка на изменения для обновления параметров "на лету".
|
|
34
|
+
## Лицензия
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
© 2026 Alacard Team
|
|
36
|
+
MIT
|
|
@@ -5,6 +5,7 @@ export declare class ConfigClient {
|
|
|
5
5
|
private consumer;
|
|
6
6
|
private options;
|
|
7
7
|
private vaultClient;
|
|
8
|
+
private breaker;
|
|
8
9
|
private static instance;
|
|
9
10
|
private constructor();
|
|
10
11
|
static initialize(options: ConfigOptions): Promise<ConfigClient>;
|
|
@@ -13,9 +14,11 @@ export declare class ConfigClient {
|
|
|
13
14
|
private loadProcessEnv;
|
|
14
15
|
private loadDotEnv;
|
|
15
16
|
private loadVaultSecrets;
|
|
17
|
+
private initCircuitBreaker;
|
|
16
18
|
private fetchRemoteConfig;
|
|
17
19
|
private startWatching;
|
|
18
|
-
get(key: string, defaultValue?:
|
|
20
|
+
get<T = string>(key: string, defaultValue?: T): T;
|
|
19
21
|
getInt(key: string, defaultValue?: number): number;
|
|
22
|
+
getBool(key: string, defaultValue?: boolean): boolean;
|
|
20
23
|
getAll(): Record<string, string>;
|
|
21
24
|
}
|
|
@@ -32,12 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.ConfigClient = void 0;
|
|
37
40
|
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
38
41
|
const kafkajs_1 = require("kafkajs");
|
|
39
42
|
const path = __importStar(require("path"));
|
|
40
43
|
const dotenv = __importStar(require("dotenv"));
|
|
44
|
+
const opossum_1 = __importDefault(require("opossum"));
|
|
41
45
|
const common_1 = require("@nestjs/common");
|
|
42
46
|
const config_1 = require("../generated/config");
|
|
43
47
|
const vault_client_1 = require("./vault.client");
|
|
@@ -47,11 +51,15 @@ class ConfigClient {
|
|
|
47
51
|
consumer = null;
|
|
48
52
|
options;
|
|
49
53
|
vaultClient = null;
|
|
54
|
+
breaker = null;
|
|
50
55
|
static instance = null;
|
|
51
56
|
constructor(options) {
|
|
52
57
|
this.options = options;
|
|
53
58
|
}
|
|
54
59
|
static async initialize(options) {
|
|
60
|
+
if (this.instance) {
|
|
61
|
+
return this.instance;
|
|
62
|
+
}
|
|
55
63
|
const client = new ConfigClient(options);
|
|
56
64
|
await client.init();
|
|
57
65
|
this.instance = client;
|
|
@@ -66,26 +74,31 @@ class ConfigClient {
|
|
|
66
74
|
async init() {
|
|
67
75
|
const { serviceName, environment, grpcUrl, version = 'v1', useDotenvFallback = true } = this.options;
|
|
68
76
|
try {
|
|
77
|
+
// 1. Load from Process Env (Highest Priority for overrides)
|
|
69
78
|
this.loadProcessEnv();
|
|
79
|
+
// 2. Load from DotEnv (if enabled)
|
|
70
80
|
if (useDotenvFallback) {
|
|
71
81
|
this.loadDotEnv(environment);
|
|
72
82
|
}
|
|
83
|
+
// 3. Load from Vault (if configured)
|
|
73
84
|
if (this.options.vault) {
|
|
74
85
|
await this.loadVaultSecrets(serviceName);
|
|
75
86
|
}
|
|
87
|
+
// 4. Fetch Remote Config via gRPC (with Circuit Breaker)
|
|
76
88
|
await this.fetchRemoteConfig(serviceName, environment, grpcUrl, version);
|
|
89
|
+
// 5. Start Kafka Watcher (if configured)
|
|
77
90
|
if (this.options.kafkaBrokers && this.options.kafkaBrokers.length > 0) {
|
|
78
91
|
await this.startWatching();
|
|
79
92
|
}
|
|
80
93
|
this.logger.log(`Configuration initialized with ${Object.keys(this.configMap).length} keys`);
|
|
81
94
|
}
|
|
82
95
|
catch (error) {
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
97
|
+
this.logger.error(`Critical initialization failure: ${errorMsg}. Service starting with loaded config.`);
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
loadProcessEnv() {
|
|
88
|
-
const ALLOWED_ENV_PREFIXES = ['APP_', 'KAFKA_', 'DB_', 'REDIS_', 'GRPC_', 'PORT', 'NODE_ENV', 'DATABASE_URL'
|
|
101
|
+
const ALLOWED_ENV_PREFIXES = ['APP_', 'KAFKA_', 'DB_', 'REDIS_', 'GRPC_', 'PORT', 'NODE_ENV', 'DATABASE_URL'];
|
|
89
102
|
for (const [key, value] of Object.entries(process.env)) {
|
|
90
103
|
if (value && ALLOWED_ENV_PREFIXES.some(prefix => key.startsWith(prefix))) {
|
|
91
104
|
this.configMap[key] = value;
|
|
@@ -93,57 +106,92 @@ class ConfigClient {
|
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
loadDotEnv(environment) {
|
|
96
|
-
|
|
109
|
+
// Only support standard .env and environment-specific .env
|
|
110
|
+
const envFiles = [`.env.${environment}`, '.env'];
|
|
97
111
|
for (const file of envFiles) {
|
|
98
112
|
const filePath = path.resolve(process.cwd(), file);
|
|
99
113
|
const result = dotenv.config({ path: filePath });
|
|
100
114
|
if (result.parsed) {
|
|
101
|
-
|
|
115
|
+
// Do not overwrite existing keys (process.env wins)
|
|
116
|
+
for (const [key, value] of Object.entries(result.parsed)) {
|
|
117
|
+
if (!this.configMap[key]) {
|
|
118
|
+
this.configMap[key] = value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
102
121
|
this.logger.log(`Loaded variables from ${file}`);
|
|
103
122
|
}
|
|
104
123
|
}
|
|
105
124
|
}
|
|
106
125
|
async loadVaultSecrets(serviceName) {
|
|
126
|
+
if (!this.options.vault)
|
|
127
|
+
return;
|
|
107
128
|
this.vaultClient = new vault_client_1.VaultClient(this.options.vault);
|
|
108
129
|
try {
|
|
109
130
|
const vaultSecrets = await this.vaultClient.getKVSecrets(`config-service/${serviceName}`);
|
|
110
|
-
|
|
131
|
+
// Vault secrets should overwrite env vars if needed, or fill gaps.
|
|
132
|
+
// Usually Vault > Env, but here we treat Env > Vault for local overrides.
|
|
133
|
+
// Let's adopt a safe merge: Existing keys (from Env) take precedence.
|
|
134
|
+
for (const [key, value] of Object.entries(vaultSecrets)) {
|
|
135
|
+
if (!this.configMap[key]) {
|
|
136
|
+
this.configMap[key] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
111
139
|
this.logger.log(`Loaded secrets from Vault for ${serviceName}`);
|
|
112
140
|
}
|
|
113
141
|
catch (error) {
|
|
114
|
-
|
|
142
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
143
|
+
this.logger.warn(`Vault secrets unavailable: ${errorMsg}. Continuing with local config.`);
|
|
115
144
|
}
|
|
116
145
|
}
|
|
146
|
+
initCircuitBreaker(client) {
|
|
147
|
+
const fetchFunction = (request, metadata) => {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
client.getConfig(request, metadata, (error, response) => {
|
|
150
|
+
if (error)
|
|
151
|
+
return reject(error);
|
|
152
|
+
resolve(response.values || {});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
this.breaker = new opossum_1.default(fetchFunction, {
|
|
157
|
+
timeout: 5000, // 5s timeout
|
|
158
|
+
errorThresholdPercentage: 50, // 50% errors opens the breaker
|
|
159
|
+
resetTimeout: 10000, // wait 10s before trying again
|
|
160
|
+
});
|
|
161
|
+
this.breaker.on('open', () => this.logger.warn('Circuit Breaker (Config gRPC) is OPEN'));
|
|
162
|
+
this.breaker.on('halfOpen', () => this.logger.log('Circuit Breaker (Config gRPC) is HALF_OPEN'));
|
|
163
|
+
this.breaker.on('close', () => this.logger.log('Circuit Breaker (Config gRPC) is CLOSED'));
|
|
164
|
+
}
|
|
117
165
|
async fetchRemoteConfig(serviceName, environment, grpcUrl, version) {
|
|
118
166
|
let credentials = grpc.credentials.createInsecure();
|
|
119
167
|
if (this.options.tls) {
|
|
120
168
|
credentials = grpc.credentials.createSsl(this.options.tls.rootCert, this.options.tls.clientKey, this.options.tls.clientCert);
|
|
121
169
|
}
|
|
122
170
|
const client = new config_1.ConfigServiceClient(grpcUrl, credentials);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
if (response && response.values) {
|
|
141
|
-
Object.assign(this.configMap, response.values);
|
|
142
|
-
this.logger.log(`Remote config loaded from config-service`);
|
|
171
|
+
if (!this.breaker) {
|
|
172
|
+
this.initCircuitBreaker(client);
|
|
173
|
+
}
|
|
174
|
+
const request = { serviceName, environment, version };
|
|
175
|
+
const metadata = new grpc.Metadata();
|
|
176
|
+
if (this.options.internalKey) {
|
|
177
|
+
metadata.set('x-internal-key', this.options.internalKey);
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const values = await this.breaker.fire(request, metadata);
|
|
181
|
+
if (values) {
|
|
182
|
+
// Remote config fills in the gaps
|
|
183
|
+
for (const [key, value] of Object.entries(values)) {
|
|
184
|
+
if (!this.configMap[key]) {
|
|
185
|
+
this.configMap[key] = value;
|
|
186
|
+
}
|
|
143
187
|
}
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
}
|
|
188
|
+
this.logger.log(`Remote config loaded from config-service`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
193
|
+
this.logger.warn(`Remote config (gRPC) failure: ${errorMsg}. Using local/vault fallback.`);
|
|
194
|
+
}
|
|
147
195
|
}
|
|
148
196
|
async startWatching() {
|
|
149
197
|
if (this.consumer)
|
|
@@ -152,6 +200,10 @@ class ConfigClient {
|
|
|
152
200
|
const kafka = new kafkajs_1.Kafka({
|
|
153
201
|
clientId: `config-sdk-${this.options.serviceName}`,
|
|
154
202
|
brokers: this.options.kafkaBrokers,
|
|
203
|
+
retry: {
|
|
204
|
+
retries: 5, // Retry up to 5 times
|
|
205
|
+
maxRetryTime: 30000 // Max time spent retrying
|
|
206
|
+
}
|
|
155
207
|
});
|
|
156
208
|
const groupId = `config-watch-${this.options.serviceName}-${this.options.environment}`;
|
|
157
209
|
this.consumer = kafka.consumer({ groupId });
|
|
@@ -171,21 +223,41 @@ class ConfigClient {
|
|
|
171
223
|
}
|
|
172
224
|
}
|
|
173
225
|
catch (err) {
|
|
174
|
-
|
|
226
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
227
|
+
this.logger.error(`Failed to parse config update: ${errorMsg}`);
|
|
175
228
|
}
|
|
176
229
|
},
|
|
177
230
|
});
|
|
178
231
|
}
|
|
179
232
|
catch (error) {
|
|
180
|
-
|
|
233
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
234
|
+
this.logger.warn(`Config watch mode (Kafka) failed: ${errorMsg}`);
|
|
181
235
|
}
|
|
182
236
|
}
|
|
183
|
-
get(key, defaultValue
|
|
184
|
-
|
|
237
|
+
get(key, defaultValue) {
|
|
238
|
+
const val = this.configMap[key];
|
|
239
|
+
if (val === undefined || val === null) {
|
|
240
|
+
if (defaultValue !== undefined) {
|
|
241
|
+
return defaultValue;
|
|
242
|
+
}
|
|
243
|
+
// Strict mode: warn if key is missing and no default value
|
|
244
|
+
this.logger.warn(`Configuration key '${key}' not found and no default value provided.`);
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
return val;
|
|
185
248
|
}
|
|
186
249
|
getInt(key, defaultValue = 0) {
|
|
187
250
|
const val = this.get(key);
|
|
188
|
-
|
|
251
|
+
if (!val)
|
|
252
|
+
return defaultValue;
|
|
253
|
+
const parsed = parseInt(val, 10);
|
|
254
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
255
|
+
}
|
|
256
|
+
getBool(key, defaultValue = false) {
|
|
257
|
+
const val = this.get(key);
|
|
258
|
+
if (!val)
|
|
259
|
+
return defaultValue;
|
|
260
|
+
return val.toLowerCase() === 'true' || val === '1';
|
|
189
261
|
}
|
|
190
262
|
getAll() {
|
|
191
263
|
return { ...this.configMap };
|
|
@@ -34,7 +34,7 @@ class VaultClient {
|
|
|
34
34
|
this.logger.log('Successfully authenticated with Vault AppRole');
|
|
35
35
|
}
|
|
36
36
|
catch (error) {
|
|
37
|
-
const errorMsg = error.response?.data?.errors?.[0] || error.message;
|
|
37
|
+
const errorMsg = error instanceof Error ? error.response?.data?.errors?.[0] || error.message : String(error);
|
|
38
38
|
this.logger.error(`Vault login failed: ${errorMsg}`);
|
|
39
39
|
throw new Error(`Vault login failed: ${errorMsg}`);
|
|
40
40
|
}
|
|
@@ -46,12 +46,16 @@ class VaultClient {
|
|
|
46
46
|
return response.data.data.data;
|
|
47
47
|
}
|
|
48
48
|
catch (error) {
|
|
49
|
-
if (error
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
50
|
+
const axiosError = error;
|
|
51
|
+
if (axiosError.response?.status === 404) {
|
|
52
|
+
this.logger.warn(`Secret not found at path: ${path}`);
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
57
|
+
this.logger.error(`Failed to fetch secrets from path ${path}: ${errorMsg}`);
|
|
58
|
+
throw new Error(`Failed to fetch secrets: ${errorMsg}`);
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
async issueCertificate(commonName) {
|
|
@@ -69,8 +73,9 @@ class VaultClient {
|
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
75
|
catch (error) {
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
77
|
+
this.logger.error(`Failed to issue certificate for ${commonName}: ${errorMsg}`);
|
|
78
|
+
throw new Error(`Failed to issue certificate: ${errorMsg}`);
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
}
|
package/dist/generated/config.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
3
|
-
// versions:
|
|
4
|
-
// protoc-gen-ts_proto v2.11.0
|
|
5
|
-
// protoc v5.29.3
|
|
6
|
-
// source: config.proto
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.ConfigServiceClient = exports.ConfigServiceService = exports.ListConfigsResponse = exports.ServiceConfig_ValuesEntry = exports.ServiceConfig = exports.ListConfigsRequest = exports.SetConfigRequest = exports.ConfigResponse_ValuesEntry = exports.ConfigResponse = exports.GetConfigRequest = void 0;
|
|
9
|
-
/* eslint-disable */
|
|
10
4
|
const wire_1 = require("@bufbuild/protobuf/wire");
|
|
11
5
|
const grpc_js_1 = require("@grpc/grpc-js");
|
|
12
6
|
function createBaseGetConfigRequest() {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { Environment } from '../enums/env.enum';
|
|
2
2
|
export declare abstract class BaseConfigService {
|
|
3
|
-
|
|
4
|
-
private remoteConfig;
|
|
5
|
-
constructor(configService: any);
|
|
6
|
-
setRemoteConfig(config: Record<string, string>): void;
|
|
3
|
+
constructor();
|
|
7
4
|
protected get<T = string>(key: string, defaultValue?: T): T;
|
|
8
5
|
getRequiredString(key: string): string;
|
|
9
6
|
getRequiredNumber(key: string): number;
|
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseConfigService = void 0;
|
|
4
4
|
const env_enum_1 = require("../enums/env.enum");
|
|
5
|
+
const config_client_1 = require("../clients/config.client");
|
|
5
6
|
class BaseConfigService {
|
|
6
|
-
|
|
7
|
-
remoteConfig = {};
|
|
8
|
-
constructor(configService) {
|
|
9
|
-
this.configService = configService;
|
|
10
|
-
}
|
|
11
|
-
setRemoteConfig(config) {
|
|
12
|
-
this.remoteConfig = { ...this.remoteConfig, ...config };
|
|
13
|
-
}
|
|
7
|
+
constructor() { }
|
|
14
8
|
get(key, defaultValue) {
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
// 1. Try Config Client (if initialized)
|
|
10
|
+
try {
|
|
11
|
+
const client = config_client_1.ConfigClient.getInstance();
|
|
12
|
+
const val = client.get(key);
|
|
13
|
+
if (val !== undefined && val !== '') {
|
|
14
|
+
return val;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (_e) {
|
|
18
|
+
// Client not initialized yet, fall through to process.env
|
|
19
|
+
}
|
|
20
|
+
// 2. Try process.env
|
|
21
|
+
const envVal = process.env[key];
|
|
22
|
+
if (envVal !== undefined) {
|
|
23
|
+
return envVal;
|
|
17
24
|
}
|
|
18
|
-
return
|
|
25
|
+
return defaultValue;
|
|
19
26
|
}
|
|
20
27
|
getRequiredString(key) {
|
|
21
28
|
const value = this.get(key);
|
package/eslint.config.mjs
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import tsParser from "@typescript-eslint/parser";
|
|
4
|
+
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
5
5
|
|
|
6
|
-
export default
|
|
6
|
+
export default [
|
|
7
|
+
js.configs.recommended,
|
|
7
8
|
{
|
|
8
|
-
ignores: [
|
|
9
|
+
ignores: ["src/generated/**/*"],
|
|
9
10
|
},
|
|
10
|
-
eslint.configs.recommended,
|
|
11
|
-
...tseslint.configs.recommended,
|
|
12
11
|
{
|
|
12
|
+
files: ["src/**/*.ts"],
|
|
13
13
|
languageOptions: {
|
|
14
|
+
parser: tsParser,
|
|
14
15
|
globals: {
|
|
15
16
|
...globals.node,
|
|
16
17
|
...globals.jest,
|
|
17
18
|
},
|
|
18
19
|
},
|
|
20
|
+
plugins: {
|
|
21
|
+
"@typescript-eslint": tsPlugin,
|
|
22
|
+
},
|
|
19
23
|
rules: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
24
|
-
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
24
|
+
"no-console": "error",
|
|
25
|
+
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
|
26
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
25
27
|
},
|
|
26
28
|
},
|
|
27
|
-
|
|
29
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alacard-project/config-sdk",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=24.0.0"
|
|
6
6
|
},
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
|
-
"gen:proto": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=src/generated --proto_path=proto proto/config.proto --ts_proto_opt=outputServices=grpc-js,env=node,useOptionals=messages,exportCommonSymbols=false,esModuleInterop=true",
|
|
17
16
|
"lint": "eslint \"src/**/*.ts\" --fix",
|
|
18
17
|
"test": "jest",
|
|
18
|
+
"test:watch": "jest --watch",
|
|
19
19
|
"test:cov": "jest --coverage",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
@@ -25,20 +25,22 @@
|
|
|
25
25
|
"@grpc/proto-loader": "^0.7.13",
|
|
26
26
|
"axios": "^1.7.9",
|
|
27
27
|
"dotenv": "^16.4.7",
|
|
28
|
-
"kafkajs": "^2.2.4"
|
|
28
|
+
"kafkajs": "^2.2.4",
|
|
29
|
+
"opossum": "^9.0.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"@eslint/js": "^9.
|
|
32
|
+
"@eslint/js": "^9.39.2",
|
|
32
33
|
"@nestjs/common": "^11.1.12",
|
|
33
34
|
"@types/jest": "^29.5.14",
|
|
34
35
|
"@types/node": "^22.10.7",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
36
|
+
"@types/opossum": "^8.1.9",
|
|
37
|
+
"eslint": "^9.39.2",
|
|
38
|
+
"globals": "^15.15.0",
|
|
37
39
|
"jest": "^29.7.0",
|
|
38
40
|
"ts-jest": "^29.2.5",
|
|
39
41
|
"ts-proto": "^2.6.1",
|
|
40
42
|
"typescript": "^5.7.3",
|
|
41
|
-
"typescript-eslint": "^8.
|
|
43
|
+
"typescript-eslint": "^8.54.0"
|
|
42
44
|
},
|
|
43
45
|
"jest": {
|
|
44
46
|
"moduleFileExtensions": [
|
|
@@ -46,15 +48,15 @@
|
|
|
46
48
|
"json",
|
|
47
49
|
"ts"
|
|
48
50
|
],
|
|
49
|
-
"rootDir": "
|
|
51
|
+
"rootDir": ".",
|
|
50
52
|
"testRegex": ".*\\.spec\\.ts$",
|
|
51
53
|
"transform": {
|
|
52
54
|
"^.+\\.(t|j)s$": "ts-jest"
|
|
53
55
|
},
|
|
54
56
|
"collectCoverageFrom": [
|
|
55
|
-
"
|
|
57
|
+
"src/**/*.(t|j)s"
|
|
56
58
|
],
|
|
57
|
-
"coverageDirectory": "
|
|
59
|
+
"coverageDirectory": "./coverage",
|
|
58
60
|
"testEnvironment": "node"
|
|
59
61
|
}
|
|
60
|
-
}
|
|
62
|
+
}
|