Overview
Every adapter implements the same Adapter contract: upload, download, head, list, copy, move, delete, url, uploadUrl, plus the snapshots and forks namespaces. Pick one based on your provider; the call site doesn’t change.
Snapshots and forks are native on Tigris (and its branded aliases Fly.io and Railway) and on GitHub — the adapter calls the provider’s native APIs and no data is copied. On GitHub specifically, every snapshot is a git tag and every fork is a git branch. On every other provider they’re emulated as sibling buckets, and the adapter does the bookkeeping via a small .storagesdk.metadata.json manifest.
Each adapter’s provider SDK is an optional peer dependency. Install only the SDKs for the adapters you actually import.
Runtime adapter selection
@storagesdk/adapters’s root export ships a small registry for runtime-driven adapter selection. CLIs, scripts, and any code that picks a backend from a string at runtime use it; library code that knows its one adapter at build time keeps using the subpath import.
import {
ADAPTERS,
type AdapterName,
type AdapterEnvVar,
buildAdapter,
getAdapterEnvVars,
} from '@storagesdk/adapters';
Quick use
import { Storage } from '@storagesdk/core';
import { buildAdapter } from '@storagesdk/adapters';
const storage = new Storage({ adapter: await buildAdapter('tigris') });
// reads TIGRIS_BUCKET, TIGRIS_ACCESS_KEY_ID, TIGRIS_SECRET_ACCESS_KEY
// from process.env and dynamically imports just the tigris adapter
buildAdapter(name) does three things: dynamic-import the factory, read env vars into a config, call the factory. One async call at startup, fully-constructed adapter back.
Introspecting env vars
getAdapterEnvVars('tigris')
// → readonly [
// { name: 'TIGRIS_BUCKET', required: true },
// { name: 'TIGRIS_ACCESS_KEY_ID', required: true },
// { name: 'TIGRIS_SECRET_ACCESS_KEY', required: true },
// { name: 'TIGRIS_ENDPOINT', required: false },
// { name: 'TIGRIS_FORCE_PATH_STYLE', required: false },
// ]
interface AdapterEnvVar {
readonly name: string;
readonly required: boolean;
readonly fallback?: readonly string[];
}
fallback lists backend-native env vars that get consulted when the adapter-prefixed one isn’t set — S3_ACCESS_KEY_ID falls back to AWS_ACCESS_KEY_ID, GCS_PROJECT_ID to GOOGLE_CLOUD_PROJECT, VERCEL_BLOB_TOKEN to BLOB_READ_WRITE_TOKEN, and so on.
Iterate ADAPTERS to enumerate every adapter and its spec — useful for CLI help text, validation, or generating docs:
import { ADAPTERS, getAdapterEnvVars } from '@storagesdk/adapters';
for (const name of ADAPTERS) {
console.log(name, getAdapterEnvVars(name));
}
Each adapter’s dependencies load on demand
buildAdapter('tigris') is the only call that pulls @tigrisdata/storage into your runtime. The other 16 adapters’ peer-SDK packages — @aws-sdk/client-s3, @azure/storage-blob, @google-cloud/storage, @octokit/rest, and so on — are never imported.
That’s why buildAdapter is async: it import()s the requested adapter’s implementation lazily. Modern bundlers code-split each dynamic import into its own chunk; runtimes execute only the one that gets called.
ADAPTERS and getAdapterEnvVars are synchronous — they’re just static data.
One adapter, tree-shakeable
When you know your adapter at build time, import it directly:
import { tigris } from '@storagesdk/adapters/tigris';
const storage = new Storage({ adapter: tigris({ ... }) });
Only the one adapter and its peer SDK end up in your bundle — the registry adds nothing, the other 16 adapters tree-shake out.
Discovery example
Run the bundled discovery script to see every adapter with its env vars:
pnpm --filter @storagesdk/examples adapters
Source: examples/adapters/index.ts.