unified config
This commit is contained in:
parent
e7c0fe2798
commit
3877902ff4
13 changed files with 856 additions and 476 deletions
155
libs/core/config/src/schemas/__tests__/unified-app.test.ts
Normal file
155
libs/core/config/src/schemas/__tests__/unified-app.test.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import { describe, expect, it } from 'bun:test';
|
||||
import { unifiedAppSchema, toUnifiedConfig, getStandardServiceName } from '../unified-app.schema';
|
||||
|
||||
describe('UnifiedAppConfig', () => {
|
||||
describe('getStandardServiceName', () => {
|
||||
it('should convert camelCase to kebab-case', () => {
|
||||
expect(getStandardServiceName('dataIngestion')).toBe('data-ingestion');
|
||||
expect(getStandardServiceName('dataPipeline')).toBe('data-pipeline');
|
||||
expect(getStandardServiceName('webApi')).toBe('web-api');
|
||||
});
|
||||
|
||||
it('should handle already kebab-case names', () => {
|
||||
expect(getStandardServiceName('data-ingestion')).toBe('data-ingestion');
|
||||
expect(getStandardServiceName('web-api')).toBe('web-api');
|
||||
});
|
||||
|
||||
it('should handle single word names', () => {
|
||||
expect(getStandardServiceName('api')).toBe('api');
|
||||
expect(getStandardServiceName('worker')).toBe('worker');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unifiedAppSchema transform', () => {
|
||||
it('should set serviceName from name if not provided', () => {
|
||||
const config = {
|
||||
name: 'test-app',
|
||||
version: '1.0.0',
|
||||
service: {
|
||||
name: 'webApi',
|
||||
port: 3000,
|
||||
},
|
||||
log: { level: 'info' },
|
||||
};
|
||||
|
||||
const result = unifiedAppSchema.parse(config);
|
||||
expect(result.service.serviceName).toBe('web-api');
|
||||
});
|
||||
|
||||
it('should keep existing serviceName if provided', () => {
|
||||
const config = {
|
||||
name: 'test-app',
|
||||
version: '1.0.0',
|
||||
service: {
|
||||
name: 'webApi',
|
||||
serviceName: 'custom-name',
|
||||
port: 3000,
|
||||
},
|
||||
log: { level: 'info' },
|
||||
};
|
||||
|
||||
const result = unifiedAppSchema.parse(config);
|
||||
expect(result.service.serviceName).toBe('custom-name');
|
||||
});
|
||||
|
||||
it('should sync nested and flat database configs', () => {
|
||||
const config = {
|
||||
name: 'test-app',
|
||||
version: '1.0.0',
|
||||
service: { name: 'test', port: 3000 },
|
||||
log: { level: 'info' },
|
||||
database: {
|
||||
postgres: {
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
database: 'test',
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
mongodb: {
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'test',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = unifiedAppSchema.parse(config);
|
||||
|
||||
// Should have both nested and flat structure
|
||||
expect(result.postgres).toBeDefined();
|
||||
expect(result.mongodb).toBeDefined();
|
||||
expect(result.database?.postgres).toBeDefined();
|
||||
expect(result.database?.mongodb).toBeDefined();
|
||||
|
||||
// Values should match
|
||||
expect(result.postgres?.host).toBe('localhost');
|
||||
expect(result.postgres?.port).toBe(5432);
|
||||
expect(result.mongodb?.uri).toBe('mongodb://localhost:27017');
|
||||
});
|
||||
|
||||
it('should handle questdb ilpPort to influxPort mapping', () => {
|
||||
const config = {
|
||||
name: 'test-app',
|
||||
version: '1.0.0',
|
||||
service: { name: 'test', port: 3000 },
|
||||
log: { level: 'info' },
|
||||
database: {
|
||||
questdb: {
|
||||
host: 'localhost',
|
||||
ilpPort: 9009,
|
||||
httpPort: 9000,
|
||||
pgPort: 8812,
|
||||
database: 'questdb',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = unifiedAppSchema.parse(config);
|
||||
expect(result.questdb).toBeDefined();
|
||||
expect((result.questdb as any).influxPort).toBe(9009);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toUnifiedConfig', () => {
|
||||
it('should convert StockBotAppConfig to UnifiedAppConfig', () => {
|
||||
const stockBotConfig = {
|
||||
name: 'stock-bot',
|
||||
version: '1.0.0',
|
||||
environment: 'development',
|
||||
service: {
|
||||
name: 'dataIngestion',
|
||||
port: 3001,
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
log: {
|
||||
level: 'info',
|
||||
format: 'json',
|
||||
},
|
||||
database: {
|
||||
postgres: {
|
||||
enabled: true,
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
database: 'stock',
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
dragonfly: {
|
||||
enabled: true,
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
db: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const unified = toUnifiedConfig(stockBotConfig);
|
||||
|
||||
expect(unified.service.serviceName).toBe('data-ingestion');
|
||||
expect(unified.redis).toBeDefined();
|
||||
expect(unified.redis?.host).toBe('localhost');
|
||||
expect(unified.postgres).toBeDefined();
|
||||
expect(unified.postgres?.host).toBe('localhost');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -12,6 +12,10 @@ export * from './provider.schema';
|
|||
export { baseAppSchema } from './base-app.schema';
|
||||
export type { BaseAppConfig } from './base-app.schema';
|
||||
|
||||
// Export unified schema for standardized configuration
|
||||
export { unifiedAppSchema, toUnifiedConfig, getStandardServiceName } from './unified-app.schema';
|
||||
export type { UnifiedAppConfig } from './unified-app.schema';
|
||||
|
||||
// Keep AppConfig for backward compatibility (deprecated)
|
||||
// @deprecated Use baseAppSchema and extend it for your specific app
|
||||
import { z } from 'zod';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { z } from 'zod';
|
|||
// Common service configuration
|
||||
export const serviceConfigSchema = z.object({
|
||||
name: z.string(),
|
||||
serviceName: z.string().optional(), // Standard service name (kebab-case)
|
||||
port: z.number().min(1).max(65535),
|
||||
host: z.string().default('0.0.0.0'),
|
||||
healthCheckPath: z.string().default('/health'),
|
||||
|
|
@ -96,6 +97,11 @@ export const browserConfigSchema = z.object({
|
|||
|
||||
// Proxy manager configuration
|
||||
export const proxyConfigSchema = z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
cachePrefix: z.string().default('proxy:'),
|
||||
ttl: z.number().default(3600),
|
||||
webshare: z.object({
|
||||
apiKey: z.string(),
|
||||
apiUrl: z.string().default('https://proxy.webshare.io/api/v2/'),
|
||||
}).optional(),
|
||||
});
|
||||
|
|
|
|||
76
libs/core/config/src/schemas/unified-app.schema.ts
Normal file
76
libs/core/config/src/schemas/unified-app.schema.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { z } from 'zod';
|
||||
import { baseAppSchema } from './base-app.schema';
|
||||
import {
|
||||
postgresConfigSchema,
|
||||
mongodbConfigSchema,
|
||||
questdbConfigSchema,
|
||||
dragonflyConfigSchema
|
||||
} from './database.schema';
|
||||
|
||||
/**
|
||||
* Unified application configuration schema that provides both nested and flat access
|
||||
* to database configurations for backward compatibility while maintaining a clean structure
|
||||
*/
|
||||
export const unifiedAppSchema = baseAppSchema.extend({
|
||||
// Flat database configs for DI system (these take precedence)
|
||||
redis: dragonflyConfigSchema.optional(),
|
||||
mongodb: mongodbConfigSchema.optional(),
|
||||
postgres: postgresConfigSchema.optional(),
|
||||
questdb: questdbConfigSchema.optional(),
|
||||
}).transform((data) => {
|
||||
// Ensure service.serviceName is set from service.name if not provided
|
||||
if (data.service && !data.service.serviceName) {
|
||||
data.service.serviceName = data.service.name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
|
||||
}
|
||||
|
||||
// If flat configs exist, ensure they're also in the nested database object
|
||||
if (data.redis || data.mongodb || data.postgres || data.questdb) {
|
||||
data.database = {
|
||||
...data.database,
|
||||
dragonfly: data.redis || data.database?.dragonfly,
|
||||
mongodb: data.mongodb || data.database?.mongodb,
|
||||
postgres: data.postgres || data.database?.postgres,
|
||||
questdb: data.questdb || data.database?.questdb,
|
||||
};
|
||||
}
|
||||
|
||||
// If nested configs exist but flat ones don't, copy them to flat structure
|
||||
if (data.database) {
|
||||
if (data.database.dragonfly && !data.redis) {
|
||||
data.redis = data.database.dragonfly;
|
||||
}
|
||||
if (data.database.mongodb && !data.mongodb) {
|
||||
data.mongodb = data.database.mongodb;
|
||||
}
|
||||
if (data.database.postgres && !data.postgres) {
|
||||
data.postgres = data.database.postgres;
|
||||
}
|
||||
if (data.database.questdb && !data.questdb) {
|
||||
// Handle the ilpPort -> influxPort mapping for DI system
|
||||
const questdbConfig = { ...data.database.questdb };
|
||||
if ('ilpPort' in questdbConfig && !('influxPort' in questdbConfig)) {
|
||||
(questdbConfig as any).influxPort = questdbConfig.ilpPort;
|
||||
}
|
||||
data.questdb = questdbConfig;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
export type UnifiedAppConfig = z.infer<typeof unifiedAppSchema>;
|
||||
|
||||
/**
|
||||
* Helper to convert StockBotAppConfig to UnifiedAppConfig
|
||||
*/
|
||||
export function toUnifiedConfig(config: any): UnifiedAppConfig {
|
||||
return unifiedAppSchema.parse(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get standardized service name
|
||||
*/
|
||||
export function getStandardServiceName(serviceName: string): string {
|
||||
// Convert camelCase to kebab-case
|
||||
return serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue