Initial import of verae-request-splitter from zapier monorepo
This commit is contained in:
commit
fe2d09dddb
5 changed files with 134 additions and 0 deletions
8
NATS.md
Normal file
8
NATS.md
Normal 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
13
SUMMARY.md
Normal 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
10
package.json
Normal 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
71
src/splitRequest.js
Normal 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
32
test/splitRequest.test.js
Normal 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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue