205 lines
6.5 KiB
TypeScript
205 lines
6.5 KiB
TypeScript
import { readFileSync, existsSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import type { StorageConfig } from '@mosaicstack/storage';
|
|
import type { QueueAdapterConfig as QueueConfig } from '@mosaicstack/queue';
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Types */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export type StorageTier = 'local' | 'standalone' | 'federated';
|
|
|
|
export interface MemoryConfigRef {
|
|
type: 'pgvector' | 'sqlite-vec' | 'keyword';
|
|
}
|
|
|
|
export interface MosaicConfig {
|
|
tier: StorageTier;
|
|
storage: StorageConfig;
|
|
queue: QueueConfig;
|
|
memory: MemoryConfigRef;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Defaults */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export const DEFAULT_LOCAL_CONFIG: MosaicConfig = {
|
|
tier: 'local',
|
|
storage: { type: 'pglite', dataDir: '.mosaic/storage-pglite' },
|
|
queue: { type: 'local', dataDir: '.mosaic/queue' },
|
|
memory: { type: 'keyword' },
|
|
};
|
|
|
|
export const DEFAULT_STANDALONE_CONFIG: MosaicConfig = {
|
|
tier: 'standalone',
|
|
storage: { type: 'postgres', url: 'postgresql://mosaic:mosaic@localhost:5432/mosaic' },
|
|
queue: { type: 'bullmq' },
|
|
memory: { type: 'keyword' },
|
|
};
|
|
|
|
export const DEFAULT_FEDERATED_CONFIG: MosaicConfig = {
|
|
tier: 'federated',
|
|
storage: {
|
|
type: 'postgres',
|
|
url: 'postgresql://mosaic:mosaic@localhost:5433/mosaic',
|
|
enableVector: true,
|
|
},
|
|
queue: { type: 'bullmq' },
|
|
memory: { type: 'pgvector' },
|
|
};
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Validation */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
const VALID_TIERS = new Set<string>(['local', 'standalone', 'federated']);
|
|
const VALID_STORAGE_TYPES = new Set<string>(['postgres', 'pglite', 'files']);
|
|
const VALID_QUEUE_TYPES = new Set<string>(['bullmq', 'local']);
|
|
const VALID_MEMORY_TYPES = new Set<string>(['pgvector', 'sqlite-vec', 'keyword']);
|
|
|
|
export function validateConfig(raw: unknown): MosaicConfig {
|
|
if (typeof raw !== 'object' || raw === null) {
|
|
throw new Error('MosaicConfig must be a non-null object');
|
|
}
|
|
|
|
const obj = raw as Record<string, unknown>;
|
|
|
|
// tier
|
|
let tier = obj['tier'];
|
|
// Deprecated alias: 'team' → 'standalone' (kept for backward-compat with 0.0.x installs)
|
|
if (tier === 'team') {
|
|
process.stderr.write(
|
|
'[mosaic] DEPRECATED: tier="team" is deprecated — use "standalone" instead. ' +
|
|
'Update your mosaic.config.json.\n',
|
|
);
|
|
tier = 'standalone';
|
|
}
|
|
if (typeof tier !== 'string' || !VALID_TIERS.has(tier)) {
|
|
throw new Error(
|
|
`Invalid tier "${String(tier)}" — expected "local", "standalone", or "federated"`,
|
|
);
|
|
}
|
|
|
|
// storage
|
|
const storage = obj['storage'];
|
|
if (typeof storage !== 'object' || storage === null) {
|
|
throw new Error('config.storage must be a non-null object');
|
|
}
|
|
const storageType = (storage as Record<string, unknown>)['type'];
|
|
if (typeof storageType !== 'string' || !VALID_STORAGE_TYPES.has(storageType)) {
|
|
throw new Error(`Invalid storage.type "${String(storageType)}"`);
|
|
}
|
|
|
|
// queue
|
|
const queue = obj['queue'];
|
|
if (typeof queue !== 'object' || queue === null) {
|
|
throw new Error('config.queue must be a non-null object');
|
|
}
|
|
const queueType = (queue as Record<string, unknown>)['type'];
|
|
if (typeof queueType !== 'string' || !VALID_QUEUE_TYPES.has(queueType)) {
|
|
throw new Error(`Invalid queue.type "${String(queueType)}"`);
|
|
}
|
|
|
|
// memory
|
|
const memory = obj['memory'];
|
|
if (typeof memory !== 'object' || memory === null) {
|
|
throw new Error('config.memory must be a non-null object');
|
|
}
|
|
const memoryType = (memory as Record<string, unknown>)['type'];
|
|
if (typeof memoryType !== 'string' || !VALID_MEMORY_TYPES.has(memoryType)) {
|
|
throw new Error(`Invalid memory.type "${String(memoryType)}"`);
|
|
}
|
|
|
|
return {
|
|
tier: tier as StorageTier,
|
|
storage: storage as StorageConfig,
|
|
queue: queue as QueueConfig,
|
|
memory: memory as MemoryConfigRef,
|
|
};
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Loader */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
export function detectFromEnv(): MosaicConfig {
|
|
const tier = process.env['MOSAIC_STORAGE_TIER'];
|
|
|
|
if (tier === 'federated') {
|
|
if (process.env['DATABASE_URL']) {
|
|
return {
|
|
...DEFAULT_FEDERATED_CONFIG,
|
|
storage: {
|
|
type: 'postgres',
|
|
url: process.env['DATABASE_URL'],
|
|
enableVector: true,
|
|
},
|
|
queue: {
|
|
type: 'bullmq',
|
|
url: process.env['VALKEY_URL'],
|
|
},
|
|
};
|
|
}
|
|
// MOSAIC_STORAGE_TIER=federated without DATABASE_URL — use the default
|
|
// federated config (port 5433, enableVector: true, pgvector memory).
|
|
return DEFAULT_FEDERATED_CONFIG;
|
|
}
|
|
|
|
if (tier === 'standalone') {
|
|
if (process.env['DATABASE_URL']) {
|
|
return {
|
|
...DEFAULT_STANDALONE_CONFIG,
|
|
storage: {
|
|
type: 'postgres',
|
|
url: process.env['DATABASE_URL'],
|
|
},
|
|
queue: {
|
|
type: 'bullmq',
|
|
url: process.env['VALKEY_URL'],
|
|
},
|
|
};
|
|
}
|
|
// MOSAIC_STORAGE_TIER=standalone without DATABASE_URL — use the default
|
|
// standalone config instead of silently falling back to local.
|
|
return DEFAULT_STANDALONE_CONFIG;
|
|
}
|
|
|
|
// Legacy: DATABASE_URL set without MOSAIC_STORAGE_TIER — treat as standalone.
|
|
if (process.env['DATABASE_URL']) {
|
|
return {
|
|
...DEFAULT_STANDALONE_CONFIG,
|
|
storage: {
|
|
type: 'postgres',
|
|
url: process.env['DATABASE_URL'],
|
|
},
|
|
queue: {
|
|
type: 'bullmq',
|
|
url: process.env['VALKEY_URL'],
|
|
},
|
|
};
|
|
}
|
|
|
|
return DEFAULT_LOCAL_CONFIG;
|
|
}
|
|
|
|
export function loadConfig(configPath?: string): MosaicConfig {
|
|
// 1. Explicit path or default location
|
|
const paths = configPath
|
|
? [resolve(configPath)]
|
|
: [
|
|
resolve(process.cwd(), 'mosaic.config.json'),
|
|
resolve(process.cwd(), '../../mosaic.config.json'), // monorepo root when cwd is apps/gateway
|
|
];
|
|
|
|
for (const p of paths) {
|
|
if (existsSync(p)) {
|
|
const raw: unknown = JSON.parse(readFileSync(p, 'utf-8'));
|
|
return validateConfig(raw);
|
|
}
|
|
}
|
|
|
|
// 2. Fall back to env-var detection
|
|
return detectFromEnv();
|
|
}
|