diff --git a/src/model/repository.ts b/src/model/repository.ts new file mode 100644 index 0000000..b236fd5 --- /dev/null +++ b/src/model/repository.ts @@ -0,0 +1,48 @@ +/** + * Model repository (C9, F7) — where the working document is persisted locally. + * + * IndexedDB (via `idb`) sits behind a tiny `ModelRepository` interface so the + * storage engine is no longer a hard-to-reverse choice (T4): a future sync + * backend (PouchDB, an API) is a new implementation behind the same two methods, + * with no caller change. Phase 1 is single-document, so the current Model is + * stored under one fixed key; a model list (C12) would key by `model.id` instead. + */ +import { type IDBPDatabase, openDB } from "idb" +import type { Model } from "./types" + +export interface ModelRepository { + /** Persist the current Model, overwriting the previous autosave. */ + save(model: Model): Promise + /** Load the autosaved Model, or null if nothing has been saved yet. */ + load(): Promise +} + +const DB_NAME = "meadows" +const DB_VERSION = 1 +const STORE = "models" +const CURRENT_KEY = "current" + +let dbPromise: Promise | null = null + +function db(): Promise { + // Memoise the open so we pay the handshake once, not per save. + dbPromise ??= openDB(DB_NAME, DB_VERSION, { + upgrade(database) { + if (!database.objectStoreNames.contains(STORE)) database.createObjectStore(STORE) + }, + }) + return dbPromise +} + +/** The default IndexedDB-backed repository used by the app. */ +export function createRepository(): ModelRepository { + return { + async save(model: Model): Promise { + await (await db()).put(STORE, model, CURRENT_KEY) + }, + async load(): Promise { + const model = await (await db()).get(STORE, CURRENT_KEY) + return (model as Model | undefined) ?? null + }, + } +}