platform-blob-storage
Cloud file storage abstraction supporting pluggable providers and optional metadata persistence via ports. Ships with a Vercel Blob provider out of the box.
Package: @breadstone/archipel-platform-blob-storage
Quick Start
typescript
import { Module } from '@nestjs/common';
import { BlobModule } from '@breadstone/archipel-platform-blob-storage';
@Module({
imports: [
// Minimal setup — Vercel Blob, no metadata tracking
BlobModule.forRoot(),
// Full setup — custom provider + metadata tracking
BlobModule.forRoot({
provider: { kind: 'vercel' },
objectPersistence: PrismaBlobObjectAdapter,
variantPersistence: PrismaBlobVariantAdapter,
isGlobal: true,
}),
],
})
export class AppModule {}Module Configuration
IBlobModuleOptions
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
provider | IBlobProviderRegistration | No | Vercel Blob | Storage backend to use |
objectPersistence | Type<BlobObjectPersistencePort> | No | — | Metadata tracking for uploaded objects |
variantPersistence | Type<BlobVariantPersistencePort> | No | — | Metadata tracking for image variants |
isGlobal | boolean | No | true | Register as a global module |
Provider Registration
typescript
// Vercel Blob (default)
{ kind: 'vercel', options?: { token?: string } }
// Custom provider
{ kind: 'custom', useClass: MyS3Provider }BlobService API
The main service for all blob operations. Inject it anywhere:
typescript
import { BlobService } from '@breadstone/archipel-platform-blob-storage';
@Injectable()
export class ImageService {
constructor(private readonly _blob: BlobService) {}
}Upload
typescript
// Upload from buffer
const result = await this._blob.uploadFile('avatars/user-123.png', imageBuffer, 'image/png');
console.log(result.metadata.url);
// Upload with full request
const result = await this._blob.uploadObject({
key: 'documents/report.pdf',
body: pdfBuffer,
contentType: 'application/pdf',
bucket: 'my-bucket',
});Upload Variant
Upload a derived version of an existing blob (e.g., thumbnail):
typescript
const thumbnail = await this._blob.uploadVariant(originalBlobId, 'thumbnail-200x200', {
key: 'avatars/user-123-thumb.png',
body: thumbnailBuffer,
contentType: 'image/png',
});Download
typescript
// Download as buffer
const result = await this._blob.downloadFile<Buffer>('avatars/user-123.png', 'arraybuffer');
const buffer = result.data;
// Download as stream
const result = await this._blob.downloadFile<ReadableStream>('videos/intro.mp4', 'stream');
// Download as JSON
const result = await this._blob.downloadFile<Record<string, unknown>>('config/settings.json', 'json');Delete
typescript
await this._blob.deleteObject({
key: 'avatars/user-123.png',
bucket: 'my-bucket',
});
// If objectPersistence is registered, metadata is also marked as deletedSigned URLs
typescript
const signedUrl = await this._blob.createSignedUrl({
key: 'private/contract.pdf',
expiresIn: 3600, // 1 hour
});Ports (Contracts)
BlobObjectPersistencePort
Tracks metadata for uploaded blob objects. Optional — when not provided, uploads still work but metadata is not persisted.
typescript
abstract class BlobObjectPersistencePort {
abstract createFromMetadata(metadata: IBlobObjectMetadata): Promise<void>;
abstract markAsDeleted(provider: string, bucket: string, key: string, versionId?: string): Promise<void>;
}BlobVariantPersistencePort
Tracks metadata for image/document variants.
typescript
abstract class BlobVariantPersistencePort {
abstract createFromMetadata(originalId: string, label: string, metadata: IBlobObjectMetadata): Promise<void>;
}Real-World Adapter: Prisma
typescript
@Injectable()
export class PrismaBlobObjectAdapter extends BlobObjectPersistencePort {
private readonly _prisma: PrismaService;
constructor(prisma: PrismaService) {
super();
this._prisma = prisma;
}
public async createFromMetadata(metadata: IBlobObjectMetadata): Promise<void> {
await this._prisma.blobObject.create({
data: {
provider: metadata.provider,
bucket: metadata.bucket,
key: metadata.key,
url: metadata.url,
size: metadata.size,
contentType: metadata.contentType,
versionId: metadata.versionId,
},
});
}
public async markAsDeleted(provider: string, bucket: string, key: string, versionId?: string): Promise<void> {
await this._prisma.blobObject.updateMany({
where: { provider, bucket, key, versionId },
data: { deletedAt: new Date() },
});
}
}Built-in Providers
VercelBlobProvider
The default blob provider using Vercel's Blob Storage API.
typescript
BlobModule.forRoot({
provider: {
kind: 'vercel',
options: { token: process.env.BLOB_READ_WRITE_TOKEN },
},
});Custom Provider
Implement IBlobProvider for any storage backend (S3, Azure Blob, GCS, MinIO):
typescript
@Injectable()
export class S3BlobProvider implements IBlobProvider {
public readonly providerId = 's3';
public async uploadObject(request: IBlobUploadRequest): Promise<IBlobUploadResult> {
// S3 PutObject implementation
}
public async downloadObject<TData>(request: IBlobDownloadRequest): Promise<IBlobDownloadResult<TData>> {
// S3 GetObject implementation
}
public async deleteObject(request: IBlobDeleteRequest): Promise<void> {
// S3 DeleteObject implementation
}
public async createSignedUrl(request: IBlobSignedUrlRequest): Promise<string> {
// S3 presigned URL
}
}
// Register
BlobModule.forRoot({
provider: { kind: 'custom', useClass: S3BlobProvider },
});Health Check
The BlobHealthIndicator integrates with NestJS Terminus for health monitoring:
typescript
import { BlobHealthIndicator } from '@breadstone/archipel-platform-blob-storage';
@Controller('health')
export class HealthController {
constructor(private readonly _blobHealth: BlobHealthIndicator) {}
@Get()
public async check(): Promise<HealthCheckResult> {
return this._blobHealth.isHealthy('blob');
}
}Exports Summary
| Export | Type | Description |
|---|---|---|
BlobModule | NestJS Module | Main module with forRoot() |
BlobService | Service | Upload, download, delete, signed URLs |
BlobObjectPersistencePort | Port | Object metadata tracking |
BlobVariantPersistencePort | Port | Variant metadata tracking |
VercelBlobProvider | Provider | Vercel Blob implementation |
BlobHealthIndicator | Health | Terminus health check |
IBlobProvider | Interface | Custom provider contract |
BlobProviderKinds | Constant | 'vercel' / 'custom' |
BlobDownloadResponseTypes | Constant | 'arraybuffer' / 'json' / 'stream' / 'text' |