v1.1.0

Minor
  • 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 the region cluster code (us-iad-1, nl-ams-1, fr-par-1, the older us-east-1/eu-central-1/ap-south-1 clusters, etc.) as https://<region>.linodeobjects.com and overridable, virtual-hosted-style addressing, "Akamai error" provider label, and AKAMAI_ACCESS_KEY_ID / AKAMAI_SECRET_ACCESS_KEY env-var fallbacks. publicBaseUrl accepts a public-bucket origin (https://<bucket>.<region>.linodeobjects.com) or a custom CNAME for unsigned URLs; otherwise url() returns a presigned GetObject (1-hour default).

  • Add Box adapter (files-sdk/box) for personal Box and Box Enterprise via the official box-typescript-sdk-gen SDK. Box files live by ID rather than by path, so the adapter walks rootFolderId and translates virtual keys (docs/a.txt) into nested Box subfolders, auto-creating intermediate folders on upload() and racing-recovering on item_name_in_use. Five auth shapes (pre-built client, developerToken, oauth with refresh-token seeding, ccg with enterpriseId or userId, and jwt with configJsonString or configFilePath) cover scripts, user apps, and enterprise installs; env-var fallback via BOX_DEVELOPER_TOKEN. Token lifecycle is handled by the SDK's built-in Authentication classes — no manual refresh bookkeeping. Direct upload() uses single-call uploads.uploadFile up to 50 MB and switches to chunkedUploads.uploadBigFile automatically; existing leaf names route through uploadFileVersion (overwrite). url() mints a signed download URL via getDownloadFileUrl by default; with publicByDefault: true, upload() also calls addShareLinkToFile (open access) and url() returns the link's download_url; responseContentDisposition always throws (no override on Box URLs). signedUploadUrl() throws — Box uploads require a multipart POST with both an attributes JSON part and the file bytes part, which fits neither the SDK's PUT-with-headers nor POST-with-form-fields shape; use upload() server-side or Box's UI Elements / Content Uploader for browser flows. list() returns immediate-children files only at rootFolderId (no recursion, subfolders filtered out, prefix matched client-side, offset encoded as a numeric cursor). User metadata and cacheControl throw (Box exposes file metadata via classifications and metadata templates — drop to raw.fileMetadata.* if you need it).

  • Add DigitalOcean Spaces adapter (files-sdk/digitalocean-spaces). Thin wrapper over the S3 adapter with Spaces defaults: endpoint derived from region (https://${region}.digitaloceanspaces.com), virtual-hosted addressing, "Spaces error" provider label, and DO_SPACES_KEY / DO_SPACES_SECRET env-var fallbacks. publicBaseUrl accepts 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 official dropbox SDK. Path-addressable like OneDrive, so virtual keys map directly to Dropbox paths — no virtual-key cache. Four auth shapes (pre-built client, static or callable accessToken, OAuth refresh-token flow with refreshToken + appKey (+ optional appSecret), and env-var fallback via DROPBOX_ACCESS_TOKEN or DROPBOX_REFRESH_TOKEN + DROPBOX_APP_KEY (+ DROPBOX_APP_SECRET)). Refresh tokens are exchanged at api.dropboxapi.com/oauth2/token and cached until ~60s before expiry. url() mints a 4-hour temporary link via filesGetTemporaryLink by default; with publicByDefault: true, upload() also creates a public shared link and url() returns it (rewritten to ?dl=1 for direct download); expiresIn is capped at Dropbox's 14400s (4h) maximum and responseContentDisposition always 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; use upload() or drop to raw.filesGetTemporaryUploadLink(...). Direct upload() uses single-call filesUpload up to 150 MB and switches to filesUploadSession* (chunked, up to 350 GB) automatically; user metadata and cacheControl throw (Dropbox files have no native arbitrary-metadata field — use raw with property_groups if you need it).

  • Add Google Drive adapter (files-sdk/google-drive) via the official @googleapis/drive v3 client. Drive has no native key field, so the adapter maps virtual keys onto appProperties.fsdkKey and amortizes lookups with a per-instance LRU cache (configurable via fileIdCacheSize, defaults to 1024). Three auth shapes: inline service-account credentials, a keyFilename JSON path, or 3-legged oauth refresh tokens — plus a pre-built client escape hatch (note: signedUploadUrl() requires an auth handle and throws when constructed via client). signedUploadUrl() initiates a Drive resumable session and returns the session URL as a one-shot PUT (maxSize is forwarded as X-Upload-Content-Length advisory only; minSize is ignored). url() requires publicByDefault: true (grants anyone, reader on upload and returns the permanent Drive download URL); expiresIn ignored, responseContentDisposition always throws. Service-account workloads should target a Shared Drive via driveId to avoid the 15 GB personal quota. Caller metadata keys starting with fsdk are reserved.

  • Add Hetzner Object Storage adapter (files-sdk/hetzner). Thin wrapper over the S3 adapter with Hetzner defaults: endpoint derived from the region location code (fsn1, nbg1, hel1) as https://<region>.your-objectstorage.com and overridable, virtual-hosted-style addressing, "Hetzner error" provider label, and HCLOUD_ACCESS_KEY_ID / HCLOUD_SECRET_ACCESS_KEY env-var fallbacks. publicBaseUrl accepts a custom CNAME or proxy host for unsigned URLs; otherwise url() returns a presigned GetObject (1-hour default).

  • Add Netlify Blobs adapter (files-sdk/netlify-blobs). Wraps the @netlify/blobs SDK with site-scoped or deploy-scoped stores, configurable consistency, and a metadata round-trip that packs contentType/size/lastModified/cacheControl plus user metadata into Netlify's metadata map so head()/download() return rich fields. Auto-detects credentials from Netlify's runtime context (NETLIFY_BLOBS_CONTEXT) when available, with explicit siteID/token overrides falling back to NETLIFY_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-item head()); url() and signedUploadUrl() 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 (clientCredentials for app-only, oauth for delegated refresh-token flow, accessToken for caller-managed tokens, and a pre-built client escape hatch) and four drive targets (/me/drive, driveId, siteId, userId). signedUploadUrl() returns a Graph upload-session URL (one-shot PUT, advisory maxSize/minSize); url() requires publicByDefault: true and creates an anonymous-view share link (Graph has no signed URL primitive, expiresIn ignored). copy() polls Graph's async copy monitor with a configurable copyTimeoutMs. Direct upload() is capped at OneDrive's 250 MB simple-upload limit; user metadata and cacheControl throw (Graph drive items have no native arbitrary-metadata field — use raw for Open Extensions).

  • Add Storj adapter (files-sdk/storj). Thin wrapper over the S3 adapter with Storj defaults: endpoint defaults to https://gateway.storjshare.io (Gateway MT, the hosted multi-tenant gateway) and is overridable for self-hosted Gateway ST, path-style addressing on, region defaulted to us-east-1 (the gateway ignores it for routing), "Storj error" provider label, and STORJ_ACCESS_KEY_ID / STORJ_SECRET_ACCESS_KEY env-var fallbacks. publicBaseUrl accepts a linksharing prefix like https://link.storjshare.io/raw/<accessGrant>/<bucket> for unsigned URLs.

  • Add UploadThing adapter (files-sdk/uploadthing). Maps the user-supplied key onto UploadThing's customId, supports public-read and private ACLs, signs UFS presigned PUT URLs via Web Crypto HMAC-SHA256, and falls back to HEAD-on-URL for head() and read-then-write for copy() since UploadThing has no native primitives for those.

Patch
  • Extract shared adapter helpers into src/internal/core.ts so authoring a new adapter is less boilerplate. The new module exports DEFAULT_URL_EXPIRES_IN, joinPublicUrl, resolveUrlStrategy (the two-state public-vs-sign decision, with responseContentDisposition always forcing signing), normalizeBody (Body → Uint8Array | ReadableStream<Uint8Array> + content-type/length), and makeErrorMapper (factory for the per-provider mapXError scaffold — code-set lookup, HTTP-status fallback, FilesError pass-through). The s3, azure, gcs, supabase, r2, fs, and uploadthing adapters now consume these helpers; supabase keeps its own normalizeBody because Blob pass-through is required for multipart uploads, and r2's url() keeps its three-state hybrid logic. mapS3Error retains 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 raw getter's pre/post-init behavior) and for fs uploads with ArrayBuffer and ArrayBufferView bodies plus rejection of keys that resolve to the adapter root. Drops the unreachable ReadableStream branch in fs/bodyToBytes — stream uploads route through writeStreamToTempThenRename, so the parameter type is narrowed to Exclude<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 numeric statusCode fallback and Date/number lastModified parsing, R2 binding copy with put failure, and concurrent reads on a lazy StoredFile sharing the in-flight cache promise.