Part 9 — blobs
Records are capped at 512KB, and last-write-wins on a 20MB payload is a bad idea. Large content goes in a blob, sealed the same way, addressed by an id a record points at.
const blobId = await engine.uploadBlob('g:acme', bytes, { policy: 'retain' });await engine.put('g:acme', { kind: 'file', data: { blobId, name: 'plan.pdf' } });
const bytes = await engine.downloadBlob('g:acme', blobId);await engine.ackBlob('g:acme', blobId); // "I have a durable copy"Retention policies
Section titled “Retention policies”| policy | the relay keeps the bytes until |
|---|---|
retain |
somebody deletes them (default) |
transport |
every member has acked a copy |
ephemeral |
the TTL passes (ttlMs, default 24h, capped at 30 days) |
await engine.uploadBlob('d:alex', bytes, { policy: 'transport' });await engine.uploadBlob('g:acme', bytes, { policy: 'ephemeral', ttlMs: 3_600_000 });Retention is intent, not enforcement. These policies are executed by a relay you may not control. A relay that keeps a copy is invisible to you.
A blob may predate a rotation, so downloadBlob tries the keyring newest-epoch
first. uploadBlob throws if the relay already holds different bytes under a
freshly minted id — 96 random bits colliding means something is very wrong, and
a record pointing at somebody else’s ciphertext is worse than an error.
Blob egress is limited by request count, not by bytes. 120 blob requests a minute per member, and a blob may be 25MB. Bandwidth shaping belongs in front of the relay.