AppView in a box as a Vite plugin thing hatk.dev
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: extract buildSchemas to deduplicate main.ts and cli.ts

hatk schema now inits the DB from lexicons if it doesn't exist yet,
so you can inspect the schema without running the server first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+58 -31
+15 -5
packages/hatk/src/cli.ts
··· 1 1 #!/usr/bin/env node 2 2 import { mkdirSync, writeFileSync, existsSync, unlinkSync, readdirSync, readFileSync } from 'node:fs' 3 - import { resolve, join } from 'node:path' 3 + import { resolve, join, dirname } from 'node:path' 4 4 import { execSync, spawn } from 'node:child_process' 5 - import { loadLexicons } from './schema.ts' 5 + import { loadLexicons, discoverCollections, buildSchemas } from './schema.ts' 6 6 import { loadConfig } from './config.ts' 7 7 8 8 const args = process.argv.slice(2) ··· 1961 1961 console.error('No database file configured (database is :memory:)') 1962 1962 process.exit(1) 1963 1963 } 1964 + 1965 + // Init DB from lexicons if it doesn't exist yet 1964 1966 if (!existsSync(config.database)) { 1965 - console.error(`Database not found: ${config.database}`) 1966 - console.error('Run "hatk dev" first to create it.') 1967 - process.exit(1) 1967 + const configDir = resolve('.') 1968 + const lexicons = loadLexicons(resolve(configDir, 'lexicons')) 1969 + const collections = config.collections.length > 0 ? config.collections : discoverCollections(lexicons) 1970 + if (collections.length === 0) { 1971 + console.error('No record collections found. Add record lexicons to the lexicons/ directory.') 1972 + process.exit(1) 1973 + } 1974 + mkdirSync(dirname(config.database), { recursive: true }) 1975 + const { initDatabase } = await import('./db.ts') 1976 + const { schemas, ddlStatements } = buildSchemas(lexicons, collections) 1977 + await initDatabase(config.database, schemas, ddlStatements) 1968 1978 } 1969 1979 1970 1980 const { DuckDBInstance } = await import('@duckdb/node-api')
+7 -26
packages/hatk/src/main.ts
··· 7 7 loadLexicons, 8 8 storeLexicons, 9 9 discoverCollections, 10 - generateTableSchema, 11 - generateCreateTableSQL, 10 + buildSchemas, 12 11 } from './schema.ts' 13 12 import { discoverViews } from './views.ts' 14 13 import { initDatabase, getCursor, querySQL } from './db.ts' ··· 68 67 discoverViews() 69 68 await loadOnLoginHook(resolve(configDir, 'hooks')) 70 69 71 - const schemas = [] 72 - const ddlStatements = [] 73 - 74 - for (const nsid of collections) { 75 - const lexicon = lexicons.get(nsid) 76 - if (!lexicon) { 77 - log(`[main] No lexicon found for ${nsid}, using generic JSON storage`) 78 - const genericDDL = `CREATE TABLE IF NOT EXISTS "${nsid}" ( 79 - uri TEXT PRIMARY KEY, 80 - cid TEXT, 81 - did TEXT NOT NULL, 82 - indexed_at TIMESTAMP NOT NULL, 83 - data JSON 84 - ); 85 - CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_indexed ON "${nsid}"(indexed_at DESC); 86 - CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_author ON "${nsid}"(did);` 87 - schemas.push({ collection: nsid, tableName: `"${nsid}"`, columns: [], refColumns: [], children: [], unions: [] }) 88 - ddlStatements.push(genericDDL) 89 - continue 70 + const { schemas, ddlStatements } = buildSchemas(lexicons, collections) 71 + for (const s of schemas) { 72 + if (s.columns.length === 0) { 73 + log(`[main] No lexicon found for ${s.collection}, using generic JSON storage`) 74 + } else { 75 + log(`[main] Schema for ${s.collection}: ${s.columns.length} columns, ${s.unions.length} unions`) 90 76 } 91 - 92 - const schema = generateTableSchema(nsid, lexicon, lexicons) 93 - schemas.push(schema) 94 - ddlStatements.push(generateCreateTableSQL(schema)) 95 - log(`[main] Schema for ${nsid}: ${schema.columns.length} columns, ${schema.unions.length} unions`) 96 77 } 97 78 98 79 // 3. Ensure data directory exists and initialize DuckDB
+36
packages/hatk/src/schema.ts
··· 430 430 431 431 return [createTable, ...indexes, ...childDDL].join('\n') 432 432 } 433 + 434 + /** 435 + * Build table schemas and DDL from lexicons and collections. 436 + * Shared by main.ts (server boot) and cli.ts (hatk schema command). 437 + */ 438 + export function buildSchemas( 439 + lexicons: Map<string, any>, 440 + collections: string[], 441 + ): { schemas: TableSchema[]; ddlStatements: string[] } { 442 + const schemas: TableSchema[] = [] 443 + const ddlStatements: string[] = [] 444 + 445 + for (const nsid of collections) { 446 + const lexicon = lexicons.get(nsid) 447 + if (!lexicon) { 448 + const genericDDL = `CREATE TABLE IF NOT EXISTS "${nsid}" ( 449 + uri TEXT PRIMARY KEY, 450 + cid TEXT, 451 + did TEXT NOT NULL, 452 + indexed_at TIMESTAMP NOT NULL, 453 + data JSON 454 + ); 455 + CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_indexed ON "${nsid}"(indexed_at DESC); 456 + CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_author ON "${nsid}"(did);` 457 + schemas.push({ collection: nsid, tableName: `"${nsid}"`, columns: [], refColumns: [], children: [], unions: [] }) 458 + ddlStatements.push(genericDDL) 459 + continue 460 + } 461 + 462 + const schema = generateTableSchema(nsid, lexicon, lexicons) 463 + schemas.push(schema) 464 + ddlStatements.push(generateCreateTableSQL(schema)) 465 + } 466 + 467 + return { schemas, ddlStatements } 468 + }