@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 CHANGED
@@ -1,14 +1,6 @@
1
- # @alacard-project/config-sdk
1
+ # Alacard Config SDK
2
2
 
3
- **Alacard Config SDK** — это клиентская библиотека для взаимодействия с централизованным микросервисом конфигураций (CCS) через gRPC. SDK обеспечивает строго типизированный доступ к настройкам, поддерживает горячую перезагрузку через Kafka и имеет встроенный fallback на `.env` файлы.
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 { ConfigClient } from '@alacard-project/config-sdk';
27
-
28
- async function bootstrap() {
29
- const config = await ConfigClient.initialize({
30
- serviceName: 'auth-service',
31
- environment: process.env.NODE_ENV || 'development',
32
- grpcUrl: process.env.GRPC_CONFIG_URL || 'config-microservice:50055',
33
- kafkaBrokers: ['kafka:9092'],
34
- useDotenvFallback: true, // Использовать .env если gRPC недоступен
35
- });
36
-
37
- const dbUrl = config.get('DATABASE_URL');
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
- ### Использование в NestJS
43
-
44
- Рекомендуется создать `ConfigService` обертку или использовать `ConfigModule` (если он реализован в вашем проекте), который внутри вызывает `ConfigClient.initialize`.
28
+ ## Возможности
45
29
 
46
- ## Как это работает (Порядок загрузки)
30
+ - Загрузка секретов из Vault.
31
+ - Резервное копирование и кэширование конфигурации.
32
+ - **Отказоустойчивость**: Если Vault недоступен, SDK может использовать локальные переменные окружения или кэш.
47
33
 
48
- 1. **.env Fallback**: SDK пробует прочитать локальный `.env.<env>` файл (если `useDotenvFallback: true`).
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?: string): string;
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
- this.logger.error(`Critical initialization failure: ${error.message}`);
84
- throw error;
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', 'RABBITMQ_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
- const envFiles = [`.env.${environment}`, '.env.local', '.env'];
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
- Object.assign(this.configMap, result.parsed);
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
- Object.assign(this.configMap, vaultSecrets);
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
- this.logger.warn(`Vault secrets unavailable: ${error.message}. Continuing with local config.`);
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
- return new Promise((resolve) => {
124
- const request = { serviceName, environment, version };
125
- const metadata = new grpc.Metadata();
126
- if (this.options.internalKey) {
127
- metadata.set('x-internal-key', this.options.internalKey);
128
- }
129
- const timeout = 3000;
130
- const timer = setTimeout(() => {
131
- this.logger.warn(`Remote config (gRPC) timeout after ${timeout}ms. Using local/vault fallback.`);
132
- resolve();
133
- }, timeout);
134
- client.getConfig(request, metadata, (error, response) => {
135
- clearTimeout(timer);
136
- if (error) {
137
- this.logger.warn(`Remote config (gRPC) unavailable: ${error.message}. Using local/vault fallback.`);
138
- return resolve();
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
- resolve();
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
- this.logger.error(`Failed to parse config update: ${err.message}`);
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
- this.logger.warn(`Config watch mode (Kafka) failed: ${error.message}`);
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
- return this.configMap[key] || defaultValue;
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
- return val ? parseInt(val, 10) : defaultValue;
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.response?.status === 404) {
50
- this.logger.warn(`Secret not found at path: ${path}`);
51
- return {};
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
- this.logger.error(`Failed to fetch secrets from path ${path}: ${error.message}`);
54
- throw new Error(`Failed to fetch secrets: ${error.message}`);
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
- this.logger.error(`Failed to issue certificate for ${commonName}: ${error.message}`);
73
- throw new Error(`Failed to issue certificate: ${error.message}`);
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
  }
@@ -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
- protected readonly configService: any;
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
- configService;
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
- if (this.remoteConfig[key] !== undefined) {
16
- return this.remoteConfig[key];
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 this.configService.get(key, defaultValue);
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
- // @ts-check
2
- import eslint from '@eslint/js';
3
- import tseslint from 'typescript-eslint';
4
- import globals from 'globals';
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 tseslint.config(
6
+ export default [
7
+ js.configs.recommended,
7
8
  {
8
- ignores: ['dist/**'],
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
- '@typescript-eslint/interface-name-prefix': 'off',
21
- '@typescript-eslint/explicit-function-return-type': 'off',
22
- '@typescript-eslint/explicit-module-boundary-types': 'off',
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.9",
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.18.0",
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
- "eslint": "^9.18.0",
36
- "globals": "^15.14.0",
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.20.0"
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": "src",
51
+ "rootDir": ".",
50
52
  "testRegex": ".*\\.spec\\.ts$",
51
53
  "transform": {
52
54
  "^.+\\.(t|j)s$": "ts-jest"
53
55
  },
54
56
  "collectCoverageFrom": [
55
- "**/*.(t|j)s"
57
+ "src/**/*.(t|j)s"
56
58
  ],
57
- "coverageDirectory": "../coverage",
59
+ "coverageDirectory": "./coverage",
58
60
  "testEnvironment": "node"
59
61
  }
60
- }
62
+ }