feat(ingest): content-addressed blob store
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
34
lib/ingest/blob_store.js
Normal file
34
lib/ingest/blob_store.js
Normal file
@@ -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;
|
||||
}
|
||||
38
tests/ingest/blob_store.test.js
Normal file
38
tests/ingest/blob_store.test.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user