Initial import of verae-request-splitter from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 14:59:39 -04:00
commit 8c26fca687
6 changed files with 166 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.

32
README.md Normal file
View file

@ -0,0 +1,32 @@
# verae-request-splitter
Splits a Zapier/middleware body into (1) the **chain hash** and (2) off-chain **archive puts**. File bytes never go on `api.veraetime.net`.
**Forgejo:** https://git.georgelambert.org/marchon/verae-request-splitter
**Catalog:** https://zapier.georgelambert.org/packages/verae-request-splitter/README.pdf
**NATS:** https://zapier.georgelambert.org/docs-master/modules/verae-request-splitter/NATS.pdf
## Run
```bash
cd packages/verae-request-splitter
npm test
```
Today middleware calls this **in-process**. A later split-out can subscribe `verae.splitter.in`.
## Outputs
| Path | Content |
|------|---------|
| Chain | `{ sha256, hashAlg }` only |
| `verae.archive.put` | each `publicMeta` / `privateMeta` / `file` |
| Batch | Merkle **root** on chain; leaves → tree-node puts (`kind=tree`) |
## Depends on
- middleware-http (caller)
- archive-worm / tree-node (puts)
- chain client (hash seal)
Does not talk to Zapier or the job poller.

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/);
});
});