import { browser } from "$app/environment"; import { asset } from "$app/paths"; import { getClientDb } from "$lib/db/client-db"; /** * Version-keyed seeding for the client-side SQLocal database. * * Your shipped snapshot lives at: * - `static/data/amq.sqlite` -> `/data/amq.sqlite` * * SQLocal stores its own managed DB file (named in `client-db.ts`), and we * overwrite its contents from the shipped snapshot when needed. * * This is intended for READ-ONLY browsing. Bump the version when you ship a new * snapshot so clients refresh. */ export const AMQ_DB_SEED_VERSION = 5; const SEED_ASSET_PATH = "/data/amq.sqlite"; const seededStorageKey = (version: number) => `amq.sqlocal.seeded.v${version}`; let seedPromise: Promise | null = null; function ensureBrowser() { if (!browser) { throw new Error("Client DB seeding can only run in the browser."); } } /** * Ensure the client DB has been seeded for the given version. * * - Uses `localStorage` to skip reseeding once done. * - Uses a shared promise to avoid races across multiple callers. * - Uses streaming (ReadableStream) when available for large DB files. * * @param opts.version Bump to force users to refresh from the shipped snapshot * @param opts.force If true, reseed even if already seeded for this version * @param opts.fetch Optional fetch implementation (prefer the one from load) */ export async function ensureSeeded( opts: { version?: number; force?: boolean; fetch?: typeof fetch } = {}, ): Promise { ensureBrowser(); const version = opts.version ?? AMQ_DB_SEED_VERSION; const key = seededStorageKey(version); const fetcher = opts.fetch ?? fetch; if (!opts.force && localStorage.getItem(key) === "1") return; // Serialize seeding work so multiple callers don't overwrite concurrently. if (seedPromise) return seedPromise; seedPromise = (async () => { // Re-check inside the serialized section in case another caller finished first. if (!opts.force && localStorage.getItem(key) === "1") return; const url = asset(SEED_ASSET_PATH); const res = await fetcher(url, { cache: "no-cache" }); if (!res.ok) { throw new Error( `Failed to fetch seed DB from ${url}: ${res.status} ${res.statusText}`, ); } // Prefer streaming when possible. const { overwriteDatabaseFile } = getClientDb(); if (res.body) { await overwriteDatabaseFile(res.body); } else { const blob = await res.blob(); await overwriteDatabaseFile(blob); } localStorage.setItem(key, "1"); })(); try { await seedPromise; } finally { // If seeding failed, allow future attempts (and let the error surface). if (localStorage.getItem(key) !== "1") { seedPromise = null; } } } /** * Utility for debugging / support: clears the seeded marker for a given version. * This does NOT delete SQLocal's stored DB file; it only forces reseed on next call. */ export function clearSeedMarker(version = AMQ_DB_SEED_VERSION) { ensureBrowser(); localStorage.removeItem(seededStorageKey(version)); }