v0.xMulti-provider · Apache 2.0 · Node 20+

Universal API for fork

A unified TypeScript SDK for storage with first-class support for snapshotting, forking across many storage providers.

npm install @storagesdk/core @storagesdk/adapters
one api

Same API, different provider.

Switch providers by changing the import. The rest of your code doesn't move.

storage.ts
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' });
More adapters

Two primitives no other SDK has.

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.

Built for agents
Forks are sandboxes for your agents. Branch a bucket per run; let the agent upload, mutate, and delete freely; merge or throw the fork away when it's done. Snapshots make every run reproducible — start the next agent from the same frozen state.
snapshots

Freeze a bucket. Read it forever.

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.

snapshots.ts
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'
forks

Branch a bucket. Mutate without fear.

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.

forks.ts
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.
Native on Tigris and GitHub. Emulated as sibling buckets on S3, R2, GCS and others.
api

One verb per intent.

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.

ops.ts
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');
Full API reference
features

More than the CRUD basics.

The same shape on every adapter — with the modern primitives you expect.

extensible

Write your own adapter.

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.

my-adapter.ts
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 */ },
  });
}
View all adaptersRequest an adapter
install

From zero to upload in a minute.

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.

01

Install the packages

@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/adapters
02

Make your first call

The filesystem adapter is the fastest way to try the surface — no credentials, no network. Swap the import to switch providers.

index.ts
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' });

One SDK. Snapshots and forks on every provider.

Open source, Apache 2.0, ESM-only, Node 20+. Built by the Tigris team — for everyone.