Repository navigation
fix(deps): update dependency files-sdk to v3 - #581
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
renovate
Bot
force-pushed
the
renovate/files-sdk-3.x
branch
from
October 13, 2026 11:03
58bbe11 to
0da77ae
Compare
renovate
Bot
force-pushed
the
renovate/files-sdk-3.x
branch
from
October 17, 2026 01:49
0da77ae to
e095a62
Compare
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
^2.6.2→^3.1.1Release Notes
haydenbleasel/files-sdk (files-sdk)
v3.1.1Compare Source
Patch Changes
cfb6062: Interrupting a bufferedDownloadedFileread infiles-sdk/effect(text,arrayBuffer, orblob) now cancels the body read underneath, instead of leaving it running after the fiber stops waiting. The read is cancelled only once no other read is waiting on the same body, so a plainStoredFileread in progress is never cut short, and later reads of a cancelled body fail as anaborted(never retried)Providererror. Bodies downloaded through theusage()plugin stay cancellable. Resolves #173.0c51802: A plugin failure after a conditional exact read (download(key, { condition: { etag } })) is no longer reported as an applied mutation (#176). The conditional dispatcher marked any rejection that followed a successful native call withapplied: true, so a plugin throwing after the read (for examplefiles-sdk/encryptionfailing to decrypt) reached the caller,onError, andfiles-sdk/auditas though the object had changed. A read commits nothing, so that error now comes through unmarked. Anappliederror a plugin raises itself still passes through untouched, and conditional uploads, deletes, and copies are unchanged.6936be5:files-sdk/memoryETags no longer collide for different bodies (#177). The adapter hashed bytes with a 31-bit polynomial hash, so trivially related bodies shared an ETag:"Aa"and"BB"both came out as"00000840", and leading zero bytes didn't change the hash at all. Becausesync()treats a matching size and ETag as unchanged, it silently skipped a file whose bytes had changed, and the gateway'sIf-Rangecheck could accept a stale validator. The ETag is now the quoted MD5 of the bytes (computed in pure JS, so the adapter still runs in browsers and edge runtimes), the same value S3 returns for a single-part upload. Identical content still keeps the same ETag across re-uploads and copies.v3.1.0Compare Source
Minor Changes
d02e8ca: Add an Effect v4 bridge (files-sdk/effect).Filesis aContext.Serviceprovided byFiles.layer(), from aFilesinstance or the options to build one. Its methods mirrorFilesand returnEffects, withlistAll,search, and download bodies asStreams. Every operation fails with aFilesErrorwhosereasonis a tagged error for the SDK's code (NotFound,Unauthorized,Conflict,ReadOnly,Invalid,Unsupported,Provider), soEffect.catchReason("FilesError", "NotFound", …)recovers from one code, and each reason keeps the SDK error's flags and the original error as itscause. Each call receives its fiber'sAbortSignal, so interrupting the fiber (a timeout, a lost race, a closed scope) aborts the provider request. With theevents()plugin,files.events.on()runs an Effect handler for each storage event and keeps delivery at-least-once, andfiles.events.stream()serves the events as aStream.tryPromiseruns plugin methods with the same error mapping and cancellation, andmake()provides the service under your own key for multiple buckets or typed plugin methods.effectis an optional peer dependency.Patch Changes
203ecfe:files-sdk/openai: aneedsApprovaloverride increateAgentsFileTools({ overrides })no longer breaks the tool. It used to replace the Agents SDK's approval function with a boolean, so the runner failed withneedsApproval is not a functionon the first call. Overrides are now applied when each tool is built, an override takes precedence overrequireApproval(and can gate a read tool), and every single-tool factory accepts{ description, needsApproval }.16c1079: Fixfiles mcpfailing to start from the published package. The bundled MCP server read its version from a fixed../../package.jsonthat only resolved from source, so in an installed package it failed at startup with a misleading "themcpsubcommand requires@modelcontextprotocol/sdkandzod" error even when both were installed. The CLI now finds its ownpackage.jsonfrom wherever the build places each module.203ecfe: Thefiles uploadCLI's--metadataflag now takes onekey=valuepair per flag; repeat the flag for more (--metadata team=finance --metadata quarter=q1). It used to be variadic and swallowed the positional key that followed it, sofiles upload --metadata a=1 report.pdf --file r.pdffailed. Several pairs after a single--metadataare now refused as extra arguments.8529ee6: Bulkupload([...])anddownload([...])withstopOnErrorinfiles-sdk/client(and theuseFilesbindings) now behave like the SDK: items run one at a time in input order, and the call resolves at the first failure with{ results, errors: [thatFailure] }. They used to reject while sibling items kept running, so writes could land after the call had failed andisUploadingcould readfalsewhile uploads were still in flight. Items that never started are still reported as"aborted"in the upload state.8529ee6:download()infiles-sdk/clientnow reports the rightsizeandlastModifiedmore often. On a full proxied download it takessizefrom the gateway's metadata header instead ofContent-Length, which compression middleware can drop (giving0) or rewrite. A download the gateway redirected to storage now falls back to the storage host'sLast-Modifiedheader forlastModified.8529ee6:files-sdk/clientand theuseFilesbindings (files-sdk/react,files-sdk/vue,files-sdk/svelte) now reject with aFilesErrorwhen a request never gets an answer. A network failure used to surface as the runtime's bareTypeErrorand a cancelled call as a rawAbortError, and the hooks recorded a hook-levelabort()inerroras aProviderfailure withaborted: false. A network failure is now aProviderFilesErrorwith the original error as itscause, and a cancelled call (per-callsignal, hook-levelsignal, orabort()) is flaggedaborted: true, matching the XHR upload path. The hooks rethrow the same error they record inerror.8529ee6:files-sdk/clientnow classifies error responses that don't come from the gateway by HTTP status, and says why. A body such as Supabase's{ "error": "InvalidJWT", "message": "jwt expired" }used to be read as the gateway's envelope and became aProvidererror with an empty message, and a401from auth middleware in front of the gateway became a genericProvidererror. Only a body whoseerror.codeis a string is now treated as the envelope. Anything else maps401/403toUnauthorized,404toNotFoundand409/412toConflict(elseProvider), and the message keeps the oldgateway responded 401orupload failed (403)prefix with the body's reason appended when it has one.8529ee6: A keylessupload()fromfiles-sdk/client(and theuseFilesbindings) of a React Native file reference without asizenow works. The client used to declare such a file's size as0, and since the gateway holds an upload to its declared size, the upload was refused. The client now leavessizeout of the presign request when it doesn't know it, so the gateway applies only itsmaxUploadSize.8529ee6: The reactiveuseFile,useListanduseSearchinfiles-sdk/reactandfiles-sdk/vueno longer show the previous input'sdata. Switching to a new key, prefix, pattern, or endpoint used to keep the old result visible while loading (withisLoadingfalse) and next to the new input's error, and a disabled query (useFile(undefined)) kept it too. A new input now starts with no data andisLoading: true, and a disabled query has no data or error. Arefetch()of the same input still keeps its data while it reloads, and when the reload fails.8529ee6: The reactive queries infiles-sdk/react,files-sdk/vueandfiles-sdk/svelte(useFile,useList,useSearch) now honor thesignaloption. It was documented as merged into every call but was ignored. Aborting it cancels the query's request and settles the query with anabortedFilesError, and the query removes its listener once its request settles or is replaced.8529ee6: A string body uploaded withfiles-sdk/client(or theuseFilesbindings) and nocontentTypeis now stored astext/plain; charset=utf-8, as the server SDK stores it. It used to be sent untyped and stored asapplication/octet-stream.8529ee6: A globalContent-Typein theheadersoption offiles-sdk/client(and theuseFilesbindings) no longer overrides the type of keyed uploads. It used to replace everyupload(key, body)request's type, even an explicitcontentType, so files were stored as, say,application/json. The body's own type orcontentTypenow always wins, and the JSON verbs always sendapplication/json.b557950:files-sdk/cloudinarynow invalidates the CDN copy whencopy()or a resumable (control) upload overwrites an existing asset, as a plainupload()already did. Without it the CDN kept serving the replaced content until its cache expired.b557950:files-sdk/netlify-blobsnow rejects keys that@netlify/blobscan't address, with anInvaliderror before any request. The SDK puts keys into request URLs unencoded, soupload("Invoice #​42.pdf")storedInvoice, a?cut the key short in the same way, a\became/, tabs, newlines and trailing spaces were dropped, and..segments could even reach another store. Keys containing#,?,%,\, tabs or newlines, ending in a space or control character, or with./..segments now throw instead of silently naming a different blob.b557950:files-sdk/pocketbasenow prefersadminEmail/adminPasswordpassed as options over aPOCKETBASE_AUTH_TOKENenvironment variable. The env token was checked first, so a stale token left in the environment overrode valid credentials passed in code. The order is now theauthTokenoption, admin credentials from options,POCKETBASE_AUTH_TOKEN, thenPOCKETBASE_ADMIN_EMAIL/POCKETBASE_ADMIN_PASSWORD.b557950:files-sdk/supabasecopy()(and somove()) now replaces an existing destination. Supabase only overwrites on copy when asked to, so copying or moving onto a key that already existed failed with aConflicterror, which also broke re-deleting a key withsoftDelete()and restoring over an existing key. Copies are now sent withx-upsert: true.b557950:files-sdk/supabasebulk deletes of more than 1000 keys now succeed. All keys went out in one request, which Supabase Storage refuses past 1000 objects, so every key was reported as failed. Keys are now deleted in batches of 1000, and a refused batch fails only its own keys.b557950:files-sdk/supabasenow percent-encodes keys in every request URL. Keys went into the path unencoded, so a#or?cut the key short:upload("uploads/Invoice #​42.pdf")storeduploads/Invoice, a later#43upload silently overwrote it, andhead,downloadanddeletedisagreed about which object the key named. Uploads, downloads,head,exists,url()(signed and public) andsignedUploadUrl()now address exactly the key given, including keys with?,%, spaces or+. Keys that Supabase Storage itself refuses (such as ones containing#or non-ASCII characters) now fail with Supabase'sInvalid keyerror instead of being truncated.b557950:files-sdk/uploadthinglist()now returns only files that finished uploading. It also returned files UploadThing reported as still uploading, failed, or pending deletion, which can't be downloaded. Pagination is unchanged: the cursor still advances past every row UploadThing returned.b557950:files-sdk/vercel-blobcopy()now keeps the source's content type and cache max-age.blob.copydoesn't carry them over, so every copy, move and versioning snapshot got a content type guessed from the destination's extension and the default one-month cache. The adapter now reads both from the source and passes them to the copy.b557950:files-sdk/vercel-blobdownloads no longer return stale bytes right after an overwrite. The body came through the CDN cache while the size and ETag came from a freshhead(), so for up to a minute the old content was returned labelled with the new metadata. Private downloads now read from origin (useCache: false), and public downloads add av=<etag>query parameter to the blob URL so each new version misses the cache once.b557950:files-sdk/azureno longer lets environment credentials for one storage account authenticate an adapter configured for another. Previously,azure({ container, accountName: "a" })picked up anAZURE_STORAGE_CONNECTION_STRINGfor accountb, so data calls went toband SAS URLs were signed witha's name andb's key (a 403). Now an env connection string whoseAccountNamediffers from an explicitaccountNameis ignored, and so areAZURE_STORAGE_ACCOUNT_KEYandAZURE_STORAGE_SAS_TOKENwhenAZURE_STORAGE_ACCOUNT_NAME(orAZURE_STORAGE_ACCOUNT) names a different account. Env credentials for the same account, or that name no account, still apply.b557950:files-sdk/azurecopy()now copies blobs larger than 256 MiB. It used Copy Blob From URL only, which refuses bigger sources with a 409 that surfaced as aConflicterror. Now, when that cap is the cause, it falls back to the asynchronous Copy Blob and resolves once the copy finishes. A failed or aborted copy rejects with aProvidererror carrying Azure's reason.b557950:files-sdk/convexupload()no longer fails after the file is stored when reading its metadata back fails. Previously that error escaped unmapped, and the caller never learned the new storage id, which orphaned the file. Now the upload resolves with the new id and the uploaded content type and size.b557950:files-sdk/firebase-storagenow prefers a passedapp's ownstorageBucketoverFIREBASE_STORAGE_BUCKET. Previously the env var won, so in a process serving several Firebase projects, an adapter built from one project's app used the env var's bucket with that app's credentials. The order is nowbucket, then the app'sstorageBucket, thenFIREBASE_STORAGE_BUCKET.b557950:files-sdk/gcsandfiles-sdk/firebase-storageupload()now take the result'setag,lastModified, and size from the upload response instead of reading the object back afterwards. A service account that can create objects but not read them (Storage Object Creator) no longer gets anUnauthorizederror for an upload that landed, and concurrent writers to the same key no longer risk reporting each other's etag. If the response lacks an etag, the follow-up read is best-effort and can't fail the upload.b557950:files-sdk/gcs,files-sdk/firebase-storage, andfiles-sdk/bunny-storagenow honorsignal(andtimeout) on uploads. Previously an aborted or timed-out upload kept running in the background and could land later, over a newer write. Now aborting cancels the in-flight request: GCS and Firebase destroy the upload stream, and Bunny Storage, whose SDK takes no signal, errors the request body instead. Bunny Storage'sdownload()andcopy()honor the signal the same way.db60e17: A conditionalupload()infiles-sdkwhose write committed but whose adapter returned a malformed or weak ETag now rejects withapplied: true. Before, the post-commit ETag check failed the call as a plain provider error, hiding that the object had already changed.db60e17:url()andsignedUploadUrl()infiles-sdknow reject anexpiresInthat isn't a positive whole number of seconds with anInvaliderror before anything is signed. Before, values like-60,0,NaN, or0.5were passed to the signer and came back as URLs that never worked.db60e17:list(),listAll(), andsearch()infiles-sdknow reject alimitthat isn't a positive integer, andsearch()a negative, fractional, orNaNmaxResults, with anInvaliderror before any provider call. Before,limit: -1made thefs, memory, FTP, and SFTP adapters silently drop the last key of each page,0orNaNwalked nothing, and a badmaxResultsreturned nothing or everything.maxResults: 0still yields no matches.db60e17:upload()infiles-sdknow rejects amultipart.partSizeormultipart.concurrencythat isn't a positive integer with anInvaliderror before any request, on every upload path (single, bulk, and resumable). Before, a fractional or non-positive value could spin a resumable upload in an endless loop that starved the event loop, silently drop the last byte on Azure, or commit an empty object. The resumable orchestrator also refuses to finalize a part list that doesn't add up to the whole body, and fails a chunk the provider acknowledges without advancing instead of re-sending it forever.db60e17: Thefiles-sdk/ftp,files-sdk/sftp, andfiles-sdk/webdavadapters now reject keys that contain a backslash or start with a Windows drive letter (C:) with anInvaliderror. Before, a key like..\..\secretpassed the..traversal guard as a single segment, andC:/Windows/win.iniresolved to an absolute path, so on servers running on Windows hosts either could reach files outside the configured root.db60e17: Resumable uploads infiles-sdknow open, probe, and finalize their provider session under the same per-attempttimeout, callersignal,control.abort(), andretriesas each part. Before, a hung session call ignored the timeout and abort, and a transient provider error there failed the upload despiteretries. A session that opens after its attempt timed out or was aborted is discarded as soon as it lands, and a finalize retry that finds the session already gone now reports that the object may have been committed instead of a misleading "not found". Custom resumable drivers receive an optionalsignalinbegin(),probe(), andcomplete().21c88cc:files-sdk/boxOAuth auth now survives Box's single-use refresh tokens. Before, the rotated refresh token lived only in the instance's memory, so after a restart or in a second instance the configured token was already spent and every call failed withUnauthorized. A newoauth.tokenStorageoption persists the rotated tokens (the configuredrefreshTokenbecomes the first-run seed), and concurrent refreshes now share one exchange instead of spending the same token several times.21c88cc:files-sdk/dropboxnow stripsrootFolderPathfrom listed keys regardless of case. Dropbox paths are case-insensitive, so withrootFolderPath: "uploads"and a real folder named/Uploads,list()used to return keys likeUploads/a.txtthat then addressed/uploads/Uploads/a.txt. Listed keys are now relative to the root (a.txt), as they are when the casing matches.21c88cc:files-sdk/google-driveno longer trusts a cached file id after another process or the Drive UI deleted, trashed, or replaced the file. Before,headreportedNotFound,existsreturnedfalse, anddeleteresolved while the live file stayed. Now, when Drive answers that a cached id is gone, the adapter drops it and looks the key up again once. A trashed file reads as missing, anddelete,copy, andurlcheck that a cached id isn't trashed before acting on it.21c88cc:files-sdk/onedriveandfiles-sdk/sharepointnow create a missing destination folder beforecopy(), socopy()andmove()into a new folder succeed. Before, Graph's copy failed because its destination folder must already exist, even thoughupload()to the same key creates intermediate folders, as copy does on Box, Dropbox, and WebDAV.21c88cc:files-sdk/onedriveandfiles-sdk/sharepointno longer reject validlist()cursors from page 2 onward when the drive path is percent-encoded differently in Graph's@odata.nextLink. This affected asiteId(its commas) and folder names containing characters such as@ , ; = + $ &. Cursors are now compared on their decoded paths, and a cursor for another folder is still refused.21c88cc:files-sdk/dropboxandfiles-sdk/onedrive(andfiles-sdk/sharepoint) refresh-token auth now shares one in-flight token exchange. Before, a cold burst of calls sent one token request per call, which risked rate limiting and, with rotating refresh tokens, rejected redemptions. A failed exchange is retried by the next call instead of being replayed.203ecfe:files-sdk/events: a Google OIDC token whose signature segment isn't base64 is now refused with a401("malformed OIDC token"), before any key fetch. It used to escape as an unexpected error, answered500(so Pub/Sub redelivered it) and reported toonError.203ecfe:files-sdk/events: SNS signature verification now accepts aSigningCertURLonly athttps://sns.<region>.amazonaws.com/SimpleNotificationService-<id>.pem(or.amazonaws.com.cn). The previous host check also matched S3 bucket endpoints such assns.s3-accelerate.amazonaws.com, so a bucket namedsnscould have served a certificate.203ecfe:files-sdk/events: a webhook withverify: { sns }now parses only the fields of an SNS message that its signature covers. Before, the raw body was parsed after verification, so unsignedEventSource/Snsfields added beside a genuine signed message could feed forged S3 events (a delete, say) to your handlers. Thes3format also refuses a body that is both an SNS notification and a Lambda SNS record.203ecfe:files-sdk/eventsno longer reports adeletedevent when only one version of an object was removed, since the key may still have a live version. That covers S3ObjectRemoved:DeleteandLifecycleExpiration:Deletecarrying a version id (includingNoncurrentVersionExpirationcleanups) and EventBridgePermanently Deletedevents with aversion-id, Backblaze B2b2:ObjectDeleted:*, and GCSOBJECT_DELETE/ Eventarcdeletedevents whose payload shows the generation was already noncurrent (timeDeletedmore than a minute before the event). Delete markers, B2 hide markers, GCS archives and unversioned deletes are still reported, so cleanup handlers no longer delete live data when a lifecycle rule prunes old versions.51204c2: Thefiles-sdk/apigateway'surloperation, andfiles-sdk/signed-url-policy, now accept a requestedresponseContentDispositionas an attachment only when its type is exactlyattachment. Values such asattachment, inline,attachment inline, orattachment/xpassed the old check but render inline in Chromium; they are now replaced with a plainattachment.51204c2: Thefiles-sdk/apigateway no longer lets a comma-separated content type such asimage/png, text/htmlskip the download sandbox. A browser renders such a value as its last entry, so a proxied download opened inline could run stored HTML as your app; the sandbox now applies to any stored type that isn't exactly one well-formed media type. The gateway also refuses such a content type from a client with a422(reason: "type") on the keyed uploadPUT,presign, andsigned-upload-url, while an absent content type is still accepted.51204c2: Proxied downloads from thefiles-sdk/apigateway now name the file in their defaultContent-Disposition, asattachment; filename="report.pdf"(with an RFC 6266filename*for a non-ASCII name), taken from the key's last segment. Before, the bareattachmentmade a browser save a download opened directly from its URL asfiles. A disposition returned byauthorizeis still sent exactly as given.51204c2: Thefiles-sdk/apigateway now answersHEADon the download route instead of refusing it with422. A proxied download returns the same headers aGETwould (Content-Length,ETag,Last-Modified, the disposition) with no body, without reading the object, and a redirected download returns the same redirect.51204c2: A full (200) proxied download from thefiles-sdk/apigateway now carries the object'ssizein itsX-Files-Metaheader, so a client can read the size even when compression middleware dropsContent-Length. A206range response leaves it out.51204c2: A zero-byte upload through thefiles-sdk/apigateway no longer fails with422 missing request bodyon Bun, Deno, or Cloudflare Workers. Those runtimes give aPUTwithContent-Length: 0anullbody, which the keyed upload and the proxy upload now treat as an empty body.51204c2: The Node gateway bindings (files-sdk/express,files-sdk/fastify,files-sdk/koa,files-sdk/nitro, andfiles-sdk/nestjs) now work over HTTP/2. Under Fastify'shttp2: trueornode:http2's compatibility API every request failed with a500, because the HTTP/2 pseudo-headers (:method,:path,:authority,:scheme) were copied into the WebRequest; they are now skipped, and the host is taken from:authoritywhen there is noHostheader.51204c2: Thefiles-sdk/apigateway no longer forwards a storage provider's error message to the client. Messages from an adapter'sNotFound,Unauthorized,Conflict, andProvidererrors could carry absolute filesystem paths, internal hostnames, or bucket names, so those now reach the client as a fixed message per code (not found,storage provider error, …) with the same code and status, andProviderandUnauthorizederrors are passed toonErrorso the detail still reaches your logs. The SDK's ownInvalid,Unsupported, andReadOnlymessages, and anyFilesErrorthrown by yourauthorize,filesfactory, oronUploadComplete, are still sent as written.51204c2: Thefiles-sdk/apigateway now refuses keys inside a plugin's reserved storage however they're spelled. On a case-insensitive store or a Windows filesystem,.TRASH/notes.txtor.trash\notes.txtreached thesoftDelete()trash (and.VERSIONS/…the version store), so a client allowed todeletebut notpurgecould hard-delete a trashed file; those now get403like.trash/notes.txt. ArestoreVersionversionIdcontaining a backslash,.., or a NUL byte is now refused with422, like one containing a slash.51204c2: Asearchthrough thefiles-sdk/apigateway now always walks storage in pages ofmaxListLimitkeys. The client'slimitwas used as the page size whilemaxSearchScancounts keys, solimit: 1turned one request into up to 10,000 providerlist()calls;limitis now ignored by the gateway, andmaxResultsstill caps the matches.51204c2: A keyless upload through thefiles-sdk/apigateway is now held to the size the client declared atpresign, capped bymaxUploadSize. Before,authorizesaw the declared size but the upload token allowed anything up tomaxUploadSize, so a client could understate the size to pass a per-user quota. The proxyPUTandcompletenow refuse a larger body, a storage-signed target binds the declared size where the adapter can, and a client that doesn't know a file's size may omitsize(that upload is held tomaxUploadSizealone). A declared size that isn't a non-negative whole number is a422.51204c2: Upload tokens from thefiles-sdk/apigateway are now bound to the origin (scheme and host) of the request that minted them, as well as its path and query. With afilesfactory that picks the instance from the host, a token minted on one tenant's host could previously be redeemed by the proxy uploadPUTon another's; that request, andcompleteon the wrong host, now getUnauthorized. The docs now also warn that a factory must select the instance from the URL, not from a cookie or header, because the proxyPUTruns noauthorize.9fdc79f:files-sdk/cacheno longer caches a stale read that overlapped a write. A slowdownload(),head(), orurl()miss that was in flight while anupload()(ordelete,copy,move) of the same key completed used to store what it had fetched after the write invalidated the key, serving the old value for the fullttl; such a read now returns its result without caching it. Writes also drop the key before they run and after any failure, not only after a success or aConflict, since a timed-out write may still have landed.9fdc79f:files-sdk/content-typenow classifies a document behind an<?xmlprolog by its root element, so an XHTML or SVG document can no longer pass as an arbitrary+xmltype. Previously any declared+xmltype agreed with an XML sniff, so an XHTML page uploaded asimage/x+xmlpassedonMismatch: "reject"(and animage/*allowlist after it) and ran its scripts when served. Anhtmlorsvgroot (or one in the XHTML or SVG namespace) now needs exactlyapplication/xhtml+xmlorimage/svg+xml, an XML document never agrees with animage/,audio/,video/, orfont/type other thanimage/svg+xml, anddetectContentType()reportsapplication/xhtml+xmlfor an XHTML root.9fdc79f:files-sdk/dedupnow reserves its blob store (.dedupby default) for the instance, the wayversioning()andsoftDelete()reserve theirs. Previously afiles-sdk/apigateway client could list.dedup/, download another user's content by its hash, or delete or move a blob and break every pointer to it; those requests now get a403. Amoveout of the store through the instance is also refused (it would remove the blob), and a backslash spelling such as.dedup\xis recognized as a store key.9fdc79f:files-sdk/failoverno longer fails over an upload that carries a resumablecontrol. AnUploadControldrives exactly one upload, so replaying it on a secondary after the primary failed threwThis UploadControl has already driven an uploadand hid the primary's real error. Such uploads now go to the primary only, like stream uploads, and surface its error.9fdc79f:files-sdk/validationandfiles-sdk/content-typeno longer accept a declared content type that is really a list, such asimage/png;a=b, text/html. Both plugins used to check only the text before the first;and then store the caller's value verbatim, which a browser renders as its last entry (text/html), so a PNG/HTML polyglot could passallowedTypes: ["image/*"]orcontentType({ onMismatch: "reject" }). A declared type that isn't exactly one well-formed media type is now refused with anInvaliderror (byvalidation()wheneverallowedTypesis set, and bycontentType()in every mode), and the type either plugin forwards is stored with its type and subtype normalized.9fdc79f:files-sdk/soft-deleteno longer hard-deletes a live file through a trash key that resolves out of the trash. Adelete(".trash/../notes.txt"), which a filesystem resolves tonotes.txt, used to be treated as a delete inside the trash and destroyed the live file; it's now trashed like any live key.purge()andrestoreTrashed()refuse a key that resolves out of the trash, such as../notes.txt, with anInvaliderror.9fdc79f:files-sdk/soft-delete'spurge()is now idempotent on adapters whosedeletethrowsNotFoundfor a missing key, such as GCS and Firebase.purge(key)with nothing trashed used to reject withNotFoundthere, and a whole-trashpurge()failed when an object was purged concurrently; both now resolve.9fdc79f:files-sdk/soft-deletenow explains a trash collision on hierarchical stores such asfiles-sdk/fsand SFTP. Withaalready in the trash, deletinga/bneeds a.trash/a/folder where the trashed file sits, and used to fail with a bareEEXIST … mkdir .trash/aerror; it now throws aConflictthat names the trashed entry in the way and says topurge()(orrestoreTrashed()) it first. The live object is untouched either way.9fdc79f:files-sdk/tieringwithfallback: truenow works over tiers whosedeletethrowsNotFoundfor a missing key, as GCS and Firebase do. The plugin's cleanup deletes assumed a missing key was a no-op, so every first upload rejected withNotFoundafter it had landed, acopyormoverejected after landing, and adeleteof a key held by the other tier threw before reaching it, leaving the object in place. Those cleanup deletes now ignoreNotFound; adeleteremoves the key from whichever tier holds it, and throwsNotFoundonly when both tiers report the key missing.9fdc79f:files-sdk/versioning'srestoreVersion()now refuses aversionIdthat is empty or., or that contains a backslash,.., or a NUL byte, as it already refused one containing/(thefiles-sdk/apigateway refuses the same ids). On a Windows filesystem aversionIdsuch as..\..\users\2\secret\<id>restored another key's version (possibly another tenant's) into the caller's key. A write to a key such as.versions/../notes.txt, which a filesystem resolves tonotes.txt, is also no longer mistaken for a write inside the version store, so it's snapshotted like the live key it is.22f8f5d:files-sdk/s3,files-sdk/s3-fetch, and every S3-compatible adapter now refuse usermetadatavalues that aren't printable ASCII, including Latin-1 text such ascafé, with anInvaliderror before any request. Such a value used to go out and fail asSignatureDoesNotMatch, reported asUnauthorized, because the HTTP client sends a Latin-1 character as one byte while SigV4 signs it as two. Encode the value first, for example withencodeURIComponent. The R2 binding stores metadata without HTTP headers, so it still accepts any text.22f8f5d:files-sdk/bun-s3now reports bad credentials onlist(),upload(),delete(), andcopy()asUnauthorized. Bun's S3 errors carry no HTTP status, andSignatureDoesNotMatch,InvalidAccessKeyId,ExpiredToken, andInvalidTokenweren't recognized, so a wrong key or secret was aProvidererror thatretriesreissued.22f8f5d:files-sdk/s3,files-sdk/s3-fetch, every S3-compatible adapter, andfiles-sdk/bun-s3now refuse acontentTypeorcacheControlcontaining control characters (such as CR/LF) or non-ASCII text with anInvaliderror before any request, on uploads, resumable uploads, andsignedUploadUrl(). Such a value used to fail as a retriedProvidererror, or asUnauthorizedon the aws-sdk client while the fetch client stored it mangled.22f8f5d: Adownload()whoserangestarts past the end of the object (416 InvalidRange) is no longer retried onfiles-sdk/s3,files-sdk/s3-fetch, the S3-compatible adapters,files-sdk/bun-s3, or thefiles-sdk/r2binding (error code 10039). It stays aProvidererror, since it's the provider's answer, but is now markedpermanent, soretriesdoesn't reissue a request that can only fail the same way.22f8f5d:copy()andmove()onfiles-sdk/s3,files-sdk/s3-fetch, and the S3-compatible adapters now handle sources over 5 GiB. S3's singleCopyObjectrequest refuses them, which used to surface as a retriedProvidererror; the adapter now confirms the size with a HEAD and copies the object server-side with a multipart copy (UploadPartCopy), carrying its content type, cache and disposition headers, and user metadata. On AWS every part is pinned to the source's ETag, so a source overwritten mid-copy fails withConflict. A conditional copy of such a source throwsUnsupported, since it can't stay one atomic request.22f8f5d:multipart.partSizeonfiles-sdk/s3and the S3-compatible adapters' aws-sdk client is now rounded into what S3 accepts. A size under 5 MiB is raised to S3's minimum instead of failing every attempt withEntityTooSmall, a size over 5 GiB is lowered to the maximum, and when the body's length is known the size grows until the body fits in S3's 10,000 parts, instead of failing at part 10,001 after about 48.8 GiB had uploaded.22f8f5d:url()andsignedUploadUrl()onfiles-sdk/s3,files-sdk/s3-fetch, the S3-compatible adapters,files-sdk/bun-s3, and thefiles-sdk/r2binding's hybrid signer now throwInvalidfor anexpiresInordefaultUrlExpiresInthat isn't a whole number of seconds of at least 1. A zero, negative, fractional, orNaNlifetime used to mint a URL that was dead on arrival; only the seven-day ceiling was checked.22f8f5d: A resumable (control) upload onfiles-sdk/s3and the S3-compatible adapters no longer fails after the object has committed when the follow-upHeadObjectis denied or fails (for example, a principal allowedPutObjectbut notGetObject). The upload now resolves with the summed part sizes and the upload's content type, where it used to reject withUnauthorizedand leave a resume that could only hitNoSuchUpload.22f8f5d: An expired or malformed session token (ExpiredToken,InvalidToken, which S3 returns as HTTP 400) is nowUnauthorizedonfiles-sdk/s3,files-sdk/s3-fetch, and the S3-compatible adapters, instead of aProvidererror thatretriesreissued.21c88cc:files-sdk/fsresumable uploads no longer follow a symlink planted at the<key>.fls-partstaging path. Starting an upload used to open that path and truncate whatever the link pointed at, inside the root or not, and completing it then renamed the link into place. Now the upload replaces anything at the staging path and creates the file exclusively, and a chunk or completion that finds a symlink there fails withConflictinstead of writing or reading through it.21c88cc:files-sdk/fsnow rejects keys that end in/, or in a.or..segment, withInvalid. Path resolution used to drop the trailing part, soupload("dir/")wrote a file nameddiranddelete("a.txt/")deleteda.txt. Adelete("dir/")that used to be a silent no-op now throwsInvalidtoo.21c88cc:files-sdk/ftpnow runs calls on an injectedclientone at a time. An FTP connection can only run one command at a time, andbasic-ftpcloses the whole connection when a second command starts, so concurrent calls on a shared client (Promise.all, or the bulk forms ofupload,download, andhead) used to fail and leave the client closed for good. Calls now queue on the client, a failed call no longer blocks the ones behind it, and a streamed download holds the client until the stream is read to the end or cancelled.21c88cc:files-sdk/ftpandfiles-sdk/sftpmove()now overwrite an existing destination like every other adapter. A plain rename failed whenever the destination existed: OpenSSH and most SFTP servers answered with a generic failure that was retried and then thrown asProvider, and FTP servers that refuse to rename over a file, such as IIS, reported a misleadingNotFound. When the server refuses, the destination is now deleted and the rename retried (so the key is briefly absent), except for a case-only rename, which could otherwise delete the source on a case-insensitive server.21c88cc:files-sdk/webdavstreamed downloads (as: "stream") no longer reportsize: 0when the server sends a chunked response without aContent-Lengthheader. The adapter now takes the size from aPROPFIND, narrowed to the requested slice for a ranged read.21c88cc:files-sdk/zipunzip()now decodes entry names the way the archive declares them. Names were always read as UTF-8 with invalid bytes replaced, so archives from Windows Explorer and other tools that write code page 437 produced garbled keys, and two such entries could collapse into one name and fail as duplicates. Names flagged UTF-8, or carried in an Info-ZIP Unicode Path field, are now read as strict UTF-8 and fail closed on invalid bytes. Unflagged names are read as UTF-8 when they are valid UTF-8 and as code page 437 otherwise.v3.0.0Compare Source
Major Changes
0223b30:files-sdk/cacheno longer shares entries between instances. Store keys now include the instanceprefix, and a customstorerequires anamespacenaming the bucket it caches (cache({ store, namespace: "uploads-prod" })); without one,cache()throwsInvalid. Reusing onecache()on an instance with a different adapter orprefixalso throwsInvalid, so create one per instance. Before, two tenants sharing a store could read each other's cached bytes, URLs, and metadata.0223b30: Thefiles-sdk/apigateway now refuses client keys andlist/searchprefixes inside theversioning()store (.versions/) or thesoftDelete()trash (.trash/) with a 403, so a client can't hard-delete trashed objects, wipe version history, or forge versions through the core operations. Use the plugin operations instead. Under an authorizekeyPrefix, a tenant's own folders with those names stay ordinary keys.0223b30: The Google Drive adapter (files-sdk/google-drive) now refusesupload({ cacheControl })withUnsupported(capabilities.cacheControlisfalse): Drive never serves it, and the value was silently dropped. It also declaressignedUpload.contentType: true, since the session binds the type, and no longer offers resumable uploads when built from a pre-builtclient, where they always threw.0223b30:signedUploadUrl({ minSize })withoutmaxSizenow throwsUnsupportedonfiles-sdk/s3,files-sdk/s3-fetch,files-sdk/r2,files-sdk/bun-s3, and the adapters built on them, instead of returning a presigned PUT that silently accepts empty uploads. Ons3(), passmaxSizetoo to get a POST policy that enforces both.minSize: 0still returns a PUT.40a3694: The array (bulk) forms ofupload,download,head, anddeletenow resolve to one shape,BulkResult<T>={ results: T[]; errors?: BulkError[] }, instead of four differently named arrays.resultswhere you readuploaded,downloaded,files, ordeleted.exists([…])keeps its{ existing, missing, errors? }split.UploadManyResult,DownloadManyResult,HeadManyResult, andDeleteManyResultare now aliases ofBulkResult.DeleteManyOptionsis an alias ofBulkOptions, andDeleteManyErroris removed in favour ofBulkError(the same{ key, error }shape).stopOnErroron a bulkdelete: it now always deletes one key at a time and skips the adapter's native batch, because a batch request can't stop partway. So it returns the same result with or without plugins installed, where before the native path and the plugin path could disagree.Adapter.deleteManyreturns{ results, errors? }.files-sdk/apiwire,files-sdk/client, theuseFilesbindings, and the CLI and MCP bulk output use the same shape.58e15bc: Adapters now declare what they can do as onecapabilitiesobject, andfiles.capabilitiesgains the fields the old flags couldn't express.supportsRange,supportsDelimiter,supportsMetadata,supportsCacheControl,supportsServerSideCopy,reportsUploadProgress, andsignedUrlfields onAdapterare replaced bycapabilities: { rangeRead, delimiter, metadata, cacheControl, serverSideCopy, uploadProgress, signedUrl, signedUpload }. Every field is optional and defaults to unsupported.resumableandconditionalare still derived fromresumableUploadandconditional. Declare it per instance, so a constructor option that changes what the adapter can do (apublicBaseUrl, an access mode, thefetchclient) is reflected. Rename the old flags:supportsRange→rangeRead,supportsDelimiter: true→delimiter: "any"(or"slash"),supportsMetadata→metadata,supportsCacheControl→cacheControl,supportsServerSideCopy→serverSideCopy,reportsUploadProgress→uploadProgress, top-levelsignedUrl→capabilities.signedUrl. The old fields are ignored and anything you leave out is off: withoutsignedUrl.supported,url(key, { expiresIn })throwsUnsupportedbefore yoururl()runs; withoutsignedUpload, the gateway proxies every upload; withoutsignedUrl.dispositionorpublicUrl, it proxies downloads.files.capabilities:multipartis renamedresumable(it always describedupload({ control })).delimiteris now"any" | "slash" | false, since Vercel Blob, Netlify Blobs, Supabase, Dropbox, Box, OneDrive, and SharePoint only accept"/".signedUrlgainsexpiry: "exact" | "provider" | "none": Box, PocketBase, and Dropbox's 4-hour temporary links are"provider", because the provider sets the lifetime. There's a newsignedUpload: { supported, maxSize, contentType, maxExpiresIn? }describingsignedUploadUrl(), and plugins that refuse direct uploads (encryption,compression,dedup,validation,contentType) turn it off.files-sdk/api): presigning uploads now followssignedUploadinstead of the downloadsignedUrlflag. An upload is presigned only when the adapter can bind the gateway'smaxUploadSizeand the file's content type; otherwise it goes through the proxy, which enforces both. UploadThing in its default public-read mode now reportssignedUrl.supported: false(itsurl()is a permanent link) while still presigning uploads, and Vercel Blob in public mode now gets direct presigned uploads instead of proxied ones.range,metadata,cacheControl,control, ordelimiteris now refused before any plugin runs, checked against the plugin-narrowed capabilities, so a plugin can no longer do I/O (a version snapshot, a trash move) for a call that's about to be rejected. When a plugin is what turned the option off, the error names it, for examplerange downloads are not supported by the "encryption" plugin.7ca626f:FilesErrorgains two codes,InvalidandUnsupported, split out ofProvider, which now only means the backend or transport failed.Invalid: the call itself is wrong. That covers an empty, null-byte, or..key, a malformed range, ETag, or condition, contradictory options, anexpiresInpast a hard cap, missing or bad constructor config, and a file avalidation()rule rejects (ValidationErrorkeeps itsreason).Unsupported: the call is well-formed but this adapter, in this mode and with these plugins, can't do it. That covers arange,metadata,cacheControl,delimiter, orcontrolthe adapter has no primitive for,url()without apublicBaseUrlon FTP/SFTP/WebDAV, asignedUploadUrl()maxSizeorcontentTypethe provider can't bind, and the fail-closed refusals ofencryption(),compression(),dedup(),validation(), andcontentType().permanent: both new codes are alwayspermanentand are never retried or failed over.Provideris still the only retried code.versioning()'srestoreVersion()andsoftDelete()'srestoreTrashed()now throwNotFoundwhen there's nothing to restore.error.code === "Provider"to catch bad input or unsupported options must also checkInvalid/Unsupported.files-sdk/api): anInvaliderror is answered with a422and the wire codeValidation(it was a500), and anUnsupportedone with a422and the new wire codeUnsupported.files-sdk/clientand theuseFilesbindings turn both back into the matchingFilesErrorcode, and map any wire code they don't know toProvider. When presigning an upload, the gateway falls back to its proxy only for anUnsupportedorInvalidrefusal; a backend failure or an abort now surfaces instead of being masked as a proxy target.filesnow exits5for aProviderfailure, so a script can tell "the backend failed, retry" from "the command is wrong".Invalid,Unsupported, andReadOnlyexit2, like a usage error. A missing optional@modelcontextprotocol/sdkforfiles mcpis reported asUnsupported.9765e54:head(),list(),listAll(), andsearch()now return aFileInfo({ key, size, contentType, etag?, lastModified?, metadata? }), metadata with no body, instead of aStoredFile.text()/arrayBuffer()/blob()/stream()accessors that quietly issued a full download when called. Reading bytes is now always an explicitdownload(), which still returns aStoredFile. AStoredFileis now aFileInfoplus theFile-likename/typealiases and the body accessors.contentTypewhere you readtype, andkeywhere you readname.UploadResultis now the sameFileInfoshape.versioning()'srestoreVersion()andsoftDelete()'srestoreTrashed()resolve toConfiguration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.