v1.1.0
Add Akamai Cloud Object Storage adapter (
files-sdk/akamai), formerly Linode Object Storage. Thin wrapper over the S3 adapter with Akamai defaults: endpoint derived from theregioncluster code (us-iad-1,nl-ams-1,fr-par-1, the olderus-east-1/eu-central-1/ap-south-1clusters, etc.) ashttps://<region>.linodeobjects.comand overridable, virtual-hosted-style addressing,"Akamai error"provider label, andAKAMAI_ACCESS_KEY_ID/AKAMAI_SECRET_ACCESS_KEYenv-var fallbacks.publicBaseUrlaccepts a public-bucket origin (https://<bucket>.<region>.linodeobjects.com) or a custom CNAME for unsigned URLs; otherwiseurl()returns a presigned GetObject (1-hour default).Add Box adapter (
files-sdk/box) for personal Box and Box Enterprise via the officialbox-typescript-sdk-genSDK. Box files live by ID rather than by path, so the adapter walksrootFolderIdand translates virtual keys (docs/a.txt) into nested Box subfolders, auto-creating intermediate folders onupload()and racing-recovering onitem_name_in_use. Five auth shapes (pre-builtclient,developerToken,oauthwith refresh-token seeding,ccgwithenterpriseIdoruserId, andjwtwithconfigJsonStringorconfigFilePath) cover scripts, user apps, and enterprise installs; env-var fallback viaBOX_DEVELOPER_TOKEN. Token lifecycle is handled by the SDK's built-inAuthenticationclasses — no manual refresh bookkeeping. Directupload()uses single-calluploads.uploadFileup to 50 MB and switches tochunkedUploads.uploadBigFileautomatically; existing leaf names route throughuploadFileVersion(overwrite).url()mints a signed download URL viagetDownloadFileUrlby default; withpublicByDefault: true,upload()also callsaddShareLinkToFile(open access) andurl()returns the link'sdownload_url;responseContentDispositionalways throws (no override on Box URLs).signedUploadUrl()throws — Box uploads require a multipart POST with both anattributesJSON part and the file bytes part, which fits neither the SDK's PUT-with-headers nor POST-with-form-fields shape; useupload()server-side or Box's UI Elements / Content Uploader for browser flows.list()returns immediate-children files only atrootFolderId(no recursion, subfolders filtered out, prefix matched client-side, offset encoded as a numeric cursor). UsermetadataandcacheControlthrow (Box exposes file metadata via classifications and metadata templates — drop toraw.fileMetadata.*if you need it).Add DigitalOcean Spaces adapter (
files-sdk/digitalocean-spaces). Thin wrapper over the S3 adapter with Spaces defaults: endpoint derived fromregion(https://${region}.digitaloceanspaces.com), virtual-hosted addressing,"Spaces error"provider label, andDO_SPACES_KEY/DO_SPACES_SECRETenv-var fallbacks.publicBaseUrlaccepts a Spaces CDN host (https://${bucket}.${region}.cdn.digitaloceanspaces.com) or a custom CNAME.Add Dropbox adapter (
files-sdk/dropbox) for personal Dropbox and Dropbox Business via the officialdropboxSDK. Path-addressable like OneDrive, so virtual keys map directly to Dropbox paths — no virtual-key cache. Four auth shapes (pre-builtclient, static or callableaccessToken, OAuth refresh-token flow withrefreshToken+appKey(+ optionalappSecret), and env-var fallback viaDROPBOX_ACCESS_TOKENorDROPBOX_REFRESH_TOKEN+DROPBOX_APP_KEY(+DROPBOX_APP_SECRET)). Refresh tokens are exchanged atapi.dropboxapi.com/oauth2/tokenand cached until ~60s before expiry.url()mints a 4-hour temporary link viafilesGetTemporaryLinkby default; withpublicByDefault: true,upload()also creates a public shared link andurl()returns it (rewritten to?dl=1for direct download);expiresInis capped at Dropbox's 14400s (4h) maximum andresponseContentDispositionalways throws (no override on Dropbox links).signedUploadUrl()throws — Dropbox's temporary upload link expects POST with a raw body, which fits neither the SDK's PUT-with-headers nor POST-with-form-fields shape; useupload()or drop toraw.filesGetTemporaryUploadLink(...). Directupload()uses single-callfilesUploadup to 150 MB and switches tofilesUploadSession*(chunked, up to 350 GB) automatically; usermetadataandcacheControlthrow (Dropbox files have no native arbitrary-metadata field — userawwithproperty_groupsif you need it).Add Google Drive adapter (
files-sdk/google-drive) via the official@googleapis/drivev3 client. Drive has no native key field, so the adapter maps virtual keys ontoappProperties.fsdkKeyand amortizes lookups with a per-instance LRU cache (configurable viafileIdCacheSize, defaults to 1024). Three auth shapes: inline service-accountcredentials, akeyFilenameJSON path, or 3-leggedoauthrefresh tokens — plus a pre-builtclientescape hatch (note:signedUploadUrl()requires an auth handle and throws when constructed viaclient).signedUploadUrl()initiates a Drive resumable session and returns the session URL as a one-shot PUT (maxSizeis forwarded asX-Upload-Content-Lengthadvisory only;minSizeis ignored).url()requirespublicByDefault: true(grantsanyone, readeron upload and returns the permanent Drive download URL);expiresInignored,responseContentDispositionalways throws. Service-account workloads should target a Shared Drive viadriveIdto avoid the 15 GB personal quota. Callermetadatakeys starting withfsdkare reserved.Add Hetzner Object Storage adapter (
files-sdk/hetzner). Thin wrapper over the S3 adapter with Hetzner defaults: endpoint derived from theregionlocation code (fsn1,nbg1,hel1) ashttps://<region>.your-objectstorage.comand overridable, virtual-hosted-style addressing,"Hetzner error"provider label, andHCLOUD_ACCESS_KEY_ID/HCLOUD_SECRET_ACCESS_KEYenv-var fallbacks.publicBaseUrlaccepts a custom CNAME or proxy host for unsigned URLs; otherwiseurl()returns a presigned GetObject (1-hour default).Add Netlify Blobs adapter (
files-sdk/netlify-blobs). Wraps the@netlify/blobsSDK with site-scoped or deploy-scoped stores, configurable consistency, and a metadata round-trip that packscontentType/size/lastModified/cacheControlplus user metadata into Netlify's metadata map sohead()/download()return rich fields. Auto-detects credentials from Netlify's runtime context (NETLIFY_BLOBS_CONTEXT) when available, with explicitsiteID/tokenoverrides falling back toNETLIFY_SITE_ID/NETLIFY_API_TOKEN/NETLIFY_BLOBS_TOKEN.copy()is read-then-write since Netlify has no native copy primitive;list()returns key + etag (rich metadata requires a per-itemhead());url()andsignedUploadUrl()throw because Netlify Blobs has no public URL or presigned-upload primitive.Add OneDrive adapter (
files-sdk/onedrive) for OneDrive personal, OneDrive for Business, and SharePoint document libraries via Microsoft Graph (@microsoft/microsoft-graph-client+@azure/identity). Path-addressable like the underlying API, so virtual keys map onto real OneDrive paths — no virtual-key cache, no reserved-metadata namespace. Four auth shapes (clientCredentialsfor app-only,oauthfor delegated refresh-token flow,accessTokenfor caller-managed tokens, and a pre-builtclientescape hatch) and four drive targets (/me/drive,driveId,siteId,userId).signedUploadUrl()returns a Graph upload-session URL (one-shot PUT, advisorymaxSize/minSize);url()requirespublicByDefault: trueand creates an anonymous-view share link (Graph has no signed URL primitive,expiresInignored).copy()polls Graph's async copy monitor with a configurablecopyTimeoutMs. Directupload()is capped at OneDrive's 250 MB simple-upload limit; usermetadataandcacheControlthrow (Graph drive items have no native arbitrary-metadata field — userawfor Open Extensions).Add Storj adapter (
files-sdk/storj). Thin wrapper over the S3 adapter with Storj defaults:endpointdefaults tohttps://gateway.storjshare.io(Gateway MT, the hosted multi-tenant gateway) and is overridable for self-hosted Gateway ST, path-style addressing on, region defaulted tous-east-1(the gateway ignores it for routing),"Storj error"provider label, andSTORJ_ACCESS_KEY_ID/STORJ_SECRET_ACCESS_KEYenv-var fallbacks.publicBaseUrlaccepts a linksharing prefix likehttps://link.storjshare.io/raw/<accessGrant>/<bucket>for unsigned URLs.Add UploadThing adapter (
files-sdk/uploadthing). Maps the user-supplied key onto UploadThing'scustomId, supports public-read and private ACLs, signs UFS presigned PUT URLs via Web Crypto HMAC-SHA256, and falls back to HEAD-on-URL forhead()and read-then-write forcopy()since UploadThing has no native primitives for those.
Extract shared adapter helpers into
src/internal/core.tsso authoring a new adapter is less boilerplate. The new module exportsDEFAULT_URL_EXPIRES_IN,joinPublicUrl,resolveUrlStrategy(the two-state public-vs-sign decision, withresponseContentDispositionalways forcing signing),normalizeBody(Body →Uint8Array | ReadableStream<Uint8Array>+ content-type/length), andmakeErrorMapper(factory for the per-providermapXErrorscaffold — code-set lookup, HTTP-status fallback,FilesErrorpass-through). The s3, azure, gcs, supabase, r2, fs, and uploadthing adapters now consume these helpers; supabase keeps its ownnormalizeBodybecause Blob pass-through is required for multipart uploads, and r2'surl()keeps its three-state hybrid logic.mapS3Errorretains its 2-arg legacy signature for the S3-compatible wrappers (R2 HTTP, MinIO, DigitalOcean Spaces, Storj, Hetzner, Akamai). No public-API changes.Improve test coverage and remove dead code in the fs adapter. Adds tests for r2's HTTP-path delegation (copy/delete/download/head/list/signedUploadUrl proxies to the lazy-loaded inner s3 adapter, plus the
rawgetter's pre/post-init behavior) and for fs uploads withArrayBufferandArrayBufferViewbodies plus rejection of keys that resolve to the adapter root. Drops the unreachableReadableStreambranch infs/bodyToBytes— stream uploads route throughwriteStreamToTempThenRename, so the parameter type is narrowed toExclude<Body, ReadableStream<Uint8Array>>to enforce that at the type level.Further hardens coverage of edge paths across the fs, azure, supabase, r2, and stored-file modules: corrupt/partial sidecar JSON handling, lazy-body errors when an underlying file is removed, atomic upload cleanup when rename fails (buffer + stream paths), non-ENOENT delete errors, Azure stream downloads with missing
readableStreamBody, anonymous Azure copy source URLs, Supabase numericstatusCodefallback and Date/numberlastModifiedparsing, R2 binding copy with put failure, and concurrent reads on a lazyStoredFilesharing the in-flight cache promise.