import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { RegistrySchema, type ConnectorMeta, type Registry, type RareIndexConnector } from './types.js'; const here = path.dirname(fileURLToPath(import.meta.url)); export const CONNECTORS_DIR = path.resolve(here, '../../../connectors'); export const REGISTRY_PATH = path.join(CONNECTORS_DIR, 'registry.json'); let cached: Registry | null = null; /** Load and validate connectors/registry.json (§106). */ export function loadRegistry(force = false): Registry { if (cached && !force) return cached; const raw = JSON.parse(readFileSync(REGISTRY_PATH, 'utf8')); cached = RegistrySchema.parse(raw); const ids = new Set(); for (const c of cached.connectors) { if (ids.has(c.id)) throw new Error(`registry: duplicate connector id ${c.id}`); ids.add(c.id); } return cached; } export function getConnectorMeta(id: string): ConnectorMeta { const meta = loadRegistry().connectors.find((c) => c.id === id); if (!meta) throw new Error(`Unknown connector: ${id}`); return meta; } export function listConnectorMeta(filter: { enabled?: boolean; category?: string; sourceType?: string } = {}): ConnectorMeta[] { return loadRegistry().connectors.filter((c) => { if (filter.enabled !== undefined && c.enabled !== filter.enabled) return false; if (filter.category && !c.categories.includes(filter.category)) return false; if (filter.sourceType && c.sourceType !== filter.sourceType) return false; return true; }); } /** * Dynamically import a connector module. Each module default-exports a factory * `(meta: ConnectorMeta) => RareIndexConnector`. */ export async function loadConnector(id: string): Promise { const meta = getConnectorMeta(id); const modPath = path.join(CONNECTORS_DIR, meta.module, 'index.ts'); const mod = (await import(modPath)) as { default?: (meta: ConnectorMeta) => RareIndexConnector | Promise; createConnector?: (meta: ConnectorMeta) => RareIndexConnector | Promise }; const factory = mod.default ?? mod.createConnector; if (!factory) throw new Error(`connector module ${meta.module} must default-export a factory`); return factory(meta); } /** Find connectors whose urlPatterns match a URL (scanner paste-URL, §114). */ export async function connectorsForUrl(url: string): Promise { const out: RareIndexConnector[] = []; for (const meta of listConnectorMeta({ enabled: true })) { if (!meta.supportsLookup) continue; const c = await loadConnector(meta.id); if (c.urlPatterns?.some((re) => re.test(url))) out.push(c); } return out; }