AbsoluteJS

Blob

@absolutejs/blobv0.5.1betaData & Sync

One BlobStore interface over local disk and every S3-compatible service, without a hard AWS SDK dependency.

Object storage substrate for the AbsoluteJS PaaS: one BlobStore interface (put, get, getStream, head, delete, list, presign) with adapters for local disk and any S3-compatible service. The S3 adapter takes a narrow S3ClientLike shape instead of depending on the AWS SDK directly, so the SDK stays out of your dependency tree until you need it. Other AbsoluteJS packages ride this substrate for uploads, replay artifacts, and media storage.

#Installation

BASH
bun add @absolutejs/blob

#Capabilities

Overview

Object storage substrate for AbsoluteJS. One BlobStore interface, multiple adapters, and bounded streaming for large artifacts.

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

Show 4 more

@absolutejs/blob/aws-s3 — Official AWS SDK wiring, including multipart streaming uploads

@absolutejs/blob/uploadthing — UploadThing server SDK with stable application-owned custom ids and signed reads

@absolutejs/blob/inspection — Provider-neutral inspection contract, bounded stored-object inspection, and ClamAV clamd adapter

Both implement the same BlobStore interface — swap providers with one constructor change.

Private upload inspection

Uploads that can contain customer-controlled bytes should remain under a private quarantine key until an inspector returns clean. The inspection subpath preserves the same workflow across local, UploadThing, S3, R2, and Spaces storage:

The clamd adapter uses the bounded INSTREAM protocol and returns clean, infected, or unavailable. Scanner errors never become a clean verdict. The host owns durable quarantine, retry, retention, and audit policy; Blob only owns byte transport and normalized inspection.

Local

Files at /. Metadata (contentType, user metadata, cache headers) at /.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)

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

R2 is fully S3-compatible — the only thing that changes is the endpoint. Same wiring + s3BlobStore adapter.

Show 2 more

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

put returns the stored object's metadata (size, contentType,

etag, user metadata).

get returns null for missing keys (not throw).

Show 6 more

getStream for large blobs — avoids loading the body into memory.

delete is idempotent: deleting a missing key is success.

list paginates via cursor — pass back into the next call as

options.cursor.

presign builds a time-limited URL for direct browser upload/

download. operation: 'put' for uploads, 'get' (default) for downloads. Throws BlobError('UNSUPPORTED') on local.

Key validation

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.

One BlobStore interface

put, get, getStream, head, delete, list, and presign behave identically across adapters, so provider choice is a deploy-time detail.

S3-compatible adapter

AWS S3, Cloudflare R2, Backblaze B2, MinIO, Wasabi, and Tigris all work through the same s3BlobStore adapter — only the endpoint changes.

No hard SDK dependency

The adapter accepts a narrow S3ClientLike object, keeping @aws-sdk/client-s3 out of your dependency tree as a hard dependency.

Presigned URLs

presign builds time-limited URLs for direct browser upload (operation: put) or download (operation: get) without proxying bytes through your server.

Key validation built in

Every operation runs validateKey, rejecting leading slashes, NUL bytes, and . or .. path segments with BlobError(INVALID_KEY) — path traversal is closed at the substrate level.

Streaming and pagination

getStream serves large blobs without loading them into memory, and list paginates with a cursor you pass back into the next call.

Quarantine and inspection

Keep customer-controlled uploads private until bounded inspection returns clean. The ClamAV adapter distinguishes clean, infected, and unavailable; scanner errors never become a clean verdict.

#Adapter capability matrix

Choose an adapter by deployment and upload requirements without changing the BlobStore contract used by application code.

OptionLocalGeneric S3AWS SDKUploadThing
Streaming readsYesYesYesYes
Presigned transferNoYesYesSigned reads
Multipart uploadNoManualBuilt inProvider managed
Best fitDev/single hostS3-compatible cloudsAWS productionManaged uploads

#Safe upload lifecycle

Treat uploaded bytes as untrusted until bounded inspection succeeds.

  1. Quarantine
    Write to a quarantine key or isolated bucket.
  2. Bound
    Enforce byte, duration, and decompression limits.
  3. Inspect
    Run provider-neutral inspectors such as ClamAV INSTREAM.
  4. Decide
    Promote clean content or retain rejection evidence.

Outcomes

What you can build

Overview

Object storage substrate for AbsoluteJS. One BlobStore interface, multiple adapters, and bounded streaming for large artifacts.

Adapters

Subpath — Backs

Private upload inspection

Uploads that can contain customer-controlled bytes should remain under a private quarantine key until an inspector returns clean. The inspection subpath preserves the same workflow across local, UploadThing, S3, R2, and Spaces storage:

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/blob version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Local
Files at /. Metadata (contentType, user metadata, cache headers) at /.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.
2
Key validation
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.

#@absolutejs/blob quick start

Partial snippet

# @absolutejs/blob

TS
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 });

#Private upload inspection

Partial snippet

Uploads that can contain customer-controlled bytes should remain under a private quarantine key until an inspector returns clean. The inspection subpath preserves the same workflow across local, UploadThing, S3, R2, and Spaces storage:

TS
import {
  createClamdBlobInspector,
  inspectStoredBlob,
} from "@absolutejs/blob/inspection";

const inspector = createClamdBlobInspector({ host: "clamav.internal" });
const result = await inspectStoredBlob(blobs, inspector, {
  filename: "evidence.pdf",
  key: "quarantine/case/evidence.pdf",
  maxBytes: 25 * 1024 * 1024,
});

