TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import { z } from "zod";2import { SENSOR_TYPES, TIERS } from "./taxonomy";34/**5 * Source-registry seed schema (shared by the engine's seed loader, the validator CLI and the6 * API's bulk-import endpoint). Dependency-free apart from zod; no filesystem access here.7 */8export const sensorSchema = z.object({9 id: z.string().optional(),10 name: z.string(),11 url: z.string().url(),12 type: z.enum(SENSOR_TYPES),13 connector: z.string().default("http"),14 tier: z.enum(TIERS).optional(),15 interval: z.number().int().positive().optional(),16 weight: z.number().positive().optional(),17 config: z.record(z.string(), z.unknown()).default({}),18});19export type SensorSeed = z.infer<typeof sensorSchema>;2021export const productSchema = z.object({ id: z.string().optional(), name: z.string(), type: z.string().default("product"), aliases: z.array(z.string()).default([]) });2223export const sourceSchema = z.object({24 id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "id must be kebab-case"),25 name: z.string(),26 domain: z.string(),27 homepage: z.string().url().optional(),28 description: z.string().optional(),29 categories: z.array(z.string()).default([]),30 tier: z.enum(TIERS).default("B"),31 weight: z.number().positive().default(1),32 entity_type: z.string().default("organization"),33 aliases: z.array(z.string()).default([]),34 products: z.array(productSchema).default([]),35 discover: z.object({ rss: z.boolean().optional(), sitemap: z.boolean().optional(), status: z.boolean().optional(), pages: z.boolean().optional() }).default({}),36 fallback: z.object({ firecrawl: z.boolean().optional(), scrapfly: z.boolean().optional() }).default({}),37 sensors: z.array(sensorSchema).default([]),38 notes: z.string().optional(),39 enabled: z.boolean().default(true),40 /** false = heuristics only (high-volume feeds such as news wires) */41 llm: z.boolean().default(true),42 /** ISO 3166-1 alpha-2 (upper-case) when the organization is national/regional; "EU" for EU bodies; "INT" for global bodies */43 country: z.string().regex(/^[A-Z]{2,3}$/).optional(),44 /** primary language of the published content (ISO 639-1); defaults to "en" downstream */45 language: z.string().regex(/^[a-z]{2}$/).optional(),46 /** false for media/aggregators reporting about others (news wires, tech press); true for the organization's own channels */47 first_party: z.boolean().optional(),48 /** fragment-only: merge into an already declared source instead of redefining it */49 extend: z.boolean().default(false),50});51export type SourceSeed = z.infer<typeof sourceSchema>;5253/** Partial schema for `extend: true` entries — only `id` is required. */54export const extendSchema = z.object({55 id: z.string(),56 extend: z.literal(true),57 aliases: z.array(z.string()).default([]),58 products: z.array(productSchema).default([]),59 sensors: z.array(sensorSchema).default([]),60 categories: z.array(z.string()).default([]),61 fallback: z.object({ firecrawl: z.boolean().optional(), scrapfly: z.boolean().optional() }).optional(),62 notes: z.string().optional(),63 country: z.string().regex(/^[A-Z]{2,3}$/).optional(),64 language: z.string().regex(/^[a-z]{2}$/).optional(),65 first_party: z.boolean().optional(),66});6768/** Bulk import document (JSON / YAML already parsed): `{ sources: [...] }`. */69export const importDocumentSchema = z.object({ sources: z.array(z.unknown()).min(1).max(2000) });70