mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add dataset library, extract-to-dataset, and render-time reference resolution
This commit is contained in:
@@ -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(),
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user