99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
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<void> | 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<void> {
|
|
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));
|
|
}
|