const db = getDb("cache.sqlite"); db.table( "blob_assets", /* SQL */ ` CREATE TABLE IF NOT EXISTS blob_assets ( hash TEXT PRIMARY KEY, refs INTEGER NOT NULL DEFAULT 0 ); `, ); /** * Uncompressed files are read directly from the media store root. Compressed * files are stored as `//` Since * multiple files can share the same hash, the number of references is tracked * so that when a file is deleted, the compressed data is only removed when all * references are gone. */ export class BlobAsset { /** sha1 of the contents */ hash!: string; refs!: number; decrementOrDelete() { BlobAsset.decrementOrDelete(this.hash); } static get(hash: string) { return getQuery.get(hash); } static putOrIncrement(hash: string) { ASSERT(hash.length === 40); putOrIncrementQuery.get(hash); return BlobAsset.get(hash)!; } static decrementOrDelete(hash: string) { ASSERT(hash.length === 40); decrementQuery.run(hash); return deleteQuery.run(hash).changes > 0; } } const getQuery = db.prepare<[hash: string]>(/* SQL */ ` SELECT * FROM blob_assets WHERE hash = ?; `).as(BlobAsset); const putOrIncrementQuery = db.prepare<[hash: string]>(/* SQL */ ` INSERT INTO blob_assets (hash, refs) VALUES (?, 1) ON CONFLICT(hash) DO UPDATE SET refs = refs + 1; `); const decrementQuery = db.prepare<[hash: string]>(/* SQL */ ` UPDATE blob_assets SET refs = refs - 1 WHERE hash = ? AND refs > 0; `); const deleteQuery = db.prepare<[hash: string]>(/* SQL */ ` DELETE FROM blob_assets WHERE hash = ? AND refs <= 0; `); import { getDb } from "#sitegen/sqlite";