Defensive parse of a sidecar JSON; returns null when missing or unusable.
(raw: unknown)
| 85 | |
| 86 | /** Defensive parse of a sidecar JSON; returns null when missing or unusable. */ |
| 87 | function normalizeSidecar(raw: unknown): DatabaseSidecar | null { |
| 88 | if (!raw || typeof raw !== 'object') return null |
| 89 | const obj = raw as Record<string, unknown> |
| 90 | const fields = Array.isArray(obj.fields) ? (obj.fields as DbField[]) : null |
| 91 | if (!fields || fields.length === 0) return null |
| 92 | if (!fields.every((f) => f && typeof f.id === 'string' && typeof f.name === 'string')) return null |
| 93 | const fieldIds = new Set(fields.map((f) => f.id)) |
| 94 | const idFieldId = typeof obj.idFieldId === 'string' && fieldIds.has(obj.idFieldId) |
| 95 | ? obj.idFieldId |
| 96 | : fields[0].id |
| 97 | let views = Array.isArray(obj.views) ? (obj.views as DbView[]) : [] |
| 98 | views = views.filter((v) => v && typeof v.id === 'string' && (v.type === 'table' || v.type === 'board')) |
| 99 | if (views.length === 0) views = buildDefaultViews(fields).views |
| 100 | const activeViewId = |
| 101 | typeof obj.activeViewId === 'string' && views.some((v) => v.id === obj.activeViewId) |
| 102 | ? obj.activeViewId |
| 103 | : views[0].id |
| 104 | const pages = |
| 105 | obj.pages && typeof obj.pages === 'object' |
| 106 | ? (Object.fromEntries( |
| 107 | Object.entries(obj.pages as Record<string, unknown>).filter( |
| 108 | ([, v]) => typeof v === 'string' |
| 109 | ) |
| 110 | ) as Record<string, string>) |
| 111 | : undefined |
| 112 | return { version: 1, idFieldId, fields, views, activeViewId, ...(pages ? { pages } : {}) } |
| 113 | } |
| 114 | |
| 115 | async function readSidecar(root: string, rel: string): Promise<DatabaseSidecar | null> { |
| 116 | try { |
no test coverage detected