// The blob store behind blob.ts and blob-global.ts: one SQLite file, // blobs.db, with the same methods and errors as Val Town's std/blob. import { DatabaseSync } from "node:sqlite"; export class ValTownBlobError extends Error {} export class ValTownBlobNotFoundError extends ValTownBlobError { constructor() { super("Blob not found"); } } export function openBlobs(dir: string) { Deno.mkdirSync(dir, { recursive: true }); const db = new DatabaseSync(`${dir}/blobs.db`); db.exec("PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000"); db.exec(`CREATE TABLE IF NOT EXISTS blobs (key TEXT PRIMARY KEY, value BLOB NOT NULL, last_modified TEXT NOT NULL)`); async function get(key: string): Promise { const row = db.prepare("SELECT value FROM blobs WHERE key = ?").get(key) as | { value: Uint8Array } | undefined; if (!row) throw new ValTownBlobNotFoundError(); return new Response(row.value); } async function set(key: string, value: BodyInit): Promise { if (key.length < 1 || key.length > 512) throw new ValTownBlobError("Invalid key"); const bytes = new Uint8Array(await new Response(value).arrayBuffer()); db.prepare(`INSERT INTO blobs (key, value, last_modified) VALUES (?, ?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value, last_modified = excluded.last_modified`).run(key, bytes, new Date().toISOString()); } async function list(prefix = "") { return (db.prepare(`SELECT key, length(value) AS size, last_modified AS lastModified FROM blobs WHERE substr(key, 1, length(?)) = ? ORDER BY key`).all(prefix, prefix)) as { key: string; size: number; lastModified: string; }[]; } async function delete_(key: string) { db.prepare("DELETE FROM blobs WHERE key = ?").run(key); } async function copy(previous: string, next: string) { await set(next, await (await get(previous)).arrayBuffer()); } async function move(previous: string, next: string) { await copy(previous, next); await delete_(previous); } async function getJSON(key: string) { try { return await (await get(key)).json(); } catch (e) { if (e instanceof ValTownBlobNotFoundError) return undefined; throw e; } } async function setJSON(key: string, value: unknown) { return await set(key, JSON.stringify(value)); } return { get, set, copy, move, list, delete: delete_, getJSON, setJSON }; }