/** * DI container — initialise les dependances une seule fois au boot et expose * un singleton typed pour les routes. Pour les tests, `setContainer` permet * d'injecter un mock complet sans toucher a `getContainer`. */ import type { Logger } from 'pino'; import { BaserowClient } from '../adapters/baserow-client.js'; import { RedisCache } from '../adapters/redis-cache.js'; import type { ApiTokenRecord } from '../middleware/auth.js'; import { parseTokens } from '../middleware/auth.js'; import { type RepoSet, TABLE_NAMES, type TableIds, buildRepos } from '../repos/baserow-repo.js'; import type { Config } from './config.js'; import { logger as rootLogger } from './logger.js'; export interface Container { config: Config; baserow: BaserowClient; redis: RedisCache; repos: RepoSet; tokens: ReadonlyMap; tableIds: TableIds; logger: Logger; } let _container: Container | null = null; export function getContainer(): Container { if (!_container) { throw new Error('Container not initialized — call initContainer() first'); } return _container; } export function setContainer(c: Container | null): void { _container = c; } export interface InitOptions { config: Config; /** Pour tests : skip le resolveTableIds reseau. */ tableIds?: TableIds; /** Pour tests : injecter une implem de Baserow/Redis. */ baserow?: BaserowClient; redis?: RedisCache; databaseId?: number; } export async function initContainer(opts: InitOptions): Promise { const { config } = opts; const baserow = opts.baserow ?? new BaserowClient({ baseUrl: config.baserowApiUrl, token: config.baserowApiToken, logger: rootLogger, }); const redis = opts.redis ?? new RedisCache({ url: config.redisUrl, logger: rootLogger }); let tableIds: TableIds; if (opts.tableIds) { tableIds = opts.tableIds; } else { if (typeof opts.databaseId !== 'number') { throw new Error('initContainer: databaseId requis si tableIds non fourni'); } const resolved = await baserow.resolveTableIds(opts.databaseId); tableIds = pickTableIds(resolved); } const repos = buildRepos(baserow, tableIds, rootLogger); const tokens = parseTokens(config.bridgeApiTokens); const container: Container = { config, baserow, redis, repos, tokens, tableIds, logger: rootLogger, }; setContainer(container); return container; } /** Verifie que toutes les tables attendues sont presentes dans le mapping name->id. */ function pickTableIds(resolved: Record): TableIds { const out: Partial = {}; for (const name of TABLE_NAMES) { const id = resolved[name]; if (typeof id !== 'number') { throw new Error(`Table Baserow manquante : ${name}`); } out[name] = id; } return out as TableIds; }