Luigit
repositories / will

will

owned by admin

src/shared/cas.ts

Raw
import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
import { open as fsOpen, mkdir, readdir, rename, rm, stat } from 'node:fs/promises'
import { join } from 'node:path'
import type { Readable } from 'node:stream'

/**
 * Content-addressed immutable media store (spec: bytes outside SQLite,
 * retained forever, deduplicated by digest).
 */

export function casPath(root: string, digest: string): string {
  return join(root, digest.slice(0, 2), digest.slice(2, 4), digest)
}

export interface StoredMedia {
  digest: string
  size: number
}

/** Store bytes under their sha256; idempotent for duplicate content. */
export async function casStore(root: string, bytes: Buffer): Promise<StoredMedia> {
  const digest = createHash('sha256').update(bytes).digest('hex')
  const path = casPath(root, digest)
  try {
    const existing = await stat(path)
    return { digest, size: existing.size }
  } catch {
    // not stored yet
  }
  const dir = join(root, digest.slice(0, 2), digest.slice(2, 4))
  await mkdir(dir, { recursive: true })
  const tmp = join(dir, `.tmp-${process.pid}-${Date.now()}`)
  const fh = await fsOpen(tmp, 'w')
  try {
    await fh.writeFile(bytes)
    await fh.sync()
  } finally {
    await fh.close()
  }
  await rename(tmp, path)
  return { digest, size: bytes.length }
}

export function casReadStream(root: string, digest: string): Readable {
  return createReadStream(casPath(root, digest))
}

/** Remove leftover .tmp-* files orphaned by crashes (maintenance reconcile). */
export async function casReconcile(root: string): Promise<number> {
  let removed = 0
  const walk = async (dir: string) => {
    let entries: import('node:fs').Dirent[]
    try {
      entries = await readdir(dir, { withFileTypes: true })
    } catch {
      return
    }
    for (const e of entries) {
      const p = join(dir, e.name)
      if (e.isDirectory()) await walk(p)
      else if (e.name.startsWith('.tmp-')) {
        await rm(p, { force: true })
        removed++
      }
    }
  }
  await walk(root)
  return removed
}