import type { ZodType, ZodTypeDef } from "zod"; import type { SourceKind } from "@/lib/schemas/source"; import { sourceModules } from "./modules"; // Context handed to a source's fetch(). `config` is the per-source config object // (already validated by configSchema); `env` is process.env for credentials. export interface SourceFetchContext { config: Config; env: NodeJS.ProcessEnv; signal?: AbortSignal; } // A source module = how to fetch + validate + describe one kind of data. // The dashboard card and edition section render the payload separately. export interface SourceModule { kind: SourceKind; label: string; // true => works with zero credentials (weather, news, onthisday, obsidian). keyless: boolean; defaultRefreshSeconds: number; // input param is `any` because schemas use .default(), so input ≠ output type. configSchema: ZodType; payloadSchema: ZodType; // Throws on failure; caller records ok:false + the error message. fetch(ctx: SourceFetchContext): Promise; // Returns false when required creds/config are missing → card shows "needs config". isConfigured?(config: Config, env: NodeJS.ProcessEnv): boolean; } const REGISTRY = new Map( sourceModules.map((m) => [m.kind, m as SourceModule]), ); export function getSourceModule(kind: SourceKind): SourceModule | undefined { return REGISTRY.get(kind); } export function allSourceModules(): SourceModule[] { return [...REGISTRY.values()]; } export function isSourceConfigured( kind: SourceKind, config: unknown, env: NodeJS.ProcessEnv = process.env, ): boolean { const mod = REGISTRY.get(kind); if (!mod) return true; // local kinds (e.g. todos) need no config const parsed = mod.configSchema.safeParse(config ?? {}); const cfg = parsed.success ? parsed.data : ({} as unknown); return mod.isConfigured ? mod.isConfigured(cfg, env) : true; }