#Local

Partial snippet

Working example for Local.

TS
import { localBlobStore } from "@absolutejs/blob/local";

const blobs = localBlobStore({ root: "./var/blobs" });
await blobs.put("uploads/file.pdf", body);

#Quick Start

Partial snippet

The local adapter stores files on disk — ideal for development, tests, and single-host deployments. get returns null for missing keys instead of throwing, and delete is idempotent.

TS
import { localBlobStore } from '@absolutejs/blob/local';

const blobs = localBlobStore({ root: './var/blobs' });

await blobs.put('users/42/avatar.png', body, {
	contentType: 'image/png'
});

const bytes = await blobs.get('users/42/avatar.png'); // null if missing
const meta = await blobs.head('users/42/avatar.png');
await blobs.delete('users/42/avatar.png'); // idempotent

#S3-Compatible Services

Partial snippet

The S3 adapter works with any S3-compatible service. You hand it an S3ClientLike object wired onto the real AWS SDK client, so the SDK is your dependency, not the substrate’s.

TS
import {
	S3Client,
	PutObjectCommand,
	GetObjectCommand,
	HeadObjectCommand,
	DeleteObjectCommand,
	ListObjectsV2Command
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { s3BlobStore } from '@absolutejs/blob/s3';

// Cloudflare R2 — swap the endpoint for B2, MinIO, Wasabi, Tigris
const aws = new S3Client({
	credentials: { accessKeyId, secretAccessKey },
	endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
	region: 'auto'
});

// Wire the narrow S3ClientLike shape onto the real SDK client
const blobs = s3BlobStore({
	bucket: 'my-bucket',
	client: {
		deleteObject: (input) => aws.send(new DeleteObjectCommand(input)),
		getObject: (input) => aws.send(new GetObjectCommand(input)),
		headObject: (input) => aws.send(new HeadObjectCommand(input)),
		listObjectsV2: (input) => aws.send(new ListObjectsV2Command(input)),
		presignGetObject: (input, options) =>
			getSignedUrl(aws, new GetObjectCommand(input), options),
		presignPutObject: (input, options) =>
			getSignedUrl(aws, new PutObjectCommand(input), options),
		putObject: (input) => aws.send(new PutObjectCommand(input))
	}
});

// Time-limited URL for direct browser upload
const url = await blobs.presign('uploads/file.pdf', {
	operation: 'put',
	ttlSeconds: 900
});

#Private Upload Inspection

Partial snippet

Inspect a quarantined object with strict byte bounds before promoting it into application-visible storage.

TS
import {
	createClamdBlobInspector,
	inspectStoredBlob
} from '@absolutejs/blob/inspection';

const inspector = createClamdBlobInspector({ host: 'clamav.internal' });
const result = await inspectStoredBlob(blobs, inspector, {
	filename: 'evidence.pdf',
	key: 'quarantine/case/evidence.pdf',
	maxBytes: 25 * 1024 * 1024
});

if (result.verdict === 'clean') await promoteUpload(result);

#Adapters

Both adapters implement the same BlobStore interface, so swapping providers is a one-constructor change.

@absolutejs/blob/localFilesystem storage for dev, tests, and single-host deployments. Atomic writes via temp file + rename; metadata stored alongside each key.
@absolutejs/blob/s3AWS S3, Cloudflare R2, Backblaze B2, MinIO, Wasabi, Tigris — any S3-compatible HTTP API via a narrow S3ClientLike shape.
@absolutejs/blob/aws-s3Official AWS SDK wiring with multipart streaming uploads that never materialize the entire body in control-plane memory.
@absolutejs/blob/uploadthingUploadThing server SDK with stable application-owned custom ids and signed reads.
@absolutejs/blob/inspectionProvider-neutral stored-object inspection with bounded reads and a ClamAV clamd INSTREAM adapter.
Beta interface
@absolutejs/blob is 0.x — the BlobStore interface is settling, and option shapes may still shift between minor versions.
Presign requires an S3 backend
The local adapter throws BlobError(UNSUPPORTED) for presign. If you need presigned URLs in development, run the S3 adapter against a local MinIO.

#API reference

Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.

12 symbols
BlobMetadatatypePermalink

@absolutejs/blob — object storage substrate. One BlobStore interface, multiple adapters: - @absolutejs/blob/local — filesystem (dev / single-host prod) - @absolutejs/blob/s3 — S3-compatible (AWS S3, Cloudflare R2, Backblaze B2, MinIO, Wasabi) Adapters share the same shape so swapping providers is one constructor change. Out-of-scope (deliberate): - CDN integration. The presign URLs from S3-compat stores work for direct browser download; CDN cache invalidation is the CDN's job. - Encryption-at-re

TS
type BlobMetadata = {
    /** Size in bytes. */
    size: number;
    /** MIME type the blob was stored with (or `undefined` if unset). */
    contentType?: string;
    /** Server-side ETag (typically the MD5 hash, but provider-specific). */
    etag?: string;
    /** Last-modified time, ms since epoch. */
    lastModified?: number;
    /** Caller-supplied user metadata. */
    metadata?: Record<string, string>;
};
Exported from @absolutejs/blob

Current package surface

What ships today

@absolutejs/blobv0.5.1 · betaData & SyncnpmSource
8entry points39symbols

Import surface · click to copy