codegen.ts89 lines · main
| 1 | export interface ColumnSnapshot { |
| 2 | sqlType: string; |
| 3 | nullable: boolean; |
| 4 | primaryKey: boolean; |
| 5 | unique: boolean; |
| 6 | default?: string; |
| 7 | references?: { table: string; column: string; onDelete?: 'cascade' | 'set null' | 'restrict' }; |
| 8 | } |
| 9 | |
| 10 | export interface TableSnapshot { |
| 11 | columns: Record<string, ColumnSnapshot>; |
| 12 | indexes: Array<{ columns: string[]; unique: boolean }>; |
| 13 | access?: { read?: string; write?: string }; |
| 14 | } |
| 15 | |
| 16 | export interface SchemaSnapshot { |
| 17 | version: 1; |
| 18 | tables: Record<string, TableSnapshot>; |
| 19 | } |
| 20 | |
| 21 | const README = `# briven/_generated |
| 22 | |
| 23 | Generated by briven cli. Do not edit by hand. Regenerated on every \`briven dev\` |
| 24 | push and at the end of \`briven\` wizard runs. |
| 25 | `; |
| 26 | |
| 27 | function tsTypeFor(sqlType: string): string { |
| 28 | if (sqlType === 'text' || sqlType === 'uuid' || sqlType === 'timestamptz') return 'string'; |
| 29 | if (sqlType.startsWith('varchar(')) return 'string'; |
| 30 | if (sqlType === 'integer') return 'number'; |
| 31 | if (sqlType === 'bigint') return 'bigint'; |
| 32 | if (sqlType === 'boolean') return 'boolean'; |
| 33 | if (sqlType === 'jsonb') return 'unknown'; |
| 34 | if (sqlType.startsWith('vector(')) return 'number[]'; |
| 35 | return 'unknown'; |
| 36 | } |
| 37 | |
| 38 | function pascal(s: string): string { |
| 39 | return s.charAt(0).toUpperCase() + s.slice(1); |
| 40 | } |
| 41 | |
| 42 | function renderTable(name: string, table: TableSnapshot): string { |
| 43 | const lines: string[] = []; |
| 44 | lines.push(`export interface ${pascal(name)} {`); |
| 45 | for (const [col, def] of Object.entries(table.columns)) { |
| 46 | const ts = tsTypeFor(def.sqlType); |
| 47 | const tail = def.nullable ? ' | null' : ''; |
| 48 | lines.push(` ${col}: ${ts}${tail};`); |
| 49 | } |
| 50 | lines.push('}'); |
| 51 | return lines.join('\n'); |
| 52 | } |
| 53 | |
| 54 | function renderDataModel(snapshot: SchemaSnapshot): string { |
| 55 | const interfaces = Object.entries(snapshot.tables) |
| 56 | .map(([name, table]) => renderTable(name, table)) |
| 57 | .join('\n\n'); |
| 58 | const tableMapEntries = Object.keys(snapshot.tables) |
| 59 | .map((name) => ` ${name}: ${pascal(name)};`) |
| 60 | .join('\n'); |
| 61 | const tablesType = |
| 62 | Object.keys(snapshot.tables).length === 0 |
| 63 | ? 'export type Tables = Record<string, never>;' |
| 64 | : `export interface Tables {\n${tableMapEntries}\n}`; |
| 65 | return `// Generated by briven cli. Do not edit by hand.\n\n${interfaces}${interfaces ? '\n\n' : ''}${tablesType}\n\nexport type Doc<T extends keyof Tables> = Tables[T];\nexport type Id<_T extends keyof Tables> = string;\n`; |
| 66 | } |
| 67 | |
| 68 | function renderApi(functionFilenames: string[]): string { |
| 69 | const names = functionFilenames |
| 70 | .map((f) => f.replace(/\.ts$/, '')) |
| 71 | .filter((n) => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(n)); |
| 72 | if (names.length === 0) { |
| 73 | return `// Generated by briven cli. Do not edit by hand.\n\nexport const api = {} as const;\n`; |
| 74 | } |
| 75 | const imports = names.map((n) => `import ${n} from '../functions/${n}.js';`).join('\n'); |
| 76 | const entries = names.map((n) => ` ${n},`).join('\n'); |
| 77 | return `// Generated by briven cli. Do not edit by hand.\n\n${imports}\n\nexport const api = {\n${entries}\n} as const;\n`; |
| 78 | } |
| 79 | |
| 80 | export function generate( |
| 81 | snapshot: SchemaSnapshot, |
| 82 | functionFilenames: string[], |
| 83 | ): Map<string, string> { |
| 84 | const out = new Map<string, string>(); |
| 85 | out.set('briven/_generated/dataModel.d.ts', renderDataModel(snapshot)); |
| 86 | out.set('briven/_generated/api.ts', renderApi(functionFilenames)); |
| 87 | out.set('briven/_generated/README.md', README); |
| 88 | return out; |
| 89 | } |