99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
import { getContainer } from '@bytelyst/cosmos';
|
|
import { config } from '../config/index.js';
|
|
import logger from '../utils/logger.js';
|
|
|
|
export interface DynamicConfigEntry {
|
|
key: string;
|
|
value: string;
|
|
description?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
interface DynamicConfigDocument extends DynamicConfigEntry {
|
|
id: string;
|
|
productId: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
const CONTAINER_NAME = 'dynamic_config';
|
|
|
|
function isCosmosConfigured(): boolean {
|
|
return Boolean(config.COSMOS_ENDPOINT && config.COSMOS_KEY);
|
|
}
|
|
|
|
function normalizeEntry(entry: Partial<DynamicConfigEntry> | null | undefined): DynamicConfigEntry | null {
|
|
const key = String(entry?.key || '').trim();
|
|
if (!key) return null;
|
|
return {
|
|
key,
|
|
value: String(entry?.value ?? ''),
|
|
description: entry?.description ? String(entry.description) : '',
|
|
updated_at: entry?.updated_at ? String(entry.updated_at) : undefined,
|
|
};
|
|
}
|
|
|
|
async function listFromCosmos(): Promise<DynamicConfigEntry[]> {
|
|
if (!isCosmosConfigured()) return [];
|
|
const container = getContainer(CONTAINER_NAME);
|
|
const { resources } = await container.items
|
|
.query<DynamicConfigDocument>({
|
|
query: 'SELECT * FROM c WHERE c.productId = @productId ORDER BY c.key',
|
|
parameters: [{ name: '@productId', value: config.PRODUCT_ID }],
|
|
})
|
|
.fetchAll();
|
|
|
|
return resources
|
|
.map((resource) => normalizeEntry({
|
|
key: resource.key,
|
|
value: resource.value,
|
|
description: resource.description,
|
|
updated_at: resource.updatedAt,
|
|
}))
|
|
.filter((entry): entry is DynamicConfigEntry => Boolean(entry));
|
|
}
|
|
|
|
export async function listDynamicConfigEntries(): Promise<DynamicConfigEntry[]> {
|
|
if (!isCosmosConfigured()) {
|
|
logger.warn('[DynamicConfig] Cosmos is not configured; dynamic config overrides are unavailable in this environment.');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
return await listFromCosmos();
|
|
} catch (error) {
|
|
logger.warn(`[DynamicConfig] Cosmos read failed: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function upsertDynamicConfigEntries(entries: DynamicConfigEntry[]): Promise<void> {
|
|
const normalized = entries
|
|
.map((entry) => normalizeEntry(entry))
|
|
.filter((entry): entry is DynamicConfigEntry => Boolean(entry));
|
|
|
|
if (normalized.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const now = new Date().toISOString();
|
|
if (!isCosmosConfigured()) {
|
|
logger.warn('[DynamicConfig] Cosmos is not configured; skipping dynamic config upsert.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const container = getContainer(CONTAINER_NAME);
|
|
await Promise.all(normalized.map((entry) => container.items.upsert<DynamicConfigDocument>({
|
|
id: entry.key,
|
|
productId: config.PRODUCT_ID,
|
|
key: entry.key,
|
|
value: entry.value,
|
|
description: entry.description || '',
|
|
updated_at: entry.updated_at || now,
|
|
updatedAt: entry.updated_at || now,
|
|
})));
|
|
} catch (error) {
|
|
logger.warn(`[DynamicConfig] Cosmos upsert failed: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
}
|
|
}
|