( headers: string[], sampleRows: string[][], genId: GenId = defaultGenId )
| 190 | * otherwise a leading `id` field is synthesized. |
| 191 | */ |
| 192 | export function inferFields( |
| 193 | headers: string[], |
| 194 | sampleRows: string[][], |
| 195 | genId: GenId = defaultGenId |
| 196 | ): InferredSchema { |
| 197 | const usedNames = new Set<string>() |
| 198 | const normalizedHeaders = headers.map((h, i) => dedupeHeader(h, usedNames, i)) |
| 199 | |
| 200 | // Detect a usable id column. |
| 201 | let idColIndex = -1 |
| 202 | const idHeaderIdx = normalizedHeaders.findIndex((h) => h.toLowerCase() === DEFAULT_ID_FIELD_NAME) |
| 203 | if (idHeaderIdx >= 0) { |
| 204 | const values = sampleRows.map((r) => r[idHeaderIdx] ?? '') |
| 205 | const allPresent = values.length > 0 && values.every((v) => v.trim().length > 0) |
| 206 | const unique = new Set(values).size === values.length |
| 207 | if (sampleRows.length === 0 || (allPresent && unique)) idColIndex = idHeaderIdx |
| 208 | } |
| 209 | |
| 210 | const fields: DbField[] = normalizedHeaders.map((name, i) => ({ |
| 211 | id: genId(), |
| 212 | name, |
| 213 | type: |
| 214 | i === idColIndex |
| 215 | ? 'text' |
| 216 | : inferColumnType(sampleRows.map((r) => r[i] ?? '')) |
| 217 | })) |
| 218 | |
| 219 | let idFieldId: string |
| 220 | if (idColIndex >= 0) { |
| 221 | idFieldId = fields[idColIndex].id |
| 222 | fields[idColIndex].hidden = true |
| 223 | } else { |
| 224 | // Synthesize a leading id field. |
| 225 | const idField: DbField = { id: genId(), name: DEFAULT_ID_FIELD_NAME, type: 'text', hidden: true } |
| 226 | fields.unshift(idField) |
| 227 | idFieldId = idField.id |
| 228 | } |
| 229 | return { idFieldId, fields } |
| 230 | } |
| 231 | |
| 232 | /** A single default Table view (id hidden) covering all fields in order. */ |
| 233 | export function buildDefaultViews( |
no test coverage detected