1import { fileURLToPath } from 'node:url';2import path from 'node:path';3import { readFileSync, existsSync } from 'node:fs';4import { sql } from 'drizzle-orm';5import { CATEGORIES, GRADERS, INDICES, INDEX_BASE_DATE, INDEX_BASE_VALUE } from '@rareindex/taxonomy';6import { getDb, closeDb } from './client.js';7import { categories, graders, indices, sources, connectors } from './schema/index.js';89/** Upsert taxonomy nodes + graders from data/taxonomy (idempotent). */10export async function seedTaxonomy(): Promise<void> {11 const db = getDb();12 for (const c of CATEGORIES) {13 await db14 .insert(categories)15 .values({16 slug: c.slug,17 parentSlug: c.parent ?? null,18 familySlug: c.familySlug,19 name: c.name,20 shortName: c.short ?? null,21 description: c.description ?? null,22 level: c.level,23 phase: c.phase,24 conditionScale: c.conditionScale ?? 'general',25 graders: c.graders ?? [],26 indexTicker: c.index ?? null,27 sortOrder: c.sortOrder,28 complianceFlags: c.compliance ?? [],29 icon: c.icon ?? null,30 })31 .onConflictDoUpdate({32 target: categories.slug,33 set: {34 parentSlug: c.parent ?? null,35 familySlug: c.familySlug,36 name: c.name,37 shortName: c.short ?? null,38 description: c.description ?? null,39 level: c.level,40 phase: c.phase,41 conditionScale: c.conditionScale ?? 'general',42 graders: c.graders ?? [],43 indexTicker: c.index ?? null,44 sortOrder: c.sortOrder,45 complianceFlags: c.compliance ?? [],46 icon: c.icon ?? null,47 updatedAt: new Date(),48 },49 });50 }51 for (const g of GRADERS) {52 await db53 .insert(graders)54 .values({ slug: g.slug, name: g.name, categorySlugs: g.categories, scale: g.scale, populationUrl: g.populationUrl ?? null, verifyUrl: g.verifyUrl ?? null })55 .onConflictDoUpdate({ target: graders.slug, set: { name: g.name, categorySlugs: g.categories, scale: g.scale, populationUrl: g.populationUrl ?? null, verifyUrl: g.verifyUrl ?? null } });56 }57}5859/** Upsert index definitions (§3). */60export async function seedIndices(): Promise<void> {61 const db = getDb();62 for (const i of INDICES) {63 const familySlugs = i.families;64 const categorySlugs = CATEGORIES.filter((c) => familySlugs.includes(c.familySlug)).map((c) => c.slug);65 await db66 .insert(indices)67 .values({68 id: `idx_${i.ticker.toLowerCase().replace(/[^a-z0-9]+/g, '_')}`,69 ticker: i.ticker,70 name: i.name,71 description: i.description ?? null,72 categorySlugs,73 familySlugs,74 parentTicker: i.flagship ? null : 'RARE',75 baseDate: INDEX_BASE_DATE,76 baseValue: INDEX_BASE_VALUE,77 minConstituents: i.minConstituents,78 isFlagship: Boolean(i.flagship),79 color: i.color,80 })81 .onConflictDoUpdate({ target: indices.ticker, set: { name: i.name, description: i.description ?? null, categorySlugs, familySlugs, minConstituents: i.minConstituents, color: i.color } });82 }83}8485/** Mirror connectors/registry.json into sources + connectors tables (state columns are preserved). */86export async function seedRegistry(): Promise<void> {87 const here = path.dirname(fileURLToPath(import.meta.url));88 const registryPath = path.resolve(here, '../../../connectors/registry.json');89 if (!existsSync(registryPath)) return;90 const reg = JSON.parse(readFileSync(registryPath, 'utf8')) as { connectors: Array<Record<string, any>> };91 const db = getDb();92 for (const c of reg.connectors) {93 await db94 .insert(sources)95 .values({ id: c.sourceId, name: c.sourceName, sourceType: c.sourceType, url: c.sourceUrl, countries: c.regions ?? [], languages: c.languages ?? ['en'], currencies: c.currency ?? ['USD'], trustScore: c.trustScore ?? 0.6, attributionRequired: c.attributionRequired ?? true, termsUrl: c.termsUrl ?? null })96 .onConflictDoUpdate({ target: sources.id, set: { name: c.sourceName, sourceType: c.sourceType, url: c.sourceUrl, trustScore: c.trustScore ?? 0.6, termsUrl: c.termsUrl ?? null, updatedAt: new Date() } });97 await db98 .insert(connectors)99 .values({100 id: c.id,101 sourceId: c.sourceId,102 displayName: c.displayName,103 enginePriority: c.enginePriority,104 categories: c.categories,105 regions: c.regions ?? [],106 languages: c.languages ?? ['en'],107 currency: c.currency ?? ['USD'],108 supportsListings: c.supportsListings ?? false,109 supportsSold: c.supportsSold ?? false,110 supportsAuctions: c.supportsAuctions ?? false,111 supportsImages: c.supportsImages ?? true,112 supportsCatalog: c.supportsCatalog ?? false,113 supportsPopulation: c.supportsPopulation ?? false,114 refreshFrequencyMinutes: c.refreshFrequencyMinutes ?? 1440,115 priority: c.priority ?? 'medium',116 status: c.enabled === false ? 'disabled' : 'active',117 schemaVersion: c.schemaVersion ?? '1.0',118 config: c.config ?? {},119 })120 .onConflictDoUpdate({121 target: connectors.id,122 set: {123 sourceId: c.sourceId,124 displayName: c.displayName,125 enginePriority: c.enginePriority,126 categories: c.categories,127 regions: c.regions ?? [],128 languages: c.languages ?? ['en'],129 currency: c.currency ?? ['USD'],130 supportsListings: c.supportsListings ?? false,131 supportsSold: c.supportsSold ?? false,132 supportsAuctions: c.supportsAuctions ?? false,133 supportsImages: c.supportsImages ?? true,134 supportsCatalog: c.supportsCatalog ?? false,135 supportsPopulation: c.supportsPopulation ?? false,136 refreshFrequencyMinutes: c.refreshFrequencyMinutes ?? 1440,137 priority: c.priority ?? 'medium',138 schemaVersion: c.schemaVersion ?? '1.0',139 config: c.config ?? {},140 updatedAt: new Date(),141 // status is intentionally NOT overwritten (admin pause survives reseeds) except when disabled in registry142 ...(c.enabled === false ? { status: 'disabled' } : {}),143 },144 });145 }146}147148export async function seedAll(): Promise<void> {149 await seedTaxonomy();150 await seedIndices();151 await seedRegistry();152 const db = getDb();153 const [{ n }] = (await db.execute(sql`select count(*)::int as n from categories`)) as unknown as [{ n: number }];154 console.log(`[seed] categories=${n} indices=${INDICES.length}`);155}156157const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);158if (isMain) {159 seedAll()160 .then(() => closeDb())161 .catch(async (e) => {162 console.error(e);163 await closeDb();164 process.exit(1);165 });166}167