Store binary data

Synapse blob documents hold opaque binary payloads in a content-addressed store. VoltDrive uses the same mechanism for binary files; any synapse client can use the blob APIs directly.

api

Use the Sync API blob methods:

  • HasSynapseBlob — check whether a hash is already stored.
  • PutSynapseBlob — upload bytes. The first message must be start with synapse_id, the expected SHA-256 (unpadded base64url), and size; later messages carry payload chunks.
  • GetSynapseBlob — download bytes by hash.
  • SetSynapseDocumentMetadata — declare a single type: "blob" schema entry and bind the document with blob_hash.

The hash is SHA-256 of the raw payload bytes, encoded as unpadded base64url. Empty payloads are never stored: bind the document to the SHA-256 of empty bytes and skip PutSynapseBlob.

permissions

HasSynapseBlob and GetSynapseBlob require volt:database-read on the synapse (volt:database-write also implies read). PutSynapseBlob and SetSynapseDocumentMetadata require volt:database-write.

cli

There is no dedicated blob CLI command. Folder sync will upload and download binary files as blobs when the drive is configured to include them:

Terminal window
volt create-drive-instance "photos" ./photos @photos-drive --include-binary-files

By default binary files are skipped. See create-drive-instance and Drive.

javascript

Hash the payload, upload it if needed, then bind a blob document.

import crypto from "crypto";
import fs from "fs";
import grpc from "@grpc/grpc-js";
import { VoltClient } from "@tdxvolt/volt-client-grpc";
const synapseId = "@photos-drive";
const documentId = "photo-001";
const payload = fs.readFileSync("./photo.jpg");
const hash = crypto.createHash("sha256").update(payload).digest("base64url");
const client = new VoltClient(grpc);
await client.initialise("./volt.config.json");
const has = await client.HasSynapseBlob({
synapse_id: synapseId,
hash,
});
if (!has.exists && payload.length > 0) {
await new Promise((resolve, reject) => {
const put = client.PutSynapseBlob();
put.on("error", reject);
put.on("end", resolve);
put.write({
start: {
synapse_id: synapseId,
hash,
size: payload.length,
},
});
put.write({ block: payload });
put.end();
});
}
await client.SetSynapseDocumentMetadata({
synapse_id: synapseId,
document_id: documentId,
metadata: [{ name: "content", type: "blob" }],
blob_hash: hash,
json: JSON.stringify({ name: "photo.jpg", s: payload.length }),
});

Download by hash once GetSynapseDocumentState reports SYNAPSE_DOCUMENT_STATE_READY on this Volt (metadata can arrive before the bytes):

const chunks = [];
const get = client.GetSynapseBlob({ synapse_id: synapseId, hash });
get.on("data", (response) => {
if (response.block) {
chunks.push(Buffer.from(response.block, "base64"));
}
});
get.on("end", () => {
const bytes = Buffer.concat(chunks);
fs.writeFileSync("./photo-download.jpg", bytes);
});

Wait for READY before treating the local copy as complete. For a blob document, READY means this Volt holds the payload in its blob store.

C++

std::string hash;
tdx::crypto::CryptoManager crypto;
crypto.sha256Base64(payload, hash);
tdx::volt_api::volt::v1::HasSynapseBlobRequest hasReq;
hasReq.set_synapse_id(synapseId);
hasReq.set_hash(hash);
tdx::volt_api::volt::v1::HasSynapseBlobResponse hasResp;
auto result = voltApi->hasSynapseBlob(hasReq, hasResp);
if (result == tdx::error_code::ok && !hasResp.exists() && !payload.empty()) {
result = voltApi->putSynapseBlobSync(synapseId, hash, payload);
}
tdx::volt_api::volt::v1::SetSynapseDocumentMetadataRequest metaReq;
metaReq.set_synapse_id(synapseId);
metaReq.set_document_id(documentId);
auto* md = metaReq.add_metadata();
md->set_name("content");
md->set_type("blob");
metaReq.set_blob_hash(hash);
tdx::volt_api::volt::v1::SetSynapseDocumentMetadataResponse metaResp;
voltApi->setSynapseDocumentMetadata(metaReq, metaResp);
std::string downloaded;
voltApi->getSynapseBlobSync(synapseId, hash, downloaded);