files-sdk@1.5.0
Minor Changes
-
c6b4df1:
upload,download,head, andexistsnow accept an array for bulk operations, mirroringdelete. Pass the usual single argument for the original behavior (resolves to one result, throws on failure); pass an array to operate on many in one call and get back a structured result instead of throwing on partial failure — so you can see exactly which keys succeeded and which failed:const up = await files.upload( [ { key: "avatars/a.png", body: a, contentType: "image/png" }, { key: "avatars/b.png", body: b }, ], { concurrency: 8, stopOnError: false } ); up.uploaded; // UploadResult[] — successes, in the order supplied up.errors; // undefined when every item succeeded const down = await files.download(["a.png", "b.png"]); // { downloaded, errors? } const meta = await files.head(["a.png", "b.png"]); // { files, errors? } const there = await files.exists(["a.png", "b.png"]); // { existing, missing, errors? }
upload's array items are flat — each carries its ownkey,body, and optionalcontentType/cacheControl/metadata. No provider exposes a native batch primitive for these operations, so the SDK always fans out to per-key calls with boundedconcurrency(default 8);stopOnError: false(default) attempts every item and collects per-key failures inerrors, whilestopOnError: truestops at the first failure. All array forms honor the client'sprefixand report the keys the caller passed, not the internal prefixed paths. Invalid keys are reported inerrorsrather than thrown.existssplits results intoexisting/missingand only routes hard errors (auth, transport) toerrors. ThefilesCLI'sheadandexistscommands and the MCPhead/existstools accept multiple keys too. -
ed72daf: Add a Convex storage adapter (
files-sdk/convex). Convex file storage is only reachable from inside a Convex function, so the adapter wraps the function context —convex({ ctx }), constructed per request inside an action, mutation, or query — and maps the unifiedAdaptersurface ontoctx.storage/ctx.db.system. Because Convex assigns the storage id (Id<"_storage">) and exposes no writable metadata, the storage id is the key:upload()returns the assigned id, anddownload/head/delete/urltake it back. Available operations follow Convex's context rules —upload/downloadneed an action,listneeds a query/mutation — and the adapter throws a descriptive error when a primitive is unavailable.copy, custommetadata, andcacheControlare unsupported;url()returns a permanent serving URL;signedUploadUrl()returns Convex's raw-body POST upload URL.convexis an optional peer dependency. -
bad4a80:
delete()now accepts an array of keys for bulk deletion. Pass a string to remove one object (resolves tovoid, throws on failure as before); pass an array to remove many in one call and get back a structured{ deleted, errors? }result instead of throwing on partial failure — so you can see exactly which keys failed:const result = await files.delete( ["avatars/a.png", "avatars/b.png", "avatars/c.png"], { concurrency: 8, stopOnError: false } ); result.deleted; // string[] — keys removed, in the order supplied result.errors; // undefined when every key succeeded
Adapters with a native bulk primitive use it — S3 sends
DeleteObjects(chunked into batches of 1000, the provider limit), Supabase usesremove(keys), and UploadThing usesdeleteFiles(keys)— while every other adapter fans out to single deletes with boundedconcurrency(default 8).stopOnError: false(default) attempts every key and collects per-key failures inerrors;stopOnError: truestops at the first failure. Invalid keys are reported inerrorsrather than thrown, and the array form honors the client'sprefixand is no-op friendly on providers that treat a missing key as success. ThefilesCLI'sdeletecommand and the MCPdeletetool accept multiple keys too. -
9e9fa13: Add FTP and SFTP adapters (
files-sdk/ftp,files-sdk/sftp) for on-prem and legacy file servers. Both expose the standard unified surface, so they're interchangeable with the cloud adapters:import { Files } from "files-sdk"; import { sftp } from "files-sdk/sftp"; const files = new Files({ adapter: sftp({ host: "files.example.com", username: process.env.SFTP_USERNAME!, privateKey: process.env.SFTP_PRIVATE_KEY!, root: "/uploads", }), }); await files.upload("reports/q1.csv", csv, { contentType: "text/csv" });
FTP uses
basic-ftp(with FTPS viasecure: true); SFTP usesssh2-sftp-client. Both are optional peer dependencies. These adapters are Node-only (raw sockets — no edge/browser/Workers support) and connect per operation by default; pass a pre-connectedclientto reuse one connection for batch work. Keys resolve under a configurablerootwith a..traversal guard,listwalks the tree recursively with cursor pagination, anddeleteManyreuses a single connection. These protocols store no MIME type (inferred from the file extension), no arbitrarymetadata/cacheControl(both throw), and serve no HTTP —url()requires apublicBaseUrlpointing at an HTTP server fronting the same tree, andsignedUploadUrl()throws.copyround-trips the bytes through the client since neither protocol has a portable server-side copy. -
1eb1dfc: Add a
files-sdk/providersexport: a zero-dependency catalog of every storage provider and the environment variables each one reads.PROVIDERSmaps each slug to its display name, description, optional peer dependencies, and a structured env spec —requiredvars, mutually exclusivecredentialModes(so Azure's connection-string-or-key-or-SAS choice is expressible),optionaltuning vars, and non-envconfig. Every variable is taggedsecretandreadBy("files-sdk"vs the underlying SDK's"sdk-chain", so AWS/GCS credential-chain vars aren't mislabeled as required). Helpers:getProvider,listEnvVars,getSecretEnvVars.PROVIDER_NAMESand theProvider/ProviderSlugtypes are also re-exported from the package root. Useful for sync engines, config UIs, and onboarding flows that need to enumerate providers and their required configuration up front.
Patch Changes
-
e80e922: Add
signal,timeout, andretriesto every operation. Set them on theFilesconstructor as defaults and override per call (a per-call value wins).retriesis a number or{ max, backoff }; onlyProviderfailures are retried —NotFound,Unauthorized,Conflict, aborts, and timeouts are returned immediately, andReadableStreamuploads are never retried because a consumed stream can't be replayed. The default backoff is exponential (100 * 2 ** (attempt - 1)ms, capped at 30s, no jitter); pass your ownbackoff({ attempt, error })for jitter or a different curve.timeoutis applied per attempt and aborts the operation rather than triggering a retry. Asignalalways fails fast at theFileslayer for every adapter; the underlying provider request is also cancelled on the S3 adapter and the S3-compatible catalog, Vercel Blob and UploadThing's fetch-backed reads, Azure, Google Drive, and PocketBase (across their operations), Supabase (downloadandlist— the only methods its SDK lets a signal through), and the fetch-backed downloads of Box, Cloudinary, and Dropbox. Adapters whose SDK exposes no cancellation (GCS, Firebase Storage, Netlify Blobs, Appwrite, Bunny, Bun S3, and the R2 binding path) still fail fast at theFileslayer but leave the in-flight request running. -
f774aa2: Add Azure AD / Managed Identity support to the Azure adapter via a
credential(TokenCredential) option. Token-authenticated adapters mint User Delegation SAS URLs forurl(),signedUploadUrl(), and same-containercopy(), so signed URLs keep working without a storage account key. SetuseUserDelegationSas: falseto opt out of SAS signing for token-only setups. -
dbda237: Add a
prefixoption to theFilesconstructor. When set, every key is resolved relative to the prefix - reads, writes, copies, listings, URLs, and signed uploads - and the prefix is stripped back off the keys (andname) returned in results, so your application code works in its own namespace:const users = new Files({ adapter: s3({ bucket: "uploads" }), prefix: "users", }); await users.upload("123/avatar.png", file); // writes users/123/avatar.png const stored = await users.head("123/avatar.png"); stored.key; // "123/avatar.png" - prefix stripped
Leading and trailing slashes on the prefix are normalized (
"/users/"and"users"behave identically), andlist()scopes the underlying query on a path boundary so aprefix: "users"instance never matches the siblingusers-archive/. -
d921741: Harden three internal regexes against polynomial ReDoS. The trailing-slash/
[. ]-stripping patterns innormalizePrefix(core, used by every adapter's prefix handling), thefsadapter's Windows trailing-noise check, and thebunny-storagekey parser each anchor with a(?<!…)lookbehind so the engine can't re-attempt the match at every character of a long trailing run (e.g."users////…"or"x.meta.json.... "). Behavior is unchanged; only the worst-case matching cost is fixed. -
ff39a2e: fs adapter: reject keys that resolve to a
.meta.jsonsidecar path — the adapter reserves that suffix for its per-object metadata sidecar, and accepting it as a regular key let a same-root caller silently overwrite, hide, or delete another key's sidecar (flipping the servedContent-Type, mutating arbitrarymetadatafields, or stripping the etag). The check runs on the resolved basename and folds case plus Windows trailing dots/spaces, so re-cased or normalized variants (x.META.JSON,x.meta.json.,x.meta.json/) that alias the sidecar on case-insensitive (APFS/NTFS) or Windows volumes are rejected too. -
979cd00: Add Vercel OIDC authentication to the Vercel Blob adapter (
files-sdk/vercel-blob). When the Blob store is connected to a Vercel project, the adapter now automatically picks upVERCEL_OIDC_TOKEN+BLOB_STORE_IDand uses OIDC instead of the long-livedBLOB_READ_WRITE_TOKEN— OIDC tokens rotate automatically, which removes the risk that a static secret leaks from your codebase or environment. Two new options,oidcTokenandstoreId, let you pass OIDC credentials explicitly for runtimes (e.g. Vite) that don't load.env.localintoprocess.env. Credential resolution mirrors the upstream SDK exactly: an explicittokenalways wins, then OIDC (option or env), thenBLOB_READ_WRITE_TOKEN. Theurl()fast path now usesstoreId(option orBLOB_STORE_IDenv) when present so OIDC users keep the no-round-trip behavior, andBLOB_STORE_IDis accepted in eitherstore_<id>or<id>form. Bumps the@vercel/blobpeer dep floor to^2.4.0, which is the first version that ships the OIDC options.