@eclosion-tech/syntropy-blob-storage
v0.1.0
Published
Shared blob storage abstraction with S3-compatible and UploadThing-style helpers
Readme
@eclosion-tech/syntropy-blob-storage
Shared blob storage abstractions for Syntropy and sibling projects.
Features
- Provider-agnostic
BlobStorageinterface. S3BlobStorageadapter for AWS S3 and S3-compatible providers.InMemoryBlobStorageadapter for tests and local development.- UploadThing-style typed upload routing with
createUploadRouterandBlobUploadService. - First-class presigned upload and download URL support.
Install
pnpm add @eclosion-tech/syntropy-blob-storageCore storage usage
import { S3BlobStorage } from "@eclosion-tech/syntropy-blob-storage";
const storage = new S3BlobStorage({
bucket: process.env.S3_BUCKET!,
region: process.env.S3_REGION ?? "us-east-1",
endpoint: process.env.S3_ENDPOINT, // Optional for S3-compatible providers
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true",
keyPrefix: process.env.S3_KEY_PREFIX, // Optional (example: "production")
publicBaseUrl: process.env.S3_PUBLIC_BASE_URL, // Optional
});
await storage.put({
key: "source-maps/web/1.2.3/main.js.map",
body: Buffer.from("..."),
contentType: "application/json",
});
const file = await storage.get("source-maps/web/1.2.3/main.js.map");UploadThing-style upload routes
import {
BlobUploadService,
S3BlobStorage,
createUploadRouter,
} from "@eclosion-tech/syntropy-blob-storage";
type AvatarInput = { userId: string };
type InvoiceInput = { orgId: string; invoiceId: string };
const storage = new S3BlobStorage({
bucket: process.env.S3_BUCKET!,
region: process.env.S3_REGION ?? "us-east-1",
endpoint: process.env.S3_ENDPOINT,
forcePathStyle: true,
});
const routes = createUploadRouter({
avatar: {
accept: ["image/*"],
maxSizeBytes: 5 * 1024 * 1024,
buildKey: ({ fileName, input }: { fileName: string; input: AvatarInput }) =>
`avatars/${input.userId}/${fileName}`,
},
invoicePdf: {
accept: ["application/pdf"],
maxSizeBytes: 25 * 1024 * 1024,
buildKey: ({ fileName, input }: { fileName: string; input: InvoiceInput }) =>
`invoices/${input.orgId}/${input.invoiceId}-${fileName}`,
},
});
const uploadService = new BlobUploadService(storage, routes, {
keyPrefix: "tenant-assets",
});
const signed = await uploadService.createUploadUrl({
route: "avatar",
input: { userId: "u_123" },
fileName: "profile.png",
contentType: "image/png",
fileSize: 120_000,
});
// Client uploads with signed.url and signed.headers
// Then your server can acknowledge completion:
await uploadService.completeUpload({
route: "avatar",
key: signed.key,
input: { userId: "u_123" },
contentType: "image/png",
fileSize: 120_000,
});Next.js route handler pattern
// app/api/uploads/route.ts
import { NextRequest, NextResponse } from "next/server";
import { BlobUploadService, S3BlobStorage, createUploadRouter } from "@eclosion-tech/syntropy-blob-storage";
const storage = new S3BlobStorage({
bucket: process.env.S3_BUCKET!,
region: process.env.S3_REGION ?? "us-east-1",
endpoint: process.env.S3_ENDPOINT,
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true",
});
const uploadService = new BlobUploadService(
storage,
createUploadRouter({
media: {
accept: ["image/*", "video/*"],
maxSizeBytes: 100 * 1024 * 1024,
buildKey: ({ fileName, input }: { fileName: string; input: { orgId: string } }) =>
`media/${input.orgId}/${Date.now()}-${fileName}`,
},
})
);
export async function POST(request: NextRequest) {
const body = await request.json();
const signed = await uploadService.createUploadUrl({
route: "media",
input: { orgId: body.orgId },
fileName: body.fileName,
contentType: body.contentType,
fileSize: body.fileSize,
});
return NextResponse.json(signed);
}Supported environments
- AWS S3
- Cloudflare R2 (set
endpoint, oftenforcePathStyle: true) - MinIO (set
endpointandforcePathStyle: true) - Any S3-compatible object store
Testing
Use the in-memory adapter when you do not want network calls:
import { InMemoryBlobStorage } from "@eclosion-tech/syntropy-blob-storage";
const storage = new InMemoryBlobStorage();Exports
BlobStoragetypes and method contractsS3BlobStorageInMemoryBlobStorageBlobUploadServicecreateUploadRouter- error types:
BlobStorageError,InvalidUploadRouteError,UploadValidationError
