Skip to content

molexxxx/zero-transfer

Repository files navigation

ZeroTransfer file transfer SDK for Node.js

One TypeScript SDK for moving files across every storage system you actually use.
FTP · FTPS · SFTP · HTTP(S) · WebDAV · S3-compatible · Azure Blob · GCS · Google Drive · Dropbox · OneDrive · Local

@zero-transfer/sdk npm version npm downloads CI Tests Coverage Docs License Node.js

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 {
  createDefaultRetryPolicy,
  createS3ProviderFactory,
  createTransferClient,
  uploadFile,
} from "@zero-transfer/sdk";

const client = createTransferClient({
  providers: [createS3ProviderFactory({ region: "us-east-1" })],
  defaults: {
    retry: createDefaultRetryPolicy(),
    timeout: { stallTimeoutMs: 30_000 },
  },
});

// 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 TransferClient and provider-neutral sessions.
  • TypeScript-first. Strict types, exact optional properties, exhaustive capability discovery, and typed errors for every protocol failure mode.
  • Checkpointed resume. Interrupted transfers pick up from the committed byte watermark - across retries, fresh calls, and process restarts. Checkpoints are keyed by source+destination path, fingerprint the source (size/mtime/etag) so a changed file never resumes onto stale bytes, and extend to whole batches: runResumableBatch() re-runs a plan and skips every step that already succeeded.
  • Built for throughput. Pipelined SFTP (64 in-flight requests x 32 KiB, OpenSSH parity) saturates high-latency links; S3 multipart and Azure staged blocks upload parts in parallel with progress that only ever reports the contiguous completed prefix.
  • Streaming everywhere. Backpressure via async iterables, byte-range downloads, multipart/staged/resumable-session uploads on every object store and cloud drive. Memory-bounded end to end - no whole-file buffering, and framing layers cap declared packet sizes.
  • Resilient by default. createDefaultRetryPolicy() retries only retryable failures with exponential backoff + full jitter and honors Retry-After hints; job-scope and attempt-scope timeouts plus a stall watchdog catch connections that go silent; every receipt records per-attempt history.
  • 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/mft

Requires 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 profile shape is the same provider-neutral ConnectionProfile used by client.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.knownHosts or ssh.pinnedHostKeySha256 the SSH session accepts any key the server presents - a MITM risk.
  • Pin TLS fingerprints when you control the server. tls.pinnedFingerprint256 is defence-in-depth on top of rejectUnauthorized: true and a CA bundle.
  • Never set tls.rejectUnauthorized: false in production. Pair self-signed servers with tls.ca instead.
  • Prefer { env }, { path }, or callback secrets over inline strings or hard-coded values.
  • See examples/sftp-private-key.ts, examples/ftps-client-certificate.ts, and examples/profile-from-env.ts for 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 ✅ parallel SHA-256 / md5
Azure Blob ⬆ via staged blocks / ⬇ Range - ✅ parallel 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 parallel multipart upload with resumable checkpoints.
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.
retry-and-timeouts.ts Retry policy, timeout scopes, stall watchdog, client defaults.
resume-checkpoints.ts Dropped transfer resuming from the checkpoint watermark.
resumable-batch.ts Crash-safe batch plan: re-runs skip already-completed steps.
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

Regenerate everything locally:

npm run docs:all      # HTML + Markdown api refs + per-scope pages + per-package READMEs

Project 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. The massive-file engine is in place: unified checkpoint/resume (cross-process, fingerprint-validated, batch-aware via runResumableBatch), pipelined SFTP transfers, parallel multipart uploads on S3 and Azure, and streaming upload sessions across every cloud drive (Dropbox, Google Drive, OneDrive, GCS), on top of the resilience layer (default retry policy with backoff + jitter, job/attempt timeout scopes, stall detection, redaction-safe error logging, memory-bounded streaming). Next up: verification engine, segmented parallel downloads, and proxy/HTTP2 support.

Contributing

git clone https://github.com/molexxxx/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  # iterate

Issues and PRs welcome. Provider integration tests are gated behind opt-in env vars - see test/integration/ for the full list.

License

MIT © Tony Wiedman

Contributors