@zero-transfer/sdk
v0.4.7
Published
Unified TypeScript file transfer SDK for Node.js
Downloads
2,129
Maintainers
Readme
ZeroTransfer is a unified, TypeScript-first file transfer SDK for Node.js. One typed API speaks to every backend you actually deploy against - classic protocols, web endpoints, object storage, cloud drives, and local disks - with streaming, resume, verification, dry-run plans, MFT-style scheduling, audit logs, and webhook delivery built in.
import { createS3ProviderFactory, createTransferClient, uploadFile } from "@zero-transfer/sdk";
const client = createTransferClient({
providers: [createS3ProviderFactory({ region: "us-east-1" })],
});
// One call, any provider you registered above.
await uploadFile({
client,
localPath: "./dist/app.tar.gz",
destination: {
path: "/lake/bronze/app.tar.gz",
profile: {
provider: "s3",
host: "data-lake-bronze",
username: { env: "AWS_ACCESS_KEY_ID" },
password: { env: "AWS_SECRET_ACCESS_KEY" },
},
},
});Why ZeroTransfer
- One API, every provider. Replace bespoke FTP, SFTP, S3, and cloud-drive code with a single
TransferClientand provider-neutral sessions. - TypeScript-first. Strict types, exact optional properties, exhaustive capability discovery, and typed errors for every protocol failure mode.
- Streaming + resume. Backpressure via
stream.pipeline, byte-range downloads, multipart uploads, and cross-process resume stores for object storage. - Dry-run-first sync. Diff remote trees, generate
TransferPlans, and review every step before any byte moves. - MFT batteries. Routes, cron + interval schedules, audit logs, HMAC-signed webhooks, retention policies, and approval gates that block on human sign-off.
- Security by default. Profile redaction in every log, host-key pinning, certificate fingerprint pinning, OAuth refresh, and SecretSource adapters for vaults / env / files / commands.
- Observable. Structured logger, redaction-safe diagnostics, immutable transfer receipts, and per-attempt history for compliance.
Install
# Batteries-included SDK (every provider):
npm install @zero-transfer/sdk
# Or pick a scoped package with a narrowed export surface:
npm install @zero-transfer/sftp
npm install @zero-transfer/s3
npm install @zero-transfer/mftRequires Node.js >=20.
Scoped packages
ZeroTransfer publishes 14 scoped packages under the @zero-transfer npm organization. @zero-transfer/sdk is the batteries-included distribution; the other 13 are narrowly scoped packages that publish only the symbols listed in their scope page. Pick one to keep your dependency tree tight, or install the SDK if you want every provider in one go.
Every protocol-scoped package (everything except @zero-transfer/core itself) automatically pulls in @zero-transfer/core as a transitive dependency and re-exports the full core surface (createTransferClient, uploadFile, downloadFile, profiles, errors, sync planner, …). A single import { … } from "@zero-transfer/<scope>" is all you need - no separate @zero-transfer/core install. If your app uses multiple protocols, install the umbrella @zero-transfer/sdk instead of multiple scoped packages.
| Package | Summary | Docs |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------- |
| @zero-transfer/sdk | Batteries-included distribution. Every provider, every helper. | API reference |
| @zero-transfer/core | Provider-neutral contracts, transfer engine, queue, profiles, errors. | Scope page |
| @zero-transfer/classic | FTP + FTPS + SFTP in one install. | Scope page |
| @zero-transfer/ftp | Classic FTP with EPSV/PASV streaming and REST resume. | Scope page |
| @zero-transfer/ftps | Explicit + implicit FTPS with full TLS profile support. | Scope page |
| @zero-transfer/sftp | SFTP with SSH key auth, known_hosts, and jump-host support. | Scope page |
| @zero-transfer/ssh | Standalone SSH 2.0 transport, auth, and channel primitives (exec/subsystem). | Scope page |
| @zero-transfer/http | HTTP(S) and signed-URL provider with ranged downloads. | Scope page |
| @zero-transfer/webdav | WebDAV with PROPFIND listings and ranged downloads. | Scope page |
| @zero-transfer/s3 | S3-compatible storage with SigV4, multipart upload, and cross-process resume. | Scope page |
| @zero-transfer/google-drive | Google Drive with OAuth, folder paths, md5 checksums. | Scope page |
| @zero-transfer/dropbox | Dropbox with content-hash verification. | Scope page |
| @zero-transfer/azure-blob | Azure Blob Storage with SAS or AAD bearer auth. | Scope page |
| @zero-transfer/mft | Routes, schedules, audit logs, webhooks, approval gates. | Scope page |
The full per-scope index lives at docs/scopes/.
Quick start
1. Connect a provider-neutral client
import { createSftpProviderFactory, createTransferClient } from "@zero-transfer/sdk";
const client = createTransferClient({
providers: [createSftpProviderFactory()],
});
const session = await client.connect({
provider: "sftp",
host: "files.example.com",
username: { env: "ZT_USER" },
password: { env: "ZT_PASSWORD" },
ssh: {
knownHosts: { path: "./known_hosts" },
pinnedHostKeySha256: "SHA256:base64-encoded-host-key-digest",
},
});
const releases = await session.fs.list("/releases");
await session.disconnect();2. Move a file with one call
import { uploadFile, type ConnectionProfile } from "@zero-transfer/sdk";
const sftpProfile: ConnectionProfile = {
host: "files.example.com",
provider: "sftp",
username: { env: "ZT_USER" },
ssh: {
privateKey: { path: "./keys/id_ed25519" },
pinnedHostKeySha256: "SHA256:base64-encoded-host-key-digest",
},
};
await uploadFile({
client,
localPath: "./dist/app.tar.gz",
destination: { path: "/releases/2026.04.28/app.tar.gz", profile: sftpProfile },
onProgress: (event) => console.log(`${event.bytesTransferred}/${event.totalBytes ?? "?"}`),
});The
profileshape is the same provider-neutralConnectionProfileused byclient.connect(). See Connection profiles below for the full field reference and security guidance.
3. Plan a sync without touching bytes
import { createSyncPlan, diffRemoteTrees, summarizeTransferPlan } from "@zero-transfer/sdk";
const diff = await diffRemoteTrees(srcSession.fs, "/dist", dstSession.fs, "/releases/current");
const plan = createSyncPlan({
id: "release-sync",
diff,
source: { provider: "sftp", rootPath: "/dist" },
destination: { provider: "s3", rootPath: "/releases/current" },
deletePolicy: "mirror",
});
console.table(summarizeTransferPlan(plan));4. Schedule it as an MFT route with audit + approval
import {
ApprovalRegistry,
MftScheduler,
RouteRegistry,
ScheduleRegistry,
createApprovalGate,
runRoute,
} from "@zero-transfer/sdk";
const approvals = new ApprovalRegistry();
const scheduler = new MftScheduler({
client,
routes: new RouteRegistry([route]),
schedules: scheduleRegistry,
runner: createApprovalGate({
approvalId: ({ route }) => `release:${route.id}:${Date.now()}`,
registry: approvals,
runner: ({ client: c, route: r, signal }) => runRoute({ client: c, route: r, signal }),
}),
onResult: ({ receipt }) => console.log(`Released ${receipt.jobId}`),
});
scheduler.start();Connection profiles
Every operation that touches a remote system takes a ConnectionProfile. Profiles are provider-neutral data - you build one once and pass it to client.connect(), uploadFile(), downloadFile(), copyBetween(), MFT routes, and diagnostics. The same shape works for every provider; only the optional auth blocks (ssh, tls, oauth, s3, …) change.
Required fields
| Field | Type | Notes |
| ---------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| host | string | Remote hostname / IP / bucket / drive identifier (provider-specific). Always required. |
| provider | ProviderId | One of "ftp", "ftps", "sftp", "http", "https", "webdav", "s3", "azure-blob", "gcs", "google-drive", "dropbox", "one-drive", "local", "memory", or any custom id you registered. |
Optional top-level fields
| Field | Type | Notes |
| ----------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| port | number | Provider applies a sensible default when omitted. |
| username | SecretSource | String, { env }, { path }, { base64Env }, { value }, or callback. |
| password | SecretSource | Same shapes as username. Used as bearer token for cloud providers. |
| secure | boolean | Request encrypted transport when the protocol allows opt-in TLS. |
| tls | TlsProfile | CA bundle, mTLS cert/key, fingerprint pinning, min/max TLS version. |
| ssh | SshProfile | Private key, passphrase, known_hosts, host-key pin, agent, algorithms. |
| timeoutMs | number | Connection / operation timeout. |
| signal | AbortSignal | Cancels connection setup and long-running operations. |
| logger | ZeroTransferLogger | Per-profile structured logger override (still redaction-safe). |
Secret-bearing fields use SecretSource
Every credential field (username, password, tls.ca, tls.key, ssh.privateKey, ssh.knownHosts, ssh.passphrase, …) accepts a SecretSource. Inline strings work for prototypes, but production code should pull from the environment, a file, or a callback so secrets stay out of source control and out of process memory dumps.
// Inline string - fine for tests, avoid in production.
password: "hunter2";
// Read from an environment variable.
password: {
env: "SFTP_PASSWORD";
}
// Read from a file (e.g. a Docker / Kubernetes secret mount).
privateKey: {
path: "/run/secrets/sftp_id_ed25519";
}
// Read base64-encoded binary from an environment variable.
ca: {
base64Env: "FTPS_CA_BUNDLE_B64";
}
// Pull from your vault / credential broker on demand.
password: async () => await vault.read("kv/sftp/deploy");Profiles are run through redactConnectionProfile() before any log line is emitted, so secret values never appear in logs, audit entries, or diagnostics.
Worked examples
// SFTP with public-key auth + host-key pin (production-hardened)
const sftpProfile: ConnectionProfile = {
host: "sftp.example.com",
provider: "sftp",
username: "deploy",
ssh: {
privateKey: { path: "./keys/id_ed25519" },
pinnedHostKeySha256: "SHA256:abc123basesixfourpinFromKnownHosts=",
},
};
// FTPS with mTLS + private CA bundle
const ftpsProfile: ConnectionProfile = {
host: "ftps.internal.example",
provider: "ftps",
username: "audit",
tls: {
ca: { path: "./certs/ca-bundle.pem" },
cert: { path: "./certs/client.crt" },
key: { path: "./certs/client.key" },
pinnedFingerprint256:
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
},
};
// S3-compatible bucket
const s3Profile: ConnectionProfile = {
host: "data-lake-bronze",
provider: "s3",
username: { env: "AWS_ACCESS_KEY_ID" },
password: { env: "AWS_SECRET_ACCESS_KEY" },
};
// Cloud drive (OAuth bearer token in `password`)
const dropboxProfile: ConnectionProfile = {
host: "",
provider: "dropbox",
password: { env: "DROPBOX_ACCESS_TOKEN" },
};Security guidance
- Pin host keys for SSH/SFTP. Without
ssh.knownHostsorssh.pinnedHostKeySha256the SSH session accepts any key the server presents - a MITM risk. - Pin TLS fingerprints when you control the server.
tls.pinnedFingerprint256is defence-in-depth on top ofrejectUnauthorized: trueand a CA bundle. - Never set
tls.rejectUnauthorized: falsein production. Pair self-signed servers withtls.cainstead. - Prefer
{ env },{ path }, or callback secrets over inline strings or hard-coded values. - See
examples/sftp-private-key.ts,examples/ftps-client-certificate.ts, andexamples/profile-from-env.tsfor end-to-end hardened profile builds.
Full per-field reference: ConnectionProfile, SshProfile, TlsProfile, SecretSource.
Capability matrix
Every provider advertises its own CapabilitySet. The full programmatic matrix is exposed via getBuiltinCapabilityMatrix() and renders to Markdown via formatCapabilityMatrixMarkdown().
| Provider | Streaming | Resume | Server-side copy | Multipart upload | Checksum exposed | | ------------- | :-------: | :------------------------------: | :--------------: | :--------------: | :----------------------: | | FTP | ✅ | ⬆/⬇ via REST | - | - | - | | FTPS | ✅ | ⬆/⬇ via REST | - | - | - | | SFTP | ✅ | ⬆/⬇ | rename | - | - | | HTTP(S) | ✅ (read) | ⬇ via Range | - | - | ETag | | WebDAV | ✅ | ⬇ via Range | COPY | - | ETag | | S3-compatible | ✅ | ⬆ via multipart resume / ⬇ Range | CopyObject | ✅ | SHA-256 / md5 | | Azure Blob | ✅ | ⬇ via Range | - | ✅ | md5 | | GCS | ✅ | ⬇ via Range | - | ✅ | crc32c / md5 | | Google Drive | ✅ | ⬇ via Range | - | - | md5 | | Dropbox | ✅ | ⬇ via Range | - | - | content_hash | | OneDrive | ✅ | ⬇ via Range | - | ✅ | sha256 / sha1 / quickXor | | Local | ✅ | ⬆/⬇ | - | - | - | | Memory | ✅ | ⬆/⬇ | - | - | - |
Examples
Real-world examples live in examples/. Run them with tsx examples/<file>.
| Example | What it shows |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| local-copy-file.ts | Zero-config local-to-local copy via copyBetween. |
| ftp-basic.ts | Plain FTP upload + download round-trip with username/password. |
| ftp-directory-ops.ts | FTP session.fs: list, stat, mkdir, rename, remove, rmdir. |
| ftps-basic.ts | FTPS with username/password over a public-CA endpoint. |
| ftps-client-certificate.ts | FTPS hardened: mTLS + private CA bundle + fingerprint pinning. |
| ftps-directory-ops.ts | FTPS session.fs: list, stat, mkdir, rename, remove, rmdir. |
| sftp-basic.ts | Minimal SFTP with username/password (no host-key pinning). |
| sftp-private-key.ts | SFTP hardened: private-key auth + pinned host-key SHA-256. |
| sftp-directory-ops.ts | SFTP session.fs: list, stat, mkdir, rename, remove, rmdir. |
| ssh-exec-command.ts | Standalone SSH stack: handshake, auth, run a remote command. |
| s3-compatible-upload.ts | S3 multipart upload with cross-process resume store. |
| webdav-sync.ts | WebDAV diff + sync plan with deterministic ordering. |
| signed-url-download.ts | HTTPS signed-URL download with progress reporting. |
| transfer-queue.ts | Concurrent transfers with TransferQueue + executor. |
| dry-run-sync.ts | Plan a sync, print a summary, never touch bytes. |
| mft-route.ts | SFTP→S3 cron-scheduled MFT route with audit hooks. |
| profile-from-env.ts | Build a ConnectionProfile from env / file / base64-env secrets. |
| diagnose-connection.ts | Provider summary + redaction-safe connection probe. |
| approval-gated-route.ts | Two-person rule: scheduled route blocks until approval lands. |
| multi-cloud-orchestration.ts | Fan-out SFTP → S3 + Azure + Local with webhook audit. |
| atomic-deploy-with-rollback.ts | Blue/green-style deploy plan with rollback path. |
Documentation
- Full API reference (HTML) - TypeDoc HTML site, deployed from
mainon every push. - Full API reference (Markdown) - every public symbol with parameter / property / type tables.
- Per-scope pages - one page per
@zero-transfer/*package. - Examples directory - runnable real-world flows.
Regenerate everything locally:
npm run docs:all # HTML + Markdown api refs + per-scope pages + per-package READMEsProject status
ZeroTransfer is in alpha under the alpha npm dist-tag. The provider-neutral foundation, transfer engine, queue, sync planner, atomic deploy planner, MFT layer, friendly client surface, and diagnostics module are stable. Multipart / resumable upload sessions are now wired up across S3, Azure Blob, GCS, and OneDrive. Phase work in progress: broader real-server compatibility coverage and the push to higher coverage targets.
Contributing
git clone https://github.com/tonywied17/zero-transfer.git
cd zero-transfer
npm install
npm run ci # lint, format check, typecheck, tests with coverage, build, pack dry-run
npm run test:watch # iterateIssues and PRs welcome. Provider integration tests are gated behind opt-in env vars - see test/integration/ for the full list.
License
MIT © Tony Wiedman
