Object Store
One object-storage interface over five protocols: a local directory, a plain HTTP PUT endpoint, S3 and anything speaking SigV4, Google Cloud Storage, and Azure Blob Storage. A backend is built from a config naming the protocol, and every backend answers the same calls, so the protocol is a deployment decision rather than an application one.
Each call is wrapped in the retry policy and a circuit breaker, so a store that starts failing stops being asked until it recovers rather than failing every request slowly. getStream is passed through unwrapped, because a stream cannot be replayed by a retry.
A backend carries the data classifications it may hold and a residency tag, and answers servesClassification(cls), which is how a caller holding regulated data picks a store allowed to keep it. Every backend carries the three presign functions; a protocol that cannot presign a request throws objectstore/presign-not-supported from them. listVersions is null on a protocol without object versioning. Bucket lifecycle is reached through b.objectStore.bucketOps.create.
b.objectStore.buildBackend(config) #
{
protocol: string, // required; local | http-put | sigv4 | gcs | azure-blob
name: string, // store + breaker name; see above for the defaults
classifications: array, // data classes this store may hold
residencyTag: string, // default: "unrestricted"
retry: object, // b.retry.withRetry opts; not applied to a put whose
// body is a stream, which cannot be replayed
breaker: object, // b.retry.CircuitBreaker opts
}
Build a backend for one store. protocol picks the implementation and is required: omitting it raises protocol-dispatcher/missing-protocol rather than selecting a default. A name outside the five raises protocol-dispatcher/unknown-protocol listing them, and no config at all raises objectstore/bad-opt. The rest of the config is that protocol's own, so a SigV4 store takes its region and credentials here and a local store takes its root directory.
The returned backend answers put, get, head, delete and list through the retry policy and a circuit breaker named after the store. getStream is answered directly by the protocol, with neither: a stream cannot be replayed, so a retry would hand back a half-consumed one. A transient failure opening a download reaches the caller as it happened. On the remote protocols, a get or getStream whose body ends short of its Content-Length, or whose HTTP/2 stream is reset before the body ends, fails with objectstore/truncated-body, marked transient, over HTTP/1.1 and HTTP/2. listVersions is present only on a protocol with object versioning (SigV4) and is null otherwise. presignedUploadUrl, presignedDownloadUrl and presignedUploadPolicy are present on every backend; a protocol that cannot presign that request throws objectstore/presign-not-supported. raw is the unwrapped protocol object, for the cases where retrying would be wrong.
name names the store and the breaker both. Omit it and the backend's name is the protocol while the breaker is protocol:root, so two local stores under different directories trip independently; pass it and both take the value given.
classifications lists the data classes the store may hold and defaults to ["*"], meaning any. servesClassification(cls) answers whether this store accepts a given class, which is the check a caller holding regulated data makes before writing. residencyTag records where the bytes come to rest and defaults to unrestricted.
var b = require("@blamejs/core");
var store = b.objectStore.buildBackend({
protocol: "local",
rootDir: "./data/objects",
classifications: ["public"],
});
await store.put("notes/one.txt", Buffer.from("hello"));
store.servesClassification("phi"); // → false