Skip to content
Files SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

files-sdk@2.6.2

Patch Changes

  • 3306ac0: files-sdk/client download() now reports a failure at the storage host, after the gateway redirected to a signed URL, with the code its HTTP status implies: 404 is NotFound, 401 and 403 are Unauthorized, and 409 and 412 are Conflict. Previously every such failure was a generic Provider error (“gateway responded 404”), so downloading a missing key through a signing adapter never surfaced NotFound.
  • 3306ac0: files-sdk/api now passes the request’s abort signal to every storage call it makes for url, the download redirect, signed-upload-url, presign, complete, and both upload PUTs. Previously only the read, list, delete, copy, and move calls and the proxied download received it, so the rest ran to completion after the client had disconnected, and an explicit-key upload could still store its object.
  • 3306ac0: files-sdk/api now refuses a search whose string pattern uses match: "regex" with 422 when the pattern doesn’t compile (invalid search regex) or could backtrack catastrophically (search pattern is too complex), the same as the isRegex form. Previously it failed later, inside the matcher, as a 500 Provider error.
  • 3306ac0: files-sdk/api now binds each keyless-upload token to the endpoint path that minted it, and the proxy upload and complete steps refuse a token presented at any other path. Previously the token was bound only to the endpoint’s query, so with two gateways sharing one secret (the FILES_API_SECRET default, as in the one-route-per-bucket setup), a token presigned at one route could be replayed against the other route’s ?op=proxy upload. That wrote bytes into the second route’s bucket, outside its keyPrefix, even though its authorize and operations never approved an upload.
  • 3306ac0: The default upload transport in files-sdk/client (and so in files-sdk/react, files-sdk/vue and files-sdk/svelte) now removes its abort listener from the caller’s signal once each XMLHttpRequest settles. Previously a long-lived signal shared across many uploads kept a listener, and with it every finished request and its response, alive until the signal was aborted or collected.
  • 97d5e1d: The files CLI now reports a malformed --config-json in the format the output flags ask for: plain text (error (Provider): …) under --no-json, with a stack trace under --verbose. Previously that error was raised before the output flags were read, so it always printed the JSON envelope.
  • 97d5e1d: loadFiles from files-sdk/loader (and the files CLI) now keeps the adapter’s original construction error as the cause of the FilesError it throws when it appends a provider’s configuration hint. Previously the hinted error dropped it.
  • 97d5e1d: The files mcp server’s download tool now enforces maxBytes on the bytes it actually reads, streaming the body and cancelling the transfer as soon as it passes the cap. Previously the cap was checked against head() and then the whole body was buffered before a second check, so an object replaced between the two calls (or a backend that misreports its size) could pull an arbitrarily large body into memory. The tool’s size field now reports the bytes returned.
  • 97d5e1d: The files CLI now passes --public-base-url to the box, dropbox, and bunny-storage providers, and --default-url-expires-in to box, dropbox, uploadthing, and vercel-blob. Previously these providers silently ignored the flags, so url() kept the adapter’s default expiry and Bunny Storage’s url() failed for want of an origin even when --public-base-url was given. The --region, --endpoint, and --public-base-url help text now names the providers that actually read each flag.
  • 97d5e1d: The files CLI now exits with code 2 on a usage error (an unknown flag, a missing required option or argument, an invalid choice). Previously these exited 1, the code the CLI documents for NotFound and for exists reporting a missing key, so a mistyped flag on files exists read as “missing”. --help and --version still exit 0.
  • 52f31bd: files-sdk/azure now maps failures when reading the body of a head() or list() result. For a blob deleted in between, text(), arrayBuffer(), and stream() now fail with a FilesError (NotFound) instead of the raw Azure RestError.
  • 52f31bd: files-sdk/azure’s signedUploadUrl() now throws on a positive minSize instead of silently ignoring it, because a SAS has no minimum-size constraint. minSize: 0 is still accepted.
  • 52f31bd: files-sdk/cloudinary now maps failures when reading the body of a head() or list() result. A transport error now fails text(), arrayBuffer(), and stream() with a FilesError instead of escaping raw.
  • 52f31bd: files-sdk/cloudinary resumable (control) uploads now classify a failed chunk by HTTP status, so a rejected signature (401) is an Unauthorized error that is not retried. Previously every failed chunk was a retryable Provider error.
  • 52f31bd: files-sdk/firebase-storage now maps failures when reading the body of a head() or list() result. For an object deleted in between, text(), arrayBuffer(), and stream() now fail with a FilesError (NotFound) instead of the raw ApiError.
  • 52f31bd: files-sdk/gcs and files-sdk/firebase-storage now report capabilities.signedUrl.maxExpiresIn as 604800 seconds (7 days). @google-cloud/storage rejects any V4 signed URL or POST policy that outlives 7 days, but the adapters left the cap unset, so the files-sdk/api gateway did not clamp to it and a longer expiresIn failed instead.
  • 52f31bd: files-sdk/gcs now maps failures when reading the body of a head() or list() result. For an object deleted in between, text(), arrayBuffer(), and stream() now fail with a FilesError (NotFound) instead of the raw ApiError.
  • 52f31bd: files-sdk/netlify-blobs now maps failures when reading the body of a head() or list() result. A store error, such as a rejected token, now fails text(), arrayBuffer(), and stream() with a FilesError instead of the raw BlobsInternalError.
  • 52f31bd: Resuming a part-based upload (upload({ control: UploadControl.from(token) })) now ignores any already-uploaded part the provider reports past the body’s last part, or at a size the body’s slicing can’t produce, and re-uploads that part where the body has one. files-sdk/azure lists every uncommitted block on the blob, including blocks an abandoned upload to the same key left behind, and those were committed into the resumed object, splicing stale bytes into it.
  • 52f31bd: files-sdk/supabase resumable (control) uploads now classify TUS failures by HTTP status: 401/403 are Unauthorized, an expired or terminated upload (404/410) is NotFound, and an offset mismatch (409) is Conflict, none of which are retried. Previously every failure was a retryable Provider error, so a rejected key or an expired session was re-sent chunk by chunk before failing with the wrong code.
  • 52f31bd: files-sdk/supabase’s signedUploadUrl() now throws on a positive minSize instead of silently ignoring it, because Supabase signed upload URLs have no minimum-size constraint. minSize: 0 is still accepted.
  • 52f31bd: files-sdk/uploadthing now maps failures when reading the body of a head() or list() result. A transport error, or a signing error for a private file, now fails text(), arrayBuffer(), and stream() with a FilesError instead of escaping raw.
  • 52f31bd: files-sdk/uploadthing with acl: "private" now reports the 7-day capabilities.signedUrl.maxExpiresIn cap that generateSignedURL enforces, so the files-sdk/api gateway clamps a longer requested expiry instead of failing the call.
  • 52f31bd: files-sdk/vercel-blob now maps failures when reading the body of a head() or list() result. A blob.get error in private mode, or a transport error in public mode, now fails text(), arrayBuffer(), and stream() with a FilesError instead of escaping raw.
  • 52f31bd: files-sdk/vercel-blob resumable uploads now slice a resumed upload on the part size pinned in the UploadControl token, as the S3 and Azure adapters do. A resume with a different (or default) multipart.partSize used the new size, which misaligned with the parts the token already held.
  • 3dd7d7e: Corrected editor docs across files-sdk: list({ delimiter }) now lists Bun’s S3 as supported (it no longer throws), upload({ multipart }) notes that the S3 fetch client throws rather than ignoring it, delete(keys, { concurrency }) names every adapter with a native bulk delete and explains that a wrapping plugin makes the per-key fan-out (and so concurrency) apply everywhere, move() lists every adapter with a native rename, capabilities.signedUrl names the fs adapter’s urlBaseUrl option correctly, and files-sdk/zip states its real limit of 65,534 entries (65,535 is the ZIP64 marker the writer refuses).
  • 3dd7d7e: Content types the SDK infers from a key’s extension (S3-family list() items, FTP, SFTP, WebDAV, Dropbox, Box, files-sdk/content-type, files-sdk/validation, files-sdk/zip’s unzip(), and the CLI) now fall back to application/octet-stream for a root-level key with no extension or a root dotfile. Previously a bare key named after an extension, such as json or html, or a dotfile such as .html, was typed as application/json or text/html, while the same name inside a folder was not.
  • 3dd7d7e: files-sdk/webdav, files-sdk/ftp, and files-sdk/sftp now reject a key that resolves to the configured root itself ("/", ".", "./") before any server call. Previously such a key addressed the root directory: on WebDAV, files.delete("/") sent a DELETE for the root collection, which removes everything under it. Their key-validation errors (traversal, null bytes, the reserved .fls-part suffix) are now also marked permanent, so retries no longer re-sends a call that can only fail the same way.
  • 3dd7d7e: files-sdk now keeps upload({ onProgress }) fire-and-forget on adapters that report progress themselves (S3 and the S3-compatible adapters, Azure, GCS, Firebase Storage, Vercel Blob, FTP). Previously a throwing onProgress on those adapters rejected the upload attempt, and with retries set the SDK re-uploaded the whole body on each retry before failing.
  • 3dd7d7e: files-sdk/zip’s unzip() now reports an entry that inflates past its declared size as a corrupt archive. Previously it blamed “the configured unzip size limit”, even though the entry was far under maxEntrySize and the archive’s own header was what lied.
  • 3dd7d7e: files-sdk/zip’s unzip() now rejects an archive that lists two entries with the same path, as its docs promise. Previously it extracted both, so the second silently overwrote the first.
  • fd236a0: files-sdk/box now classifies Box API errors by their HTTP status again: 404 maps to NotFound, 401/403 to Unauthorized, and 409/412 to Conflict. The Box SDK’s ESM build, which is what an import loads, drops responseInfo from every BoxApiError, so each one surfaced as a retryable Provider error. The status is now read from the error message the SDK formats. Where responseInfo is intact, the adapter now reads the raw Box error code from the response body, since the copy in responseInfo.code is JSON-quoted and never matched. A rejected OAuth grant (invalid_grant, invalid_client) now maps to Unauthorized.
  • fd236a0: The defaultUrlExpiresIn JSDoc in files-sdk/dropbox now says that a default above 14400 seconds is capped to Dropbox’s 4-hour link lifetime. It used to say such values throw, but only a per-call expiresIn above 14400 throws.
  • fd236a0: files-sdk/dropbox now lists the folder a prefix points into when list() is called with a delimiter. The whole prefix used to be read as a folder path, so a prefix without a trailing / came back wrong: photos/20 listed nothing instead of photos/2023/ and photos/2024/, and photos/2024 returned that folder’s children instead of the common prefix photos/2024/. The part of the prefix up to its last / now picks the folder and the whole prefix filters its children, matching the other adapters.
  • fd236a0: files-sdk/dropbox now treats a throttled (429) or failing (5xx) token endpoint during a refresh-token exchange as a retryable Provider error. It used to throw Unauthorized, so a transient outage of Dropbox’s OAuth endpoint was never retried. A rejected refresh token still maps to Unauthorized.
  • fd236a0: files-sdk/google-drive now maps a rejected OAuth grant (a revoked refresh token, a bad or deleted service-account key, reported as invalid_grant / invalid_client) to Unauthorized instead of a retryable Provider error. A failed resumable-session initiation, for signedUploadUrl() or a resumable upload(), is now classified like any other Drive error. A 401 maps to Unauthorized, a missing file to NotFound, and a rate-limited 403 stays a retryable Provider error. Resumable uploads used to report every initiation failure as Provider, and signedUploadUrl() treated a rate-limited 403 as Unauthorized.
  • fd236a0: files-sdk/google-drive built with publicByDefault now grants the anyone, reader permission in url() before returning the link. Only a plain upload() used to grant it, so after copy(), a resumable upload, or a client upload through signedUploadUrl(), url() returned a link that asked for a Google sign-in.
  • fd236a0: files-sdk/box, files-sdk/dropbox, files-sdk/google-drive, files-sdk/onedrive, and files-sdk/sharepoint now map failures when reading the body of a head() or list() result. If the file was deleted in between, text(), arrayBuffer(), and stream() now fail with a FilesError (NotFound) instead of the raw SDK error.
  • fd236a0: files-sdk/onedrive and files-sdk/sharepoint now report rejected credentials as Unauthorized. The Graph client strips an auth failure down to its error name, so a revoked oauth refresh token or a bad clientCredentials secret surfaced as a retryable Provider error. A throttled (429) or failing (5xx) token endpoint during an oauth refresh now stays a retryable Provider error instead of Unauthorized.
  • fd236a0: files-sdk/onedrive and files-sdk/sharepoint now report a key that names a folder as missing. exists() returned true for a folder path and head() returned the folder’s metadata as if it were a file. They now return false and throw NotFound, as the Dropbox and Box adapters do.
  • fd236a0: files-sdk/onedrive and files-sdk/sharepoint now reject signedUploadUrl({ contentType }) before creating an upload session. A Graph upload session doesn’t bind a Content-Type, so the Content-Type header the adapter used to return was only advisory, which the signedUploadUrl contract rules out. Omit contentType, or validate it at your gateway before issuing the URL.
  • fd236a0: files-sdk/sharepoint now lets an explicit site or library option win over the SHAREPOINT_* env targets. SHAREPOINT_DRIVE_ID used to override an explicit siteId, siteUrl, hostname, sitePath, or documentLibrary. SHAREPOINT_SITE_ID overrode an explicit siteUrl or hostname, and SHAREPOINT_SITE_URL overrode an explicit hostname. In each case the adapter silently read and wrote a different site or library than the one passed. The env targets now apply only when no option names a site, and for the drive, no library either. An explicit documentLibrary still resolves against an env site, and SHAREPOINT_HOSTNAME still pairs with an explicit sitePath.
  • bcb88dc: files-sdk/appwrite resumable uploads now classify a failed chunk by its HTTP status (409 as Conflict, 401/403 as Unauthorized, 404 as NotFound). Previously every chunk failure was a generic Provider error, so a chunked upload onto an existing file ID was retried with backoff instead of failing with Conflict.
  • bcb88dc: files-sdk/appwrite now rejects keys that aren’t valid Appwrite file IDs on every method (download, head, exists, delete, url, and the copy source), not only on writes. Previously a key such as .. or . reached node-appwrite, which leaves dots unencoded, so URL normalization pointed the request at the bucket instead of a file: delete("..") sent DELETE /storage/buckets/{bucketId}.
  • bcb88dc: files-sdk/appwrite resumable uploads no longer delete an existing file when the upload is aborted before any of its chunks landed. Previously control.abort() always deleted the file at the key, so aborting after Appwrite refused a chunked upload onto an existing file ID (or before the first chunk finished) deleted that existing file.
  • bcb88dc: files-sdk/bunny-storage now rejects keys containing . or .. path segments (including their %2e spellings) with a Provider error before calling the Storage API. Previously the Bunny SDK’s URL handling resolved those segments away, so delete(".") or delete("a/..") targeted the storage zone root and delete("x/.") the directory x/ instead of an object.
  • bcb88dc: files-sdk/fs copy() now stages the copied body (and its sidecar) next to the destination and renames it into place, like upload(). Previously it wrote straight to the destination path, so a symlink already sitting at the destination key or its .meta.json sidecar was followed and the file it pointed at, even one outside the adapter root, was overwritten; a crash mid-copy could also leave a truncated body or sidecar behind.
  • bcb88dc: files-sdk/fs, files-sdk/appwrite, and files-sdk/bunny-storage now mark their key-validation errors as permanent. These are keys that escape the adapter root or resolve onto it, reserved sidecar names, invalid Appwrite file IDs, and Bunny keys with . or .. segments. Previously these deterministic Provider errors counted as retryable, so a call made with retries re-sent the same invalid key, with backoff, until the retry budget ran out.
  • bcb88dc: files-sdk/fs resumable uploads now classify filesystem errors from chunk writes like every other method (ENOENT/ENOTDIR as NotFound, EACCES/EPERM as Unauthorized). Previously a failed chunk write surfaced as a generic Provider error, so a permission failure was retried as if it were transient.
  • bcb88dc: files-sdk/ftp and files-sdk/sftp streaming downloads (as: "stream") now remove their abort listener once the stream ends, errors, or closes. Previously the listener stayed on the signal, so a long-lived signal such as the Files constructor’s signal kept every streamed download’s connection and stream in memory for as long as the signal lived.
  • bcb88dc: files-sdk/pocketbase copy() now overwrites an existing destination key by replacing the file on its record, the same way upload() does. Previously it always created a new record, which the collection’s unique key index refused, so copying onto an existing key failed with a Provider error.
  • bcb88dc: files-sdk/pocketbase download() now maps a 401 or 403 from the file endpoint to Unauthorized. Previously anything other than a 404 was a Provider error, so a refused file token was retried as if it were transient.
  • 3306ac0: The files-sdk/api gateway and files-sdk/signed-url-policy no longer accept a requested attachment disposition that contains control characters. Previously any value starting with attachment was kept as “already safe”, so a caller could send attachment\r\n… and have it ride into the signed URL’s response-content-disposition. Such values are now replaced with the configured default disposition, like inline is. A tab (\t) is still allowed.
  • 30d0a2b: list() items under files-sdk/compression now return the original bytes from their body accessors on adapters whose listing carries no object metadata, such as S3 and the S3-compatible adapters. Without the algorithm marker in the listing the plugin passed those items through untouched, so text(), arrayBuffer(), blob() and stream() returned the stored compressed bytes; they now download the object back through the plugin and decompress it. Such items still report the stored size, since the original size lives in metadata the listing doesn’t include.
  • 30d0a2b: files-sdk/compression now marks its refusals and read failures as permanent: url(), signedUploadUrl(), a ranged download(), an unknown stored algorithm, and a failed decompress. Previously they were ordinary Provider errors, so a failover() placed before the plugin treated them as an outage and re-sent the call to its secondary, which runs without the plugin and minted an upload URL that skips compression or served raw stored bytes.
  • 30d0a2b: files-sdk/content-type now marks its onMismatch: "reject" and onUnknown: "reject" rejections, and its signedUploadUrl() refusal, as permanent. Previously they were ordinary Provider errors, so a failover() placed before the plugin treated a rejected upload as an outage and re-sent it to its secondary, which runs without the plugin, storing the mislabeled upload anyway.
  • 30d0a2b: list() items under files-sdk/dedup now return the content from their body accessors on adapters whose listing carries no object metadata, such as S3 and the S3-compatible adapters. Without the pointer marker in the listing the plugin passed those items through untouched, so text(), arrayBuffer(), blob() and stream() returned the empty pointer; they now follow the pointer to the stored content. Such items still report the pointer’s own size and ETag, since the content size and hash live in metadata the listing doesn’t include.
  • 30d0a2b: files-sdk/dedup now marks its url() and signedUploadUrl() refusals as permanent. Previously they were ordinary Provider errors, so a failover() placed before the plugin treated them as an outage and re-sent the call to its secondary, which runs without the plugin and minted a URL that bypasses content-addressing.
  • 30d0a2b: The files-sdk/encryption JSDoc now says to place failover() and tiering() after encryption(), and that only the body is encrypted. It previously said to put encryption last in the plugin array, which, combined with either of those plugins, stores whatever they route to a secondary or cold backend unencrypted; and its threat model didn’t mention that the key, contentType, and user metadata are stored as-is.
  • 30d0a2b: list() items under files-sdk/encryption now return plaintext from their body accessors on adapters whose listing carries no object metadata, such as S3 and the S3-compatible adapters. Without the envelope marker in the listing the plugin passed those items through untouched, so text(), arrayBuffer(), blob() and stream() returned the stored ciphertext; they now download the object back through the plugin and decrypt it. Such items still report the stored size, since the plaintext size lives in metadata the listing doesn’t include.
  • 30d0a2b: files-sdk/encryption now marks its refusals and read failures as permanent: url(), signedUploadUrl(), a ranged download(), a failed decrypt, a tampered envelope size, and a raw key of the wrong length. Previously they were ordinary Provider errors, so a failover() placed before the plugin treated them as an outage and re-sent the call to its secondary, which runs without the plugin and minted an upload URL for unencrypted bytes, signed a link to ciphertext, or served raw stored bytes.
  • 30d0a2b: files.capabilities now reports every conditional flag as false when files-sdk/failover is installed, matching the plugin’s refusal of every conditional operation. Previously it kept advertising the primary adapter’s native support, so callers that branched on the snapshot planned a compare-and-set that then threw.
  • 30d0a2b: files.capabilities now reports conditional.delete as false when files-sdk/soft-delete is installed, matching the plugin’s refusal of a conditional delete outside the trash. Previously it kept advertising the adapter’s native support, so callers that branched on the snapshot planned a conditional delete that then threw.
  • 30d0a2b: files.capabilities now reports every conditional flag as false when files-sdk/tiering is installed, matching the plugin’s refusal of every conditional operation. Previously it kept advertising the hot adapter’s native support, so callers that branched on the snapshot planned a compare-and-set that then threw.
  • 30d0a2b: A cross-tier copy() or move() under files-sdk/tiering now applies the call’s signal and timeout to the upload into the destination tier, and a fallback: true copy or move applies them to the eviction of the destination’s stale copy. Previously only the read from the source tier honored them, so a hung destination tier ignored the caller’s abort or deadline and could stall the call indefinitely.
  • 30d0a2b: files-sdk/validation now marks every ValidationError, and its signedUploadUrl() refusal, as permanent. Previously they were ordinary Provider errors, so a failover() placed before the plugin treated a rejected write as an outage and re-sent it to its secondary, which runs without the plugin, storing an upload that broke the size, type, or key rule.
  • 30d0a2b: files.capabilities now reports conditional.create, conditional.replace, conditional.delete and every conditional.copy flag as false when files-sdk/versioning is installed, matching the conditional mutations the plugin refuses. Previously it kept advertising the adapter’s native support, so callers that branched on the snapshot planned a compare-and-set that then threw. Exact reads (conditional.exactRead) still follow the adapter.
  • 4ce2f07: files-sdk/bun-s3 now infers each list() item’s type from its key, as files-sdk/s3 and files-sdk/s3-fetch already do. An S3 list response carries no Content-Type, and this adapter labelled every item application/octet-stream, so a .csv or .png listed as a binary blob. Keys with an unknown extension still fall back to application/octet-stream.
  • 4ce2f07: files-sdk/s3 (and the S3-compatible adapters built on it) now reports a failed body read on a head() result or a list() item as a FilesError. Previously the lazy GetObject behind text(), arrayBuffer(), blob(), and stream() let the raw AWS SDK exception escape, so an object deleted after it was listed surfaced as an S3ServiceException instead of a NotFound error. The fetch engine (files-sdk/s3-fetch, client: "fetch") already behaved this way.
  • 4ce2f07: files-sdk/s3 (and every adapter built on it: r2, minio, rustfs, wasabi, and the other S3-compatible wrappers) now detaches its abort listener once a multipart, progress, or unknown-length stream upload finishes. Previously the listener stayed on the caller’s signal until that signal aborted, so a long-lived signal such as a Files-level default kept every finished upload, and the body it held, in memory.
  • 4ce2f07: files-sdk/s3 (and the S3-compatible adapters built on it) now marks the error for a missing @aws-sdk/lib-storage peer as permanent and keeps the import failure as its cause. Previously a multipart, onProgress, or unknown-length stream upload without the package installed was retried under retries, re-issuing an upload that could only fail the same way.
  • 4ce2f07: files-sdk/neon now throws at construction when only one of accessKeyId and secretAccessKey is passed. Previously the key that was passed was silently dropped and the adapter fell back to the AWS credential chain, so a half-configured pair (for example, a secret read from an unset environment variable) connected with whatever credentials the environment held instead of failing.

Last updated on September 29, 2026

Was this page helpful?