platform-blob-storage
Cloud file storage abstraction supporting pluggable strategies and optional metadata persistence via ports. Ships with Vercel Blob, Azure Blob Storage, AWS S3, local filesystem, and an empty/mock strategy out of the box.
Package: @breadstone/archipel-platform-blob-storage
npm install @breadstone/archipel-platform-blob-storageQuick Start
import { Module } from '@nestjs/common';
import { BlobModule } from '@breadstone/archipel-platform-blob-storage';
import { VercelBlobStorageStrategy, VERCEL_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/vercel';
@Module({
imports: [
// Minimal setup — Vercel Blob, no metadata tracking
BlobModule.register({
storageStrategy: VercelBlobStorageStrategy,
configEntries: VERCEL_CONFIG_ENTRIES,
}),
// Full setup — with metadata tracking
BlobModule.register({
storageStrategy: VercelBlobStorageStrategy,
configEntries: VERCEL_CONFIG_ENTRIES,
objectPersistence: PrismaBlobObjectAdapter,
variantPersistence: PrismaBlobVariantAdapter,
isGlobal: true,
}),
],
})
export class AppModule {}Module Configuration
IBlobModuleOptions
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
storageStrategy | Type<BlobStorageStrategyBase> | Yes | — | Storage strategy to use |
configEntries | IConfigEntry[] | No | [] | Config entries for the chosen strategy |
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 |
Strategy Registration
import { VercelBlobStorageStrategy, VERCEL_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/vercel';
import { AzureBlobStorageStrategy, AZURE_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/azure';
import { AwsS3BlobStorageStrategy, AWS_S3_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/aws-s3';
import { LocalBlobStorageStrategy, LOCAL_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/local';
import { EmptyBlobStorageStrategy } from '@breadstone/archipel-platform-blob-storage/strategies/empty';
// Vercel Blob
BlobModule.register({ storageStrategy: VercelBlobStorageStrategy, configEntries: VERCEL_CONFIG_ENTRIES });
// Azure Blob Storage
BlobModule.register({ storageStrategy: AzureBlobStorageStrategy, configEntries: AZURE_CONFIG_ENTRIES });
// AWS S3
BlobModule.register({ storageStrategy: AwsS3BlobStorageStrategy, configEntries: AWS_S3_CONFIG_ENTRIES });
// Local filesystem
BlobModule.register({ storageStrategy: LocalBlobStorageStrategy, configEntries: LOCAL_CONFIG_ENTRIES });
// Empty/mock strategy (for development and testing — logs operations, returns mock data)
BlobModule.register({ storageStrategy: EmptyBlobStorageStrategy });
// Custom strategy — extend BlobStorageStrategyBase
BlobModule.register({ storageStrategy: MyCustomStrategy });BlobService API
The main service for all blob operations. Inject it anywhere:
import { BlobService } from '@breadstone/archipel-platform-blob-storage';
@Injectable()
export class ImageService {
constructor(private readonly _blob: BlobService) {}
}Upload
// 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):
const thumbnail = await this._blob.uploadVariant(originalBlobId, 'thumbnail-200x200', {
key: 'avatars/user-123-thumb.png',
body: thumbnailBuffer,
contentType: 'image/png',
});Download
// 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
await this._blob.deleteObject({
key: 'avatars/user-123.png',
bucket: 'my-bucket',
});
// If objectPersistence is registered, metadata is also marked as deletedSigned URLs
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.
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.
abstract class BlobVariantPersistencePort {
abstract createFromMetadata(originalId: string, label: string, metadata: IBlobObjectMetadata): Promise<void>;
}Real-World Adapter: Prisma
@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 Strategies
VercelBlobStorageStrategy
The Vercel Blob storage strategy using Vercel's Blob Storage API.
import { VercelBlobStorageStrategy, VERCEL_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/vercel';
BlobModule.register({
storageStrategy: VercelBlobStorageStrategy,
configEntries: VERCEL_CONFIG_ENTRIES,
});Environment variables: VERCEL_BLOB_READ_WRITE_TOKEN, VERCEL_BLOB_URL, VERCEL_BLOB_PUBLIC_URL, VERCEL_BLOB_BUCKET, VERCEL_BLOB_REGION
AzureBlobStorageStrategy
Microsoft Azure Blob Storage integration using @azure/storage-blob.
import { AzureBlobStorageStrategy, AZURE_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/azure';
BlobModule.register({
storageStrategy: AzureBlobStorageStrategy,
configEntries: AZURE_CONFIG_ENTRIES,
});Environment variables:
| Variable | Required | Description |
|---|---|---|
AZURE_BLOB_CONNECTION_STRING | No* | Full connection string (takes precedence) |
AZURE_BLOB_ACCOUNT_NAME | No* | Storage account name for DefaultAzureCredential |
AZURE_BLOB_CONTAINER_NAME | Yes | Container to operate on |
* Provide either
connectionStringoraccountName. WhenaccountNameis set without a connection string, the strategy authenticates viaDefaultAzureCredentialfrom@azure/identity(supports managed identity, environment credentials, etc.). Install@azure/identityas a peer dependency when using this mode.
The strategy supports SAS token generation for signed URLs and lazy-loads the Azure SDK for tree-shaking.
AwsS3BlobStorageStrategy
Amazon S3 integration using @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner.
import { AwsS3BlobStorageStrategy, AWS_S3_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/aws-s3';
BlobModule.register({
storageStrategy: AwsS3BlobStorageStrategy,
configEntries: AWS_S3_CONFIG_ENTRIES,
});Environment variables:
| Variable | Required | Description |
|---|---|---|
AWS_S3_REGION | Yes | AWS region |
AWS_S3_BUCKET | Yes | S3 bucket name |
AWS_S3_ACCESS_KEY_ID | No* | Explicit access key |
AWS_S3_SECRET_ACCESS_KEY | No* | Explicit secret key |
AWS_S3_ENDPOINT | No | Custom endpoint (for MinIO, LocalStack, etc.) |
*
accessKeyIdandsecretAccessKeymust be provided together. Omit both to use the default AWS credential chain (IAM roles, instance profiles, environment variables).
The strategy supports pre-signed URLs and lazy-loads the AWS SDK for tree-shaking. downloadObject() gracefully handles JSON parse errors, returning the raw response body when content cannot be parsed.
LocalBlobStorageStrategy
Local filesystem storage for development and testing.
import { LocalBlobStorageStrategy, LOCAL_CONFIG_ENTRIES } from '@breadstone/archipel-platform-blob-storage/strategies/local';
BlobModule.register({
storageStrategy: LocalBlobStorageStrategy,
configEntries: LOCAL_CONFIG_ENTRIES,
});Environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
LOCAL_BLOB_BASE_PATH | No | ./blob-data | Base directory for local file storage |
LOCAL_BLOB_BUCKET | No | default | Default bucket subdirectory |
EmptyBlobStorageStrategy
A no-op storage strategy for development and testing. Logs all operations to the NestJS logger and returns mock results. No actual storage occurs.
import { EmptyBlobStorageStrategy } from '@breadstone/archipel-platform-blob-storage/strategies/empty';
BlobModule.register({
storageStrategy: EmptyBlobStorageStrategy,
});No environment variables required.
Custom Strategy
Extend BlobStorageStrategyBase for any other storage backend (GCS, MinIO, etc.):
@Injectable()
export class GcsBlobStorageStrategy extends BlobStorageStrategyBase {
public override readonly providerId = 'gcs';
public override readonly defaultBucket = 'my-gcs-bucket';
public async uploadObject(request: IBlobUploadRequest): Promise<IBlobUploadResult> {
// GCS upload implementation
}
public async downloadObject<TData>(request: IBlobDownloadRequest): Promise<IBlobDownloadResult<TData>> {
// GCS download implementation
}
public async deleteObject(request: IBlobDeleteRequest): Promise<void> {
// GCS delete implementation
}
public override async createSignedUrl(request: IBlobSignedUrlRequest): Promise<string> {
// GCS signed URL
}
}
// Register
BlobModule.register({ storageStrategy: GcsBlobStorageStrategy });Health Check
The BlobHealthIndicator pings the configured blob storage URL via HTTP. If no URL is configured, it reports disabled: true. Import it from the /health subpath:
import { BlobHealthIndicator } from '@breadstone/archipel-platform-blob-storage/health';
import { HealthModule } from '@breadstone/archipel-platform-health';
@Module({
imports: [
BlobModule.register({ /* ... */ }),
HealthModule.withIndicators([BlobHealthIndicator]),
],
})
export class AppModule {}| Key | Check | Dependencies |
|---|---|---|
blob | HTTP ping to blob URL (or disabled if unconfigured) | ConfigService, HttpHealthIndicator |
---
## Sub-path Imports
Each strategy is tree-shakeable via sub-path imports:
| Import Path | Contents |
| -------------------------------------------------------------- | ---------------------------------------------------------- |
| `@breadstone/archipel-platform-blob-storage` | Core module, service, ports, interfaces, abstract base |
| `@breadstone/archipel-platform-blob-storage/strategies/vercel` | VercelBlobStorageStrategy, VERCEL_CONFIG_ENTRIES |
| `@breadstone/archipel-platform-blob-storage/strategies/azure` | AzureBlobStorageStrategy, AZURE_CONFIG_ENTRIES |
| `@breadstone/archipel-platform-blob-storage/strategies/aws-s3` | AwsS3BlobStorageStrategy, AWS_S3_CONFIG_ENTRIES |
| `@breadstone/archipel-platform-blob-storage/strategies/local` | LocalBlobStorageStrategy, LOCAL_CONFIG_ENTRIES |
| `@breadstone/archipel-platform-blob-storage/strategies/empty` | EmptyBlobStorageStrategy (mock/logging) |
| `@breadstone/archipel-platform-blob-storage/health` | BlobHealthIndicator |
---
## Exports Summary
| Export | Type | Description |
| ----------------------------- | ------------- | ------------------------------------------------------ |
| `BlobModule` | NestJS Module | Main module with `register()` |
| `BlobService` | Service | Upload, download, delete, signed URLs |
| `BlobStorageStrategyBase` | Abstract | Base class for custom storage strategies |
| `BlobObjectPersistencePort` | Port | Object metadata tracking |
| `BlobVariantPersistencePort` | Port | Variant metadata tracking |
| `VercelBlobStorageStrategy` | Strategy | Vercel Blob implementation (sub-path) |
| `AzureBlobStorageStrategy` | Strategy | Azure Blob Storage implementation (sub-path) |
| `AwsS3BlobStorageStrategy` | Strategy | AWS S3 implementation (sub-path) |
| `LocalBlobStorageStrategy` | Strategy | Local filesystem implementation (sub-path) |
| `EmptyBlobStorageStrategy` | Strategy | Mock/logging implementation (sub-path) |
| `BlobHealthIndicator` | Health | Blob storage health check (`/health` subpath) |
| `BlobDownloadResponseTypes` | Constant | `'arraybuffer'` \| `'json'` \| `'stream'` \| `'text'` |