A unified TypeScript SDK for storage with first-class support for snapshotting, forking across many storage providers.
npm install @storagesdk/core @storagesdk/adaptersSwitch providers by changing the import. The rest of your code doesn't move.
import { Storage } from '@storagesdk/core';
import { tigris } from '@storagesdk/adapters/tigris';
const storage = new Storage({
adapter: tigris({
bucket: 'agent-runs',
accessKeyId: process.env.TIGRIS_ACCESS_KEY_ID,
secretAccessKey: process.env.TIGRIS_SECRET_ACCESS_KEY,
}),
});
await storage.upload('hello.txt', 'Hello, storage SDK!', {
contentType: 'text/plain',
});
const text = await storage.download('hello.txt', { as: 'text' });Cross-provider storage SDKs stop at upload, download, list, delete. We don't. Snapshot and fork sit in the core surface — native on Tigris, emulated as sibling buckets everywhere else. Same call site either way.
Point-in-time snapshot of a bucket. Live writes keep going on the parent; the snapshot stays exactly as it was, readable through the same Storage API.
await storage.upload('photo.jpg', 'before');
const snap = await storage.snapshots.create({ name: 'baseline' });
await storage.upload('photo.jpg', 'after');
const reader = storage.snapshots.get(snap.id);
await reader.download('photo.jpg', { as: 'text' }); // 'before'
await storage.download('photo.jpg', { as: 'text' }); // 'after'Fork from a snapshot or from a bucket's live state. Every write lands in the fork's namespace; the parent is untouched. Throw it away when you're done.
const snap = await storage.snapshots.create();
await storage.forks.create({ name: 'experiment', fromSnapshot: snap.id });
const fork = storage.forks.get('experiment');
await fork.upload('config.json', JSON.stringify({ flag: true }));
// Parent bucket is untouched. The fork has its own writable view.
const liveValue = await storage.download('config.json', { as: 'text' });
// → whatever it was; the fork didn't write here.Methods named after what you want, not what the provider calls it. download is overloaded by destination type — text, bytes, stream, or the full StorageItem. Web-standard streams in and out, AbortSignal everywhere, typed StorageError codes.
await storage.upload('report.pdf', body, { contentType: 'application/pdf' });
const item = await storage.download('report.pdf'); // StorageItem
const text = await storage.download('report.pdf', { as: 'text' });
const bytes = await storage.download('report.pdf', { as: 'bytes' });
await storage.head('report.pdf');
await storage.list({ prefix: 'reports/' });
await storage.copy('a.png', 'b.png');
await storage.move('tmp/x.png', 'img/x.png');
await storage.delete('old.pdf');The same shape on every adapter — with the modern primitives you expect.
Third-party adapters are first-class. Implement the contract, drop in the conformance test suite, ship a package. Use the adapter kit's helpers to emulate snapshots and forks via sibling buckets, or wire up your provider's native API directly.
import { defineAdapter, type Adapter } from '@storagesdk/core/adapter';
export function myAdapter(config: MyConfig): Adapter {
return defineAdapter({
name: 'my-backend',
raw: /* your underlying client */,
async upload(path, body, opts) { /* ... */ },
async download(path, opts) { /* ... */ },
async head(path, opts) { /* ... */ },
async list(opts) { /* ... */ },
async delete(path, opts) { /* ... */ },
async copy(from, to, opts) { /* ... */ },
async move(from, to, opts) { /* ... */ },
async url(path, opts) { /* ... */ },
async uploadUrl(path, opts) { /* ... */ },
snapshots: { /* create, list, head, delete, get */ },
forks: { /* create, list, head, delete, get */ },
});
}Install core plus the adapters bundle, pick a provider, construct a Storage, and call it. The local filesystem adapter has no peer deps — run it in tests today.
@storagesdk/core is the runtime; @storagesdk/adapters ships the first-party providers. Each native SDK is an optional peer dependency.
npm install @storagesdk/core @storagesdk/adaptersThe filesystem adapter is the fastest way to try the surface — no credentials, no network. Swap the import to switch providers.
import { Storage } from '@storagesdk/core';
import { fs } from '@storagesdk/adapters/fs';
const storage = new Storage({
adapter: fs({ root: './.storage', folder: 'agent-runs' }),
});
await storage.upload('hello.txt', 'Hello, storage SDK!');
const text = await storage.download('hello.txt', { as: 'text' });Open source, Apache 2.0, ESM-only, Node 20+. Built by the Tigris team — for everyone.