From 6e973404e9ea2b45d7829e22c8a1ada0db89f402 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Jun 2026 03:35:06 +1000 Subject: [PATCH] feat(ingest): content-addressed blob store Co-Authored-By: Claude Opus 4.7 --- lib/ingest/blob_store.js | 34 +++++++++++++++++++++++++++++ tests/ingest/blob_store.test.js | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 lib/ingest/blob_store.js create mode 100644 tests/ingest/blob_store.test.js diff --git a/lib/ingest/blob_store.js b/lib/ingest/blob_store.js new file mode 100644 index 0000000..a731063 --- /dev/null +++ b/lib/ingest/blob_store.js @@ -0,0 +1,34 @@ +import crypto from 'node:crypto'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +export class BlobStore { + constructor(root) { this.root = root; } + + async hash(buf) { + return crypto.createHash('sha256').update(buf).digest('hex'); + } + + path(sha) { + return path.join(this.root, sha.slice(0, 2), sha); + } + + async write(buf) { + const sha = await this.hash(buf); + const dest = this.path(sha); + try { + await fs.access(dest); + } catch { + await fs.mkdir(path.dirname(dest), { recursive: true }); + await fs.writeFile(dest, buf); + } + return { sha, path: dest }; + } +} + +let _default = null; +export function defaultStore() { + const root = process.env.BLOB_ROOT || '/var/lib/void/blobs'; + if (!_default || _default.root !== root) _default = new BlobStore(root); + return _default; +} diff --git a/tests/ingest/blob_store.test.js b/tests/ingest/blob_store.test.js new file mode 100644 index 0000000..e96fbca --- /dev/null +++ b/tests/ingest/blob_store.test.js @@ -0,0 +1,38 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import os from 'node:os'; +import { BlobStore } from '../../lib/ingest/blob_store.js'; + +let root, store; +beforeEach(async () => { + root = await fs.mkdtemp(path.join(os.tmpdir(), 'void-blob-')); + store = new BlobStore(root); +}); + +describe('blob_store', () => { + it('hashes content and resolves a sharded path', async () => { + const buf = Buffer.from('void'); + const sha = await store.hash(buf); + expect(sha).toMatch(/^[0-9a-f]{64}$/); + const p = store.path(sha); + expect(p.startsWith(root)).toBe(true); + expect(p.includes(sha.slice(0, 2))).toBe(true); + }); + + it('write is idempotent on identical content', async () => { + const buf = Buffer.from('void'); + const a = await store.write(buf); + const b = await store.write(buf); + expect(a.path).toBe(b.path); + expect(a.sha).toBe(b.sha); + const onDisk = await fs.readFile(a.path); + expect(onDisk.equals(buf)).toBe(true); + }); + + it('distinct content gets distinct paths', async () => { + const a = await store.write(Buffer.from('void-one')); + const b = await store.write(Buffer.from('void-two')); + expect(a.path).not.toBe(b.path); + }); +});