Skip to main content
Modules sometimes need to put bytes somewhere: an uploaded document, a rendered invoice PDF, a media asset. Voyant handles that with the @voyant-travel/storage package, a small provider abstraction that gives portable code one contract and lets the deployment choose the backend. This page covers the platform storage provider interface and its built-in backends. Voyant binds this in-process abstraction to managed storage for customer deployments.

One provider contract

Everything in the package targets a single interface, StorageProvider, from @voyant-travel/storage/types:
The contract is deliberately narrow: upload bytes, delete by key, mint a time-limited URL, fetch bytes back. StorageUploadBody accepts an ArrayBuffer, a Uint8Array, or a Blob. upload returns a StorageObject carrying the object key and a public url (an empty string when the object is private and only reachable through signedUrl). get returns null when the key is absent. Portable module code targets this interface. The backend is a deployment choice, not a fork of the module.

The service wrapper

Most deployments use exactly one storage backend, so createStorageService(provider) from @voyant-travel/storage/service wraps a single provider as a named StorageService:
The service exposes the same upload / delete / signedUrl / get surface as the provider, plus a provider reference. It is a thin convenience, not an orchestration layer.

Built-in backends

The package ships three providers. Each is created by a factory and satisfies the same StorageProvider contract.

Local

createLocalStorageProvider() keeps objects in an in-memory Map held in a closure. It is the right backend for unit tests and for running workflows locally without touching remote storage. Data does not survive the process.

R2

createS3CompatibleStorageProvider({ bucket }) binds to a Cloudflare R2 bucket binding (for example env.MEDIA_BUCKET). The binding handles authentication at the Worker runtime boundary, so no credentials live in this layer. R2 bindings do not mint signed URLs by themselves. The R2 provider adds a publicUrl(key) method for permanent public URLs (requires publicBaseUrl, a public custom domain or a Worker route that proxies the bucket). For time-limited access, configure a signer.
Calling signedUrl on the R2 provider without a configured signer throws. This is intentional: falling back to ${publicBaseUrl}${key} would return a permanent, unauthenticated URL while the caller believes it expires after expiresIn seconds. Use publicUrl(key) when a permanent URL is what you actually want.

S3 and S3-compatible

createS3CompatibleStorageProvider({ region, bucket, accessKeyId, secretAccessKey }) signs every request with AWS SigV4 using Web Crypto, so it needs no AWS SDK. It accepts an optional sessionToken for temporary credentials. For S3-compatible services (MinIO, Backblaze B2, DigitalOcean Spaces, Wasabi, or R2’s S3 API), set a custom endpoint. forcePathStyle defaults to true for the widest compatibility; set it to false for virtual-hosted-style URLs. signedUrl presigns a GET URL with SigV4. The SigV4 signing primitives, signRequest and presignUrl, are also exported directly from @voyant-travel/storage/lib/sigv4 for advanced use. They are verified against the AWS canonical test vectors.

Package exports

Platform storage and managed storage

The package is the in-process abstraction used by platform modules. Managed storage is the customer-facing service: Voyant runs its backend, provisioning, credentials, and access controls. Integration code uses the documented service surface and does not wire deployment credentials.

Next steps

Cloud storage

The managed storage service, the counterpart to this in-process abstraction.

Configuration

How a deployment chooses and wires its storage backend.

Services

Where module code calls into a storage provider.

Glossary

The shared vocabulary behind the storage model.