files-sdk@1.6.0
Minor Changes
-
12d6218: Bring the CLI (and MCP server) to full parity with the SDK surface.
Every
Filescapability is now reachable from thefilesbinary:- Global
--key-prefixscopes every operation under a base path (the instance prefix fromnew Files({ prefix }), distinct from the one-offlist --prefixfilter). Global--timeout/--retriesset the per-attempt timeout and retry count for all commands. download --range start-enddownloads a byte range (0-based, inclusive), e.g.0-1023or1024-.upload --multipart(with--part-size/--multipart-concurrency) uploads large objects in parallel parts.head/exists/deleteaccept--concurrencyand--stop-on-errorto tune the bulk fan-out for many keys.list --allwalks every page (following the cursor) and returns all items in one result.upload --dir <localDir>uploads a whole local tree (keyed by relative path, content type inferred per file), anddownload <keys...> --out-dir <dir>downloads many keys into a directory — both built on the SDK's bulk array forms.transfercopies every object from the configured (source) provider to another provider given as a JSON config (--to), streaming each body across backends.--prefixfilters the walk and--no-overwriteskips keys already present at the destination.
The MCP server mirrors all of the above: the
uploadtool takesmultipart,downloadtakes a byterange, thehead/exists/deletetools takeconcurrency/stopOnError,listtakesall, and a newtransfertool copies objects across providers. The global--key-prefix/--timeout/--retriesbind to the server'sFilesinstance at startup. - Global
-
0bb7ca3: Add
transferfor cross-provider migration.transfer(source, dest, options?)streams every object from oneFilesinstance to another — the one operation the unified surface uniquely enables, sincecopy/movelive inside a single adapter. It's built entirely on public primitives (the source'slistAll+ streamingdownload, the destination'sexists+upload), so no adapter implements anything new.import { Files, transfer } from "files-sdk"; import { s3 } from "files-sdk/s3"; import { r2 } from "files-sdk/r2"; const from = new Files({ adapter: s3({ bucket: "old" }) }); const to = new Files({ adapter: r2({ bucket: "new", accountId, accessKeyId, secretAccessKey }), }); const { transferred, skipped, errors } = await transfer(from, to, { prefix: "uploads/", onProgress: ({ done, key }) => console.log(done, key), });
Both sides are full
Filesinstances, so each leg honors its ownprefix, retries, timeouts, and hooks. Each object is streamed download-to-upload — the destination never buffers a whole large file. Body, content type, and user metadata travel;etag/lastModifiedare destination-assigned andCache-Controlis not carried.Like the bulk array methods,
transferdoesn't throw on partial failure: results come back as{ transferred, skipped?, errors? }in walk order. Options coverprefix,transformKey,overwrite(skip keys already present),concurrency(default 8),limit(walk page size),stopOnError(sequential, bail at first failure),signal, andonProgress. -
5d24bc8: Add
hookstonew Files(...)so applications can observe SDK activity withonAction,onError, andonRetry.Each hook is fire-and-forget (called, not awaited) and receives a small, caller-facing event — the operation
type, the publickey/keys(orfrom/toforcopy), timing, and the final result or error. It mirrors the lightweightonProgresscallback style. -
c50a55a: Add an in-memory adapter at
files-sdk/memory. It implements the fullAdaptercontract backed by aMap, so you can test code that usesFileswithout touching disk or real storage — the same swap-in-via-env story as the other adapters, but with nothing to clean up.import { Files } from "files-sdk"; import { memory } from "files-sdk/memory"; const files = new Files({ adapter: memory() }); await files.upload("hello.txt", "hi"); (await files.download("hello.txt")).text(); // "hi"
Zero dependencies and isomorphic (no
node:fs/node:crypto), so it runs unchanged in Node, Bun, Deno, the browser, and edge runtimes. Passinitialto pre-populate fixtures, and reach intoadapter.raw(the backingMap) to inspect or reset the store between tests:const adapter = memory({ initial: { "users/1.json": '{"id":1}' } }); adapter.raw.clear();
url()returns an opaque, non-fetchablememory://${key}andsignedUploadUrl()amemory://placeholder — there's no server backing the store. It's a test/reference adapter, not for production. -
67349f4: Add
moveandlistAll.files.move(from, to, options?)renames a key. It uses the adapter's native rename where one exists (thefsadapter renames in place atomically; Cloudinary uses its server-siderename, keeping the sameasset_idwith no re-upload) and otherwise falls back tocopy+delete— the same two-step every object store takes, since none offer an atomic move. Moving a key onto itself is a no-op, so the fallback can't copy-then-delete a file out of existence.movethrows on Convex, wherecopydoes (immutable storage ids, no rename).await files.move("uploads/tmp-abc.png", "avatars/user-123.png");
FileHandlegains the matchingmoveTo/moveFrom, andmovefires the lifecycle hooks (onAction/onError/onRetry) with a new"move"action type.files.listAll(options?)walks every page as an async iterable, following the cursor for you:for await (const file of files.listAll({ prefix: "avatars/" })) { console.log(file.key, file.size); }
prefixscopes the walk andlimitsets the per-page size; each page is a reallistcall, so retries, timeouts, and prefix scoping all apply.Custom adapters can implement an optional
move(from, to, opts?)to provide a native rename; omitting it keeps the copy + delete fallback.The CLI gains a
move <from> <to>command and the MCP server exposes a matchingmovetool. -
a96874f:
uploadnow accepts amultipartoption for uploading large bodies in parallel parts.await files.upload("backups/db.tar", stream, { multipart: true, // or { partSize, concurrency } });
- S3 and the S3-compatible adapters (incl. R2 over HTTP) run multipart through
@aws-sdk/lib-storage, falling back to a singlePutObjectfor small bodies. Unknown-lengthReadableStreambodies now use multipart automatically, even without the flag. - OneDrive uploads above its 250 MB simple-upload limit (and any
multipartrequest) now go through a chunked upload session instead of throwing — large files just work. - GCS and Firebase Storage switch to a resumable upload when
multipartis set;partSizemaps to the chunk size. - Azure Blob maps
partSize/concurrencyto its parallel block-upload tuning. - Dropbox now streams
ReadableStreambodies through its upload session chunk-by-chunk instead of buffering the whole file in memory;partSizetunes the chunk size (rounded to a 4 MiB multiple). - The array form of
uploadaccepts a per-itemmultiparttoggle/tuning too.
Other adapters already stream natively or only accept a fully-buffered body, so they ignore the option.
- S3 and the S3-compatible adapters (incl. R2 over HTTP) run multipart through
-
64cf324:
downloadnow accepts arangeoption for fetching a contiguous byte slice of an object — the primitive behind video seeking and resumable downloads.// Bytes 0–1023 (end is inclusive, matching the HTTP Range header) → 1024 bytes. const head = await files.download("video.mp4", { range: { start: 0, end: 1023 }, }); // Omit end to read from an offset to EOF — e.g. resume an interrupted download. const rest = await files.download("video.mp4", { range: { start: 1024 } });
Both bounds are 0-based and
endis inclusive, mirroring thebytes=start-endrequest the supporting adapters issue. The returnedStoredFilecarries just the requested bytes and reports the range length as itssize.rangeworks withas: "stream"so you never buffer the whole slice.- S3 and every S3-compatible adapter (R2 over HTTP, MinIO, DigitalOcean Spaces, Wasabi, Tigris, Backblaze B2, Storj, Hetzner, Akamai, and the rest of the
s3()family) issue a rangedGetObject. - Bun S3 slices via
S3File.slice, GCS and Firebase Storage viacreateReadStream/downloadbyte offsets, Azure Blob via its offset/count download, and the R2 Workers binding via its nativerangeoption. - The local
fsadapter reads only the requested bytes off disk, and the in-memory adapter slices its buffer. - The fetch-based adapters — UploadThing, Box, Vercel Blob (public), Cloudinary, PocketBase, Dropbox, OneDrive, SharePoint, and Google Drive — send an HTTP
Rangeheader and verify the host replied206 Partial Content, throwing if it ignored the range and returned the whole object (so the bandwidth saving is never silently lost).
Adapters whose provider has no range primitive (Supabase, Appwrite, Netlify Blobs, Bunny Storage, Convex, and Vercel Blob private blobs) throw a
FilesErrorrather than downloading the whole object and slicing it client-side. Custom adapters opt in by settingsupportsRange: trueand honoringDownloadOptions.range; theFileswrapper validates the range and gates unsupported adapters before any provider call. - S3 and every S3-compatible adapter (R2 over HTTP, MinIO, DigitalOcean Spaces, Wasabi, Tigris, Backblaze B2, Storj, Hetzner, Akamai, and the rest of the
-
841175a:
uploadnow accepts anonProgresscallback for reporting realtime progress — e.g. to drive a progress bar.await files.upload("big.zip", stream, { onProgress: ({ loaded, total }) => console.log( total ? `${Math.round((loaded / total) * 100)}%` : `${loaded} bytes` ), });
Granularity depends on the body and the adapter:
- A
ReadableStreambody is reported byte-by-byte on every adapter, as the bytes are consumed (totalis omitted, since the length is unknown). - A buffered body (
File,Blob,ArrayBuffer,Uint8Array,string) reports{ loaded: 0, total }then{ loaded: total, total }by default. - Adapters with a native upload-progress hook report true byte-level progress for every body type (buffered included): S3 and the S3-compatible adapters, R2 (HTTP), Azure Blob, Google Cloud Storage, Firebase Storage, Vercel Blob, and FTP. The S3 family uses
@aws-sdk/lib-storage(a new optional peer dependency loaded only whenonProgressis used) and also gains multipart for large files; GCS and Firebase Storage switch to a resumable upload whenonProgressis set.
The array form of
uploadacceptsonProgresstoo; each report carries the item'skey. Custom adapters can opt into reporting progress themselves by settingreportsUploadProgress: trueand callingopts.onProgress. - A
Patch Changes
-
52daa66: Update bundled and peer dependencies.
The CLI's
commanderruntime dependency moves to v14. Several optional provider-SDK peer floors are raised to the majors now built and tested against:@anthropic-ai/claude-agent-sdk→^0.3.0(claude adapter)@googleapis/drive→^20.0.0(google-drive adapter)google-auth-library→^10.0.0(gcs / google-drive auth)node-appwrite→^25.0.0(appwrite adapter)pocketbase→^0.27.0(pocketbase adapter)
No public API or behaviour changes. If you use one of the adapters above, upgrade its peer to the new major.
-
26989e0: The published package now ships its documentation. The full docs are bundled at
node_modules/files-sdk/docs(per-adapter pages underdocs/adapters/, AI tools underdocs/ai/, plusoverview,api,cli,providers, andtroubleshooting), so tools and agents can read version-matched reference material offline instead of relying on the hosted site. -
7027836: In-memory adapter (
files-sdk/memory): givemetadatathe same value semantics the bytes already have. The adapter cloned an entry's bytes on the way in but stored and returned themetadataobject by reference, so three aliases leaked: mutating the object passed toupload()(or aninitialseed) after the call reached into the store, mutating ahead()/download()result'smetadatareached back into the store, andcopy()left the source and destination sharing one mutable metadata object — mutating one silently changed the other. Metadata is now shallow-cloned on write and on read, so each stored entry owns its own copy and every read hands back a fresh one, matching how a real backend round-trips metadata. Bytes behavior is unchanged. -
293ba1d:
onProgressis now truly fire-and-forget: a throwing progress reporter can no longer fail or retry the upload it observes. Previously, a buffered upload's final progress report ran inside the retryable attempt, so a throw was caught by the retry layer, mislabelled a provider error, and re-uploaded the body up toretriestimes before rejecting; on the streaming path a throw errored the underlying stream and failed the upload. All three wrapper-drivenonProgresscalls now route through the same swallow-and-ignore guard thehookscallbacks use, matching the contract already documented onFilesHooks("a hook that throws can never fail the operation it observes"). Self-reporting adapters (reportsUploadProgress) are unaffected — they own their own reporting. -
1b978b9: Fix
signedUploadUrl({ maxSize })failing with501 Not Implementedon Cloudflare R2.The R2 adapter inherited the S3 adapter's behaviour of routing
maxSizethrough a presignedPOSTpolicy (content-length-range). Cloudflare R2 does not implement the S3POST ObjectAPI, so those uploads failed at upload time with501 Not Implemented.R2 now throws a clear
Providererror whenmaxSizeis passed (matching how the Azure and Supabase adapters handle the same limitation), instead of handing back a POST form R2 can't serve. OmitmaxSizeto get a presignedPUTURL, and enforce upload caps at your application gateway. Fixes #49.