Storage Adapters
The SHL SDK supports multiple storage backends. Choose based on your infrastructure:
Comparison
| Adapter | Use Case | Infra Required | Peer Dependency | Cost |
|---|---|---|---|---|
| FhirflyStorage | Zero-infra, just works | None | None | Included free |
| LocalStorage | Development, testing | Filesystem | None | — |
| S3Storage | AWS production | S3 bucket | @aws-sdk/client-s3 |
Your AWS bill |
| AzureStorage | Azure production | Blob container | @azure/storage-blob |
Your Azure bill |
| GCSStorage | GCP production | GCS bucket | @google-cloud/storage |
Your GCP bill |
FhirflyStorage (Recommended)
Store encrypted content on FHIRfly's hosted service. No infrastructure to manage. Included free in all plans.
import { SHL } from "@fhirfly-io/shl";
const storage = new SHL.FhirflyStorage({
apiKey: process.env.FHIRFLY_API_KEY,
// apiBaseUrl: "https://api.fhirfly.io", // default
});
FHIRfly stores only opaque encrypted blobs. The decryption key is embedded in the shlink:/ URL and never sent to FHIRfly. This means FHIRfly cannot decrypt your content.
Per-Plan Limits
| Plan | Active SHLs | Attachments/SHL | Per-file limit |
|---|---|---|---|
| Free | 25 | 10 | 1 MB |
| Developer | 500 | 10 | 1 MB |
| Pro | 5,000 | 10 | 1 MB |
| Business | 50,000 | 10 | 1 MB |
| Enterprise | Unlimited | 10 | 1 MB |
"Active" means non-revoked and non-expired. Revoke unused SHLs to free up quota. See Pricing for full plan details.
LocalStorage
Filesystem storage for local development and testing.
const storage = new SHL.LocalStorage({
directory: "./shl-data",
baseUrl: "http://localhost:3456/shl",
});
S3Storage
AWS S3 storage for production workloads.
npm install @aws-sdk/client-s3
const storage = new SHL.S3Storage({
bucket: "my-shl-bucket",
region: "us-east-1",
baseUrl: "https://shl.example.com",
prefix: "shl-data/", // optional
});
AzureStorage
Azure Blob Storage for Azure-based deployments.
npm install @azure/storage-blob
const storage = new SHL.AzureStorage({
container: "shl-data",
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
baseUrl: "https://shl.example.com",
prefix: "shl-data/", // optional
});
GCSStorage
Google Cloud Storage for GCP-based deployments.
npm install @google-cloud/storage
const storage = new SHL.GCSStorage({
bucket: "my-shl-bucket",
baseUrl: "https://shl.example.com",
prefix: "shl-data/", // optional
});
Storage Interface
All storage adapters implement the SHLStorage interface:
interface SHLStorage {
/** Base URL for manifest access */
readonly baseUrl: string;
/** Store a file (key = "{shlId}/{filename}") */
store(key: string, content: string | Uint8Array): Promise<void>;
/** Delete all files for an SHL (prefix = "{shlId}/") */
delete(prefix: string): Promise<void>;
}
You can implement this interface to create custom storage adapters for any backend.
Concurrency Note
When self-hosting with cloud storage (S3, Azure, GCS), the SDK's access counter uses a plain read-modify-write pattern without conditional writes or ETags. Under concurrent load, a few extra accesses may be allowed beyond maxAccesses. For strict one-time links, use FhirflyStorage (which uses atomic MongoDB $inc) or add your own atomic counter (e.g., Redis, DynamoDB). See the Server Guide for details.