@alacard-project/config-sdk 1.1.1 → 1.1.4
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/dist/clients/config.client.d.ts +0 -3
- package/dist/clients/config.client.js +11 -70
- package/dist/{modules/config.module.d.ts → config.module.d.ts} +1 -1
- package/dist/{modules/config.module.js → config.module.js} +12 -14
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -2
- package/package.json +62 -59
- package/eslint.config.mjs +0 -29
- package/proto/config.proto +0 -39
- package/src/clients/config.client.ts +0 -252
- package/src/clients/vault.client.ts +0 -82
- package/src/constants/index.ts +0 -1
- package/src/enums/env.enum.ts +0 -7
- package/src/generated/config.ts +0 -834
- package/src/index.ts +0 -8
- package/src/modules/config.module.ts +0 -28
- package/src/types/config.types.ts +0 -38
- package/src/types/grpc.types.ts +0 -15
- package/src/types/types.ts +0 -12
- package/src/utils/nest-helpers.ts +0 -69
- package/test/config.client.spec.ts +0 -108
- package/test/vault.client.spec.ts +0 -62
- package/tsconfig.json +0 -21
- /package/dist/utils/{nest-helpers.d.ts → config.helpers.d.ts} +0 -0
- /package/dist/utils/{nest-helpers.js → config.helpers.js} +0 -0
package/src/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export * from './clients/config.client';
|
|
2
|
-
export * from './clients/vault.client';
|
|
3
|
-
export * from './modules/config.module';
|
|
4
|
-
export * from './utils/nest-helpers';
|
|
5
|
-
export * from './constants';
|
|
6
|
-
export * from './enums/env.enum';
|
|
7
|
-
export * from './types/config.types';
|
|
8
|
-
export * from './types/grpc.types';
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { DynamicModule, Module, Global, Provider } from '@nestjs/common';
|
|
2
|
-
import { ConfigClient } from '../clients/config.client';
|
|
3
|
-
import { ConfigOptions } from '../types/config.types';
|
|
4
|
-
import { CONFIG_OPTIONS } from '../constants';
|
|
5
|
-
|
|
6
|
-
@Global()
|
|
7
|
-
@Module({})
|
|
8
|
-
export class ConfigModule {
|
|
9
|
-
public static forRoot(options: ConfigOptions): DynamicModule {
|
|
10
|
-
const optionsProvider: Provider = {
|
|
11
|
-
provide: CONFIG_OPTIONS,
|
|
12
|
-
useValue: options,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const configClientProvider: Provider = {
|
|
16
|
-
provide: ConfigClient,
|
|
17
|
-
useFactory: async (): Promise<ConfigClient> => {
|
|
18
|
-
return await ConfigClient.initialize(options);
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
module: ConfigModule,
|
|
24
|
-
providers: [optionsProvider, configClientProvider],
|
|
25
|
-
exports: [configClientProvider, optionsProvider],
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface VaultOptions {
|
|
2
|
-
address: string;
|
|
3
|
-
roleId: string;
|
|
4
|
-
secretId: string;
|
|
5
|
-
pkiPath?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface VaultCerts {
|
|
9
|
-
ca: string;
|
|
10
|
-
certificate: string;
|
|
11
|
-
privateKey: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TLSConfig {
|
|
15
|
-
rootCert: Buffer;
|
|
16
|
-
clientCert: Buffer;
|
|
17
|
-
clientKey: Buffer;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ConfigOptions {
|
|
21
|
-
serviceName: string;
|
|
22
|
-
environment: string;
|
|
23
|
-
grpcUrl: string;
|
|
24
|
-
version?: string;
|
|
25
|
-
kafkaBrokers?: string[];
|
|
26
|
-
useDotenvFallback?: boolean;
|
|
27
|
-
internalKey?: string;
|
|
28
|
-
tls?: TLSConfig;
|
|
29
|
-
vault?: VaultOptions;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ConfigUpdatedEvent {
|
|
33
|
-
service: string;
|
|
34
|
-
environment: string;
|
|
35
|
-
version: string;
|
|
36
|
-
key: string;
|
|
37
|
-
value: string;
|
|
38
|
-
}
|
package/src/types/grpc.types.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GetConfigRequest as GeneratedGetConfigRequest,
|
|
3
|
-
ConfigResponse as GeneratedConfigResponse,
|
|
4
|
-
SetConfigRequest as GeneratedSetConfigRequest,
|
|
5
|
-
ListConfigsRequest as GeneratedListConfigsRequest,
|
|
6
|
-
ListConfigsResponse as GeneratedListConfigsResponse,
|
|
7
|
-
ServiceConfig as GeneratedServiceConfig
|
|
8
|
-
} from '../generated/config';
|
|
9
|
-
|
|
10
|
-
export interface GetConfigRequest extends GeneratedGetConfigRequest { }
|
|
11
|
-
export interface ConfigResponse extends GeneratedConfigResponse { }
|
|
12
|
-
export interface SetConfigRequest extends GeneratedSetConfigRequest { }
|
|
13
|
-
export interface ListConfigsRequest extends GeneratedListConfigsRequest { }
|
|
14
|
-
export interface ListConfigsResponse extends GeneratedListConfigsResponse { }
|
|
15
|
-
export interface ServiceConfig extends GeneratedServiceConfig { }
|
package/src/types/types.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Environment } from '../enums/env.enum';
|
|
2
|
-
import { ConfigClient } from '../clients/config.client';
|
|
3
|
-
|
|
4
|
-
export abstract class BaseConfigService {
|
|
5
|
-
constructor() { }
|
|
6
|
-
|
|
7
|
-
public get<T = string>(key: string, defaultValue?: T): T {
|
|
8
|
-
// 1. Try Config Client (if initialized)
|
|
9
|
-
try {
|
|
10
|
-
const client = ConfigClient.getInstance();
|
|
11
|
-
const val = client.get(key);
|
|
12
|
-
if (val !== undefined && val !== '') {
|
|
13
|
-
return val as unknown as T;
|
|
14
|
-
}
|
|
15
|
-
} catch (_e) {
|
|
16
|
-
// Client not initialized yet, fall through to process.env
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// 2. Try process.env
|
|
20
|
-
const envVal = process.env[key];
|
|
21
|
-
if (envVal !== undefined) {
|
|
22
|
-
return envVal as unknown as T;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return defaultValue as T;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public getRequiredString(key: string): string {
|
|
29
|
-
const value = this.get<string>(key);
|
|
30
|
-
if (!value && process.env.NODE_ENV !== Environment.TEST) {
|
|
31
|
-
throw new Error(`Configuration Error: ${key} is required`);
|
|
32
|
-
}
|
|
33
|
-
return value || '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public getRequiredNumber(key: string): number {
|
|
37
|
-
const value = this.get<string>(key);
|
|
38
|
-
if (!value && process.env.NODE_ENV !== Environment.TEST) {
|
|
39
|
-
throw new Error(`Configuration Error: ${key} is required`);
|
|
40
|
-
}
|
|
41
|
-
return parseInt(value || '0', 10);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public getOptionalString(key: string, defaultValue: string): string {
|
|
45
|
-
return this.get<string>(key, defaultValue) || defaultValue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
public getOptionalNumber(key: string, defaultValue: number): number {
|
|
49
|
-
const value = this.get<string>(key);
|
|
50
|
-
return value ? parseInt(value, 10) : defaultValue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get environment(): Environment {
|
|
54
|
-
return this.get<Environment>('NODE_ENV', Environment.DEVELOPMENT) || Environment.DEVELOPMENT;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get databaseUrl(): string {
|
|
58
|
-
return this.getRequiredString('DATABASE_URL');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get kafkaBrokers(): string[] {
|
|
62
|
-
const brokers = this.get<string>('KAFKA_BROKERS', 'localhost:9092');
|
|
63
|
-
return (brokers || 'localhost:9092').split(',');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get isProduction(): boolean {
|
|
67
|
-
return this.environment === Environment.PRODUCTION;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { ConfigClient } from '../src/clients/config.client';
|
|
2
|
-
import * as grpc from '@grpc/grpc-js';
|
|
3
|
-
import { Kafka } from 'kafkajs';
|
|
4
|
-
import CircuitBreaker from 'opossum';
|
|
5
|
-
|
|
6
|
-
// Mock gRPC generated code
|
|
7
|
-
jest.mock('../src/generated/config', () => ({
|
|
8
|
-
ConfigServiceClient: jest.fn().mockImplementation(() => ({
|
|
9
|
-
getConfig: jest.fn().mockImplementation((req, meta, cb) => {
|
|
10
|
-
cb(null, { values: { REMOTE_KEY: 'REMOTE_VALUE' } });
|
|
11
|
-
}),
|
|
12
|
-
})),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
jest.mock('@grpc/grpc-js');
|
|
16
|
-
jest.mock('kafkajs');
|
|
17
|
-
jest.mock('opossum');
|
|
18
|
-
jest.mock('dotenv', () => ({
|
|
19
|
-
config: jest.fn().mockReturnValue({ parsed: { DOTENV_KEY: 'DOTENV_VALUE' } })
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
describe('ConfigClient', () => {
|
|
23
|
-
const options = {
|
|
24
|
-
serviceName: 'test-service',
|
|
25
|
-
environment: 'test',
|
|
26
|
-
grpcUrl: 'localhost:50051',
|
|
27
|
-
kafkaBrokers: ['localhost:9092'],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let client: ConfigClient;
|
|
31
|
-
let mockConsumer: any;
|
|
32
|
-
|
|
33
|
-
beforeEach(async () => {
|
|
34
|
-
jest.clearAllMocks();
|
|
35
|
-
|
|
36
|
-
// Mock Kafka
|
|
37
|
-
mockConsumer = {
|
|
38
|
-
connect: jest.fn().mockResolvedValue(undefined),
|
|
39
|
-
subscribe: jest.fn().mockResolvedValue(undefined),
|
|
40
|
-
run: jest.fn().mockResolvedValue(undefined),
|
|
41
|
-
};
|
|
42
|
-
(Kafka as any).mockImplementation(() => ({
|
|
43
|
-
consumer: () => mockConsumer,
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
// Mock CircuitBreaker
|
|
47
|
-
(CircuitBreaker as any).mockImplementation((fn: any) => ({
|
|
48
|
-
fire: jest.fn().mockImplementation((...args: any[]) => fn(...args)),
|
|
49
|
-
on: jest.fn(),
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
// Mock grpc
|
|
53
|
-
(grpc.credentials.createInsecure as any).mockReturnValue({});
|
|
54
|
-
|
|
55
|
-
// Reset singleton
|
|
56
|
-
(ConfigClient as any).instance = null;
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should initialize and load config from various sources', async () => {
|
|
60
|
-
process.env.APP_TEST_KEY = 'ENV_VALUE';
|
|
61
|
-
|
|
62
|
-
client = await ConfigClient.initialize(options);
|
|
63
|
-
|
|
64
|
-
expect(client.get('APP_TEST_KEY')).toBe('ENV_VALUE');
|
|
65
|
-
expect(client.get('REMOTE_KEY')).toBe('REMOTE_VALUE');
|
|
66
|
-
expect(client.get('DOTENV_KEY')).toBe('DOTENV_VALUE');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should return default value if key not found', async () => {
|
|
70
|
-
client = await ConfigClient.initialize(options);
|
|
71
|
-
expect(client.get('NON_EXISTENT', 'DEFAULT')).toBe('DEFAULT');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should handle numeric and boolean values', async () => {
|
|
75
|
-
client = await ConfigClient.initialize(options);
|
|
76
|
-
(client as any).configMap['INT_KEY'] = '42';
|
|
77
|
-
(client as any).configMap['BOOL_KEY'] = 'true';
|
|
78
|
-
|
|
79
|
-
expect(client.getInt('INT_KEY')).toBe(42);
|
|
80
|
-
expect(client.getBool('BOOL_KEY')).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should handle hot-reload from Kafka', async () => {
|
|
84
|
-
let kafkaHandler: any;
|
|
85
|
-
mockConsumer.run.mockImplementation(({ eachMessage }: any) => {
|
|
86
|
-
kafkaHandler = eachMessage;
|
|
87
|
-
return Promise.resolve();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
client = await ConfigClient.initialize(options);
|
|
91
|
-
|
|
92
|
-
expect(kafkaHandler).toBeDefined();
|
|
93
|
-
|
|
94
|
-
// Simulate Kafka message
|
|
95
|
-
await kafkaHandler({
|
|
96
|
-
message: {
|
|
97
|
-
value: Buffer.from(JSON.stringify({
|
|
98
|
-
service: 'test-service',
|
|
99
|
-
environment: 'test',
|
|
100
|
-
key: 'HOT_KEY',
|
|
101
|
-
value: 'HOT_VALUE'
|
|
102
|
-
}))
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(client.get('HOT_KEY')).toBe('HOT_VALUE');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { VaultClient } from '../src/clients/vault.client';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
|
|
4
|
-
jest.mock('axios');
|
|
5
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
6
|
-
|
|
7
|
-
describe('VaultClient', () => {
|
|
8
|
-
const options = {
|
|
9
|
-
address: 'http://localhost:8200',
|
|
10
|
-
roleId: 'role-id',
|
|
11
|
-
secretId: 'secret-id',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
let client: VaultClient;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
jest.clearAllMocks();
|
|
18
|
-
mockedAxios.create.mockReturnValue(mockedAxios as any);
|
|
19
|
-
client = new VaultClient(options);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should login and get secrets', async () => {
|
|
23
|
-
mockedAxios.post.mockResolvedValueOnce({
|
|
24
|
-
data: {
|
|
25
|
-
auth: {
|
|
26
|
-
client_token: 'test-token',
|
|
27
|
-
lease_duration: 3600,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
mockedAxios.get.mockResolvedValueOnce({
|
|
33
|
-
data: {
|
|
34
|
-
data: {
|
|
35
|
-
data: {
|
|
36
|
-
KEY: 'VALUE',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const secrets = await client.getKVSecrets('path');
|
|
43
|
-
expect(secrets).toEqual({ KEY: 'VALUE' });
|
|
44
|
-
expect(mockedAxios.post).toHaveBeenCalledWith('/auth/approle/login', expect.any(Object));
|
|
45
|
-
expect(mockedAxios.get).toHaveBeenCalledWith('/secret/data/path');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should handle 404 for secrets', async () => {
|
|
49
|
-
mockedAxios.post.mockResolvedValueOnce({
|
|
50
|
-
data: {
|
|
51
|
-
auth: { client_token: 't', lease_duration: 3600 },
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
mockedAxios.get.mockRejectedValueOnce({
|
|
56
|
-
response: { status: 404 },
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const secrets = await client.getKVSecrets('missing');
|
|
60
|
-
expect(secrets).toEqual({});
|
|
61
|
-
});
|
|
62
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"module": "CommonJS",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"moduleResolution": "node"
|
|
13
|
-
},
|
|
14
|
-
"include": [
|
|
15
|
-
"src/**/*"
|
|
16
|
-
],
|
|
17
|
-
"exclude": [
|
|
18
|
-
"node_modules",
|
|
19
|
-
"dist"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
File without changes
|
|
File without changes
|