Skip to content

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

PropertyTypeRequiredDefaultDescription
providerIBlobProviderRegistrationNoVercel BlobStorage backend to use
objectPersistenceType<BlobObjectPersistencePort>NoMetadata tracking for uploaded objects
variantPersistenceType<BlobVariantPersistencePort>NoMetadata tracking for image variants
isGlobalbooleanNotrueRegister 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 deleted

Signed 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

ExportTypeDescription
BlobModuleNestJS ModuleMain module with forRoot()
BlobServiceServiceUpload, download, delete, signed URLs
BlobObjectPersistencePortPortObject metadata tracking
BlobVariantPersistencePortPortVariant metadata tracking
VercelBlobProviderProviderVercel Blob implementation
BlobHealthIndicatorHealthTerminus health check
IBlobProviderInterfaceCustom provider contract
BlobProviderKindsConstant'vercel' / 'custom'
BlobDownloadResponseTypesConstant'arraybuffer' / 'json' / 'stream' / 'text'

Released under the MIT License.