Add dataset library, extract-to-dataset, and render-time reference resolution

This commit is contained in:
2026-06-05 15:49:40 +03:00
parent 25849461e0
commit a4e4d96d3b
41 changed files with 3909 additions and 19 deletions
@@ -0,0 +1,51 @@
/**
* Read-time migration for Dataset records (docs/architecture/02 §4, spec §09B).
*
* Mirrors snippet-migrations: every dataset read from storage passes through
* `migrateDataset`, which fills missing/old fields and stamps the current
* version. It tolerates unknown fields (spread the original, only fill gaps) so a
* record written by a newer build round-trips without data loss. Records written
* before versioning existed (no `version`) are treated as v1.
*/
import { CURRENT_DATASET_VERSION, type DataSource, type Dataset } from '@core/dataset';
import type { DataFormat } from '@core/format-detection';
import type { ColumnType } from '@core/type-inference';
const FORMATS: ReadonlyArray<DataFormat> = ['json', 'csv', 'tsv', 'topojson'];
function asFormat(v: unknown): DataFormat {
return typeof v === 'string' && (FORMATS as string[]).includes(v) ? (v as DataFormat) : 'json';
}
function asSource(v: unknown): DataSource {
return v === 'url' ? 'url' : 'inline';
}
function asCount(v: unknown): number | null {
return typeof v === 'number' && Number.isFinite(v) ? v : null;
}
/** Upgrade a raw stored record to the current Dataset shape. */
export function migrateDataset(raw: unknown): Dataset {
const r = { ...(raw as Record<string, unknown>) };
return {
...r,
id: typeof r.id === 'number' ? r.id : Number(r.id),
version: CURRENT_DATASET_VERSION,
name: typeof r.name === 'string' ? r.name : 'Untitled',
data: r.data,
format: asFormat(r.format),
source: asSource(r.source),
comment: typeof r.comment === 'string' ? r.comment : '',
rowCount: asCount(r.rowCount),
columnCount: asCount(r.columnCount),
columns: Array.isArray(r.columns) ? (r.columns as string[]) : [],
columnTypes: Array.isArray(r.columnTypes)
? (r.columnTypes as Array<{ name: string; type: ColumnType }>)
: [],
size: typeof r.size === 'number' ? r.size : 0,
created: typeof r.created === 'string' ? r.created : new Date(0).toISOString(),
modified: typeof r.modified === 'string' ? r.modified : new Date(0).toISOString(),
};
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Dataset persistence adapter (docs/architecture/02, spec §09E).
*
* The typed seam between the dataset store and IndexedDB's high-capacity
* `datasets` object store (separate from snippets so large payloads live in a
* tier suited to them). Exposes plain async functions returning domain `Dataset`
* objects and migrates every record on read.
*/
import { CURRENT_DATASET_VERSION, type Dataset } from '@core/dataset';
import { DATASETS_STORE, del, getAll, put } from './db';
import { migrateDataset } from './dataset-migrations';
/** Load every dataset, upgrading each record to the current shape. */
export async function loadDatasets(): Promise<Dataset[]> {
const records = await getAll<unknown>(DATASETS_STORE);
return records.map(migrateDataset);
}
/** Persist a dataset at the current schema version. Propagates failures. */
export async function saveDataset(dataset: Dataset): Promise<void> {
await put(DATASETS_STORE, { ...dataset, version: CURRENT_DATASET_VERSION });
}
/** Permanently remove a dataset by id. */
export async function deleteDataset(id: number): Promise<void> {
await del(DATASETS_STORE, id);
}