Extensions
Cloud File
Your domain talks to one agnostic storage contract, never to a cloud SDK. @stone-js/cloud-file supplies the cloud drivers, S3 (and every S3-compatible store: R2, MinIO, Spaces, OSS, COS), Google Cloud Storage and Azure Blob, and wires them into the container, so switching or mixing backends is configuration, not code.
#Install
The provider SDK is never bundled: install the module, then the SDK for the store you use.
npm i @stone-js/cloud-file
# then the SDK for your provider:
npm i @aws-sdk/client-s3 @aws-sdk/s3-request-presigner # S3 / R2 / MinIO / Spaces / OSS / COS
npm i @google-cloud/storage # Google Cloud Storage
npm i @azure/storage-blob # Azure Blob#Enable it
The principle
Code that depends on a concrete backend is welded to it. Depend on the capability, a file store, and the backend becomes a deployment choice.
In Stone.js
Every driver implements the same FileSystem contract from @stone-js/filesystem. The provider registers them lazily (the SDK is only constructed when a disk is first used) and injects the default disk as fileSystem.
import { StoneApp } from '@stone-js/core'
import { CloudFile } from '@stone-js/cloud-file'
@CloudFile({ driver: 's3', bucket: 'uploads', region: 'eu-west-3' })
@StoneApp({ name: 'app' })
export class Application {}import { defineConfig } from '@stone-js/core'
export const AppConfig = defineConfig({
filesystem: {
default: 's3',
disks: [
{ name: 's3', driver: 's3', bucket: 'uploads', region: 'eu-west-3' },
{ name: 'r2', driver: 's3', bucket: 'assets', endpoint: 'https://<acct>.r2.cloudflarestorage.com' }
]
}
})#Inject the store
The default disk is injected as fileSystem; the multi-disk manager as storage.
export class UploadService {
constructor (private readonly fileSystem) {} // the default disk
async save (path, bytes) {
await this.fileSystem.put(path, bytes) // same API on local, S3, R2, ...
return await this.fileSystem.url(path)
}
}#Signed URLs: upload straight to the cloud
Large files should not travel through your function. Issue a short-lived signed URL and let the client PUT the bytes straight to the bucket; your handler only ever sees metadata.
import { EventHandler, Post } from '@stone-js/router'
@EventHandler('/uploads')
export class UploadController {
constructor (private readonly fileSystem) {}
@Post('/sign')
async sign (event) {
// hand the client a short-lived PUT target
return await this.fileSystem.temporaryUploadUrl(`avatars/${event.get('id')}.png`, {
contentType: 'image/png',
expiresIn: 600
})
}
}For a private download, mint a temporary read URL: fileSystem.temporaryUrl(path, { expiresIn: 60 }). Feature-detect with supportsSignedUrls(disk) from @stone-js/filesystembefore calling, since the local driver has no signed URLs.
#Drivers
| driver | Type | Description |
|---|---|---|
| s3 | @aws-sdk/client-s3 | Amazon S3 + S3-compatible: Cloudflare R2, MinIO (forcePathStyle), DigitalOcean Spaces, Alibaba OSS, Tencent COS (set endpoint). |
| gcs | @google-cloud/storage | Google Cloud Storage. Signed v4 upload/download URLs. |
| azure | @azure/storage-blob | Azure Blob Storage. SAS upload/download URLs (needs accountName + accountKey). |
| local | built-in | From @stone-js/filesystem; the default disk. No signed URLs. |