Files

66 lines
1.9 KiB
TypeScript
Raw Permalink Normal View History

2025-10-03 08:41:37 -04:00
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<CacheRow, string>('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<void> {
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);
}