Initial import of verae-request-splitter from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 13:32:11 -04:00
commit 6e9843714b
5 changed files with 134 additions and 0 deletions

8
NATS.md Normal file
View file

@ -0,0 +1,8 @@
# NATS — verae-request-splitter
| Direction | Address | To | Body |
|-----------|---------|-----|------|
| OUT | `verae.archive.put` | each WORM | one message per publicMeta / privateMeta / file |
| OUT | (in-process) chain | middleware | `{ sha256, hashAlg, data? }` |
No inbound NATS in the in-process embedding.

13
SUMMARY.md Normal file
View file

@ -0,0 +1,13 @@
# verae-request-splitter
**Job:** Peel a request into (1) chain hash and (2) off-chain archive puts.
**Expects messages from:** middleware-http (in-process call today; later NATS `verae.splitter.in` if split out).
**Sends messages to:**
- Chain path: `{ sha256, hashAlg, data? }`
- `verae.archive.put` for each `publicMeta` / `privateMeta` / `file`
**Does not talk to Zapier or NATS job poller.**
**Test:** `npm test` — hash-only has zero puts; two files + public meta → 3 puts.

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "verae-request-splitter",
"version": "0.1.0",
"type": "module",
"description": "Split hash/sign vs attachments for chain vs WORM archives",
"scripts": {
"test": "node --test test/*.test.js"
},
"engines": { "node": ">=20" }
}

71
src/splitRequest.js Normal file
View file

@ -0,0 +1,71 @@
/**
* Split a Zapier/middleware request into chain vs archive parts.
* @module splitRequest
*/
import { createHash } from 'node:crypto';
/**
* @param {string} data
* @returns {string}
*/
export function sha256Hex(data) {
return createHash('sha256').update(String(data), 'utf8').digest('hex');
}
/**
* @param {object} body
* @returns {{
* chain: { sha256: string, hashAlg: string, data?: string },
* archivePuts: Array<{ kind: string, sha256: string, record: object }>,
* includeAttached: boolean
* }}
*/
export function splitRequest(body = {}) {
const sha256 = (body.sha256 || (body.data != null ? sha256Hex(body.data) : '')).toLowerCase();
if (!sha256) {
throw new Error('data or sha256 is required');
}
const includeAttached = Boolean(body.includeAttached);
const archivePuts = [];
if (body.publicMetadata && Object.keys(body.publicMetadata).length) {
archivePuts.push({
kind: 'publicMeta',
sha256,
record: { publicMetadata: body.publicMetadata },
});
}
if (body.privateMetadata && Object.keys(body.privateMetadata).length) {
archivePuts.push({
kind: 'privateMeta',
sha256,
record: { privateMetadata: body.privateMetadata },
});
}
const files = Array.isArray(body.files) ? body.files : [];
for (const f of files) {
const bytes = f.contentBase64
? Buffer.from(String(f.contentBase64), 'base64')
: Buffer.from(String(f.data ?? ''), 'utf8');
const fileHash = sha256Hex(bytes);
archivePuts.push({
kind: 'file',
sha256,
record: {
path: f.path || f.filename || 'file.bin',
size: bytes.length,
contentSha256: fileHash,
contentBase64: bytes.toString('base64'),
},
});
}
return {
chain: {
sha256,
hashAlg: body.hashAlg || 'SHA256',
data: body.data,
},
archivePuts,
includeAttached,
};
}

32
test/splitRequest.test.js Normal file
View file

@ -0,0 +1,32 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { splitRequest, sha256Hex } from '../src/splitRequest.js';
describe('splitRequest', () => {
it('hash-only has no archive puts', () => {
const out = splitRequest({ data: 'hello' });
assert.equal(out.chain.sha256, sha256Hex('hello'));
assert.equal(out.archivePuts.length, 0);
assert.equal(out.includeAttached, false);
});
it('splits public metadata and two files onto archive puts', () => {
const out = splitRequest({
data: 'hello',
publicMetadata: { title: 't' },
files: [
{ path: 'a.txt', data: 'A' },
{ path: 'b.txt', data: 'B' },
],
includeAttached: true,
});
assert.equal(out.archivePuts.length, 3);
assert.equal(out.archivePuts.filter((p) => p.kind === 'file').length, 2);
assert.equal(out.includeAttached, true);
assert.equal(out.chain.sha256, sha256Hex('hello'));
});
it('throws without data or sha256', () => {
assert.throws(() => splitRequest({}), /data or sha256/);
});
});