@absolutejs/blob
v0.2.1
Published
Object storage substrate for AbsoluteJS. One BlobStore interface with bounded streaming local and S3-compatible adapters, plus official AWS SDK wiring for multipart object storage.
Downloads
430
Maintainers
Readme
@absolutejs/blob
Object storage substrate for AbsoluteJS. One BlobStore interface, multiple
adapters, and bounded streaming for large artifacts.
const store: BlobStore = /* localBlobStore(...) | s3BlobStore(...) */;
await store.put('users/42/avatar.png', body, { contentType: 'image/png' });
const bytes = await store.get('users/42/avatar.png');
const url = await store.presign('users/42/avatar.png', { ttlSeconds: 900 });Adapters
| Subpath | Backs |
| --- | --- |
| @absolutejs/blob/local | Filesystem (dev / single-host prod / tests) |
| @absolutejs/blob/s3 | AWS S3, Cloudflare R2, Backblaze B2, MinIO, Wasabi, Tigris — any S3-compatible HTTP API |
| @absolutejs/blob/aws-s3 | Official AWS SDK wiring, including multipart streaming uploads |
Both implement the same BlobStore interface — swap providers with
one constructor change.
Local
import { localBlobStore } from '@absolutejs/blob/local';
const blobs = localBlobStore({ root: './var/blobs' });
await blobs.put('uploads/file.pdf', body);Files at <root>/<key>. Metadata (contentType, user metadata,
cache headers) at <root>/<key>.meta.json. Atomic writes via temp
file + rename. presign() throws BlobError('UNSUPPORTED') —
use the S3 adapter against a local MinIO if you need presign in
dev.
S3 (any S3-compatible service)
import { S3Client } from '@aws-sdk/client-s3';
import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
const aws = new S3Client({ region: 'us-east-1' });
const blobs = awsS3BlobStore({ bucket: 'my-bucket', client: aws });awsS3BlobStore uses the official SDK command clients and
@aws-sdk/lib-storage multipart uploads. Streams are never materialized as one
control-plane buffer. The lower-level s3BlobStore and S3ClientLike remain
available for custom clients.
Cloudflare R2
const aws = new S3Client({
region: 'auto',
endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: { accessKeyId, secretAccessKey },
});R2 is fully S3-compatible — the only thing that changes is the
endpoint. Same wiring + s3BlobStore adapter.
Backblaze B2, MinIO, Wasabi, Tigris
All the same pattern. Point endpoint at the provider's URL,
provide credentials, hand the client into s3BlobStore.
BlobStore interface
type BlobStore = {
readonly description: string;
put: (key: string, body: BlobBody, options?: PutOptions) => Promise<BlobObject>;
get: (key: string) => Promise<Uint8Array | null>;
getStream: (key: string) => Promise<ReadableStream<Uint8Array> | null>;
head: (key: string) => Promise<BlobObject | null>;
delete: (key: string) => Promise<void>;
list: (options?: ListOptions) => Promise<ListResult>;
presign: (key: string, options?: PresignOptions) => Promise<string>;
};
type BlobBody = Uint8Array | string | ReadableStream<Uint8Array>;putreturns the stored object's metadata (size, contentType, etag, user metadata).getreturnsnullfor missing keys (not throw).getStreamfor large blobs — avoids loading the body into memory.deleteis idempotent: deleting a missing key is success.listpaginates viacursor— pass back into the next call asoptions.cursor.presignbuilds a time-limited URL for direct browser upload/ download.operation: 'put'for uploads,'get'(default) for downloads. ThrowsBlobError('UNSUPPORTED')onlocal.
Key validation
validateKey('users/42/avatar.png'); // ok
validateKey('/etc/passwd'); // BlobError('INVALID_KEY')
validateKey('../escape'); // BlobError('INVALID_KEY')
validateKey('with\0nul'); // BlobError('INVALID_KEY')Adapters call validateKey() on every operation. Leading slashes,
NUL bytes, and . / .. path segments throw BlobError('INVALID_KEY')
— closes the path-traversal class of bugs at the substrate level.
License
BSL-1.1 with named carveout against hosted object-storage services. Change date: 2030-05-31 (Apache 2.0).
