import { Database } from 'bun:sqlite'; import { createHash } from 'crypto'; import path from 'path'; import fs from 'fs/promises'; type CacheRow = { path_key: string, fpath: string, etag: string, size: number, mime: string | null, cache_path: string, updated_at: number }; export const CACHE_DIR = "/tmp", db = new Database(path.resolve(`${CACHE_DIR}/index.sqlite`)); db.run(` create table if not exists file_cache ( path_key text primary key, fpath text not null, etag text not null, size integer not null, mime text, cache_path text not null, updated_at integer not null ); `); const qGet = db.query('select * from file_cache where path_key = ?;'); const qUpsert = db.query(` insert into file_cache (path_key, fpath, etag, size, mime, cache_path, updated_at) values (?1, ?2, ?3, ?4, ?5, ?6, ?7) on conflict(path_key) do update set etag=excluded.etag, size=excluded.size, mime=excluded.mime, cache_path=excluded.cache_path, updated_at=excluded.updated_at; `); export const base64url = (buf: Buffer): string => buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); // no padding export const pathKey = (addr: string, fpath: string): string => { // normalize to avoid duplicate keys for equivalent paths const canonical = new URL(fpath, addr).toString(), digest = createHash('sha256').update(canonical).digest(); return base64url(digest); }; export const fanoutPath = (key: string): string => path.join(`${CACHE_DIR}/files`, key.slice(0, 2), key.slice(2, 4), key); export async function ensureDir(p: string): Promise { await fs.mkdir(path.dirname(p), { recursive: true }); } export function getRow(key: string): CacheRow | undefined { return qGet.get(key) as unknown as CacheRow | undefined; } export function upsertRow(row: CacheRow): void { qUpsert.run(row.path_key, row.fpath, row.etag, row.size, row.mime, row.cache_path, row.updated_at); }