// Derives META_REQUEST/JSON_SCHEMA_REQUEST field metadata from seed data itself, // so every table/view/query gets working metadata without hand-authoring it — // datasource init won't complete without a non-empty FIELD array (see README). import type { FieldMetadata, Row } from '../types.ts'; // Numeric fields scan EVERY row, not just the first non-null value: a column // like PRICE is DOUBLE if any row holds a fractional value, even when row one // happens to land on a whole number. A column whose values are all whole // numbers still infers as LONG — if it's semantically a DOUBLE, say so via // the fieldTypes override (see README). function inferFieldType(name: string, rows: Row[], sample: unknown): string { if (typeof sample === 'number') { const anyFractional = rows.some( (row) => typeof row[name] === 'number' && !Number.isInteger(row[name]), ); return anyFractional ? 'DOUBLE' : 'LONG'; } if (typeof sample === 'boolean') return 'BOOLEAN'; return 'STRING'; } /** rows: array of plain row objects. Returns a FIELD array covering every key seen. */ export function deriveFields(rows: Row[], overrides: Record = {}): FieldMetadata[] { const fieldNames: string[] = []; const seen = new Set(); for (const row of rows) { for (const key of Object.keys(row)) { if (!seen.has(key)) { seen.add(key); fieldNames.push(key); } } } return fieldNames.map((name) => { const sampleRow = rows.find((row) => row[name] !== undefined && row[name] !== null); const type = overrides[name] ?? (sampleRow ? inferFieldType(name, rows, sampleRow[name]) : 'STRING'); return { NAME: name, TYPE: type }; }); }