import { FilesError } from "./internal/errors.js"; import type { EventCapability, FileEvent } from "./internal/events.js"; import { FOLD_PROVIDER_EVENT } from "./internal/events.js"; import type { ConditionalActionType, Receipt } from "./internal/receipts.js"; import type { ResumableDriver, ResumableDriverOptions, ResumableUploadSession, UploadControl } from "./internal/resumable.js"; export { rejectConditional } from "./internal/conditional.js"; export { FilesError, type FilesErrorCode } from "./internal/errors.js"; export type { EventCapability, EventFormat, FileEvent, FileEventSource, FileEventType, } from "./internal/events.js"; export { UploadControl } from "./internal/resumable.js"; export type { OffsetResumableDriver, PartMeta, PartsResumableDriver, ResumableCallOptions, ResumableDriver, ResumableDriverOptions, ResumableUploadSession, UploadControlStatus, } from "./internal/resumable.js"; export type { ConditionalActionType, Receipt, ReceiptOp, } from "./internal/receipts.js"; export type { BodySource } from "./internal/stored-file.js"; export { createStoredFile } from "./internal/stored-file.js"; export { sync, type SyncCompare, type SyncOptions, type SyncProgress, type SyncResult, } from "./internal/sync.js"; export { transfer, type TransferOptions, type TransferProgress, type TransferResult, } from "./internal/transfer.js"; export { type Provider, PROVIDER_NAMES, type ProviderSlug, } from "./providers/index.js"; export type Body = Blob | File | ReadableStream | ArrayBuffer | ArrayBufferView | Uint8Array | string; export interface RetryBackoffContext { /** * Retry attempt number, starting at 1 for the first retry after the * initial failed call. */ attempt: number; error: FilesError; } export type RetryOptions = number | { max: number; backoff?: (ctx: RetryBackoffContext) => number; }; export interface OperationOptions { /** * Abort the operation when this signal is aborted. When both constructor * and per-call signals are provided, either one can abort the call. */ signal?: AbortSignal; /** * Timeout in milliseconds, applied to each attempt (not the call as a * whole). A timeout aborts the operation and is not retried. `0` or a * negative value disables timeout handling. */ timeout?: number; /** * Retry provider failures. A number is treated as `{ max: number }`. */ retries?: RetryOptions; } /** A native compare-and-set predicate for a single upload. */ export type UploadCondition = { type: "create"; } | { type: "replace"; etag: string; }; /** * A single upload-progress report. Passed to {@link UploadOptions.onProgress} * (and {@link UploadManyOptions.onProgress}, which also carries the item `key`). */ export interface UploadProgress { /** Cumulative bytes sent so far. */ loaded: number; /** * Total bytes to send, when known. Present for buffered bodies (`File`, * `Blob`, `ArrayBuffer`, `Uint8Array`, `string`); omitted for * `ReadableStream` bodies of unknown length, where only `loaded` can be * reported. */ total?: number; } /** * Tuning for multipart uploads. Pass `multipart: true` for sensible defaults, * or an object to override them. See {@link UploadOptions.multipart}. */ export interface MultipartOptions { /** * Size of each uploaded part, in bytes. Defaults to 5 MiB * (`5 * 1024 * 1024`) — also the S3-enforced minimum for every part except * the last. Adapters that chunk natively round this to their own valid * granularity (OneDrive to a 320-KiB multiple, GCS/Firebase to 256 KiB). */ partSize?: number; /** * How many parts upload in parallel. Defaults to `4`. Higher values trade * memory (up to `partSize * concurrency` buffered at once) for throughput. */ concurrency?: number; } export interface UploadOptions extends OperationOptions { /** * Apply the upload atomically only when the destination is absent * (`create`) or still has the supplied strong ETag (`replace`). Conditional * uploads require a native adapter primitive and are unavailable for bulk, * multipart, and resumable uploads. */ condition?: UploadCondition; /** * MIME type stored alongside the object and returned to readers in the * `Content-Type` response header. Inferred from `File` / `Blob` `type` * when not set; falls back to `application/octet-stream`. */ contentType?: string; /** * `Cache-Control` header stored on the object. Sent verbatim to the * provider; controls how downstream caches and browsers cache reads of * this key. * * **Throws** a {@link FilesError} on adapters with no cache-control field * (FTP, SFTP, WebDAV, Dropbox, Box, OneDrive, SharePoint, Cloudinary, * Appwrite, PocketBase, Bunny Storage, Convex, UploadThing, Bun's S3) rather * than silently dropping it — check {@link AdapterCapabilities.cacheControl} to * branch at runtime. */ cacheControl?: string; /** * Arbitrary user metadata stored alongside the object. Returned by * `head()` and `list()` where the provider supports it. * * **Throws** a {@link FilesError} on adapters with no user-metadata primitive * (Vercel Blob, UploadThing, FTP, SFTP, WebDAV, Dropbox, Box, OneDrive, * SharePoint, Cloudinary, Appwrite, PocketBase, Bunny Storage, Convex, Bun's * S3) rather than silently dropping it, mirroring the * {@link DownloadOptions.range} gate. * An empty object is treated as "no metadata" and never throws. Check * {@link AdapterCapabilities.metadata} to branch at runtime. */ metadata?: Record; /** * Called as the upload makes progress, for driving a progress bar. * Fire-and-forget: a throw from it is swallowed and never fails (or * retries) the upload. * * Granularity depends on the body and the adapter: * - A `ReadableStream` body is reported byte-by-byte as the adapter * consumes it (`total` is omitted unless the length is known). * - A buffered body (`File`, `Blob`, `ArrayBuffer`, `Uint8Array`, `string`) * is handed to the provider whole, so it reports `{ loaded: 0, total }` * then `{ loaded: total, total }` — unless the adapter reports true * progress itself (see below). * - **S3 and the S3-compatible adapters** report true byte-level progress * for every body type (including multipart for large files). This path * uses `@aws-sdk/lib-storage`, an optional peer dependency that must be * installed when `onProgress` is used with those adapters. * * Only fires while the upload is in flight and on success; a failed upload * does not emit a final event. On retry, progress restarts. */ onProgress?: (progress: UploadProgress) => void; /** * Upload the body in parallel parts instead of a single request. Pass `true` * for sensible defaults (5 MiB parts, 4 in flight), or an object to tune * `partSize` / `concurrency`. Multipart is the robust path for large objects * and for `ReadableStream` bodies of unknown length: a single PUT must buffer * or know the length up front, while multipart streams part-by-part. * * On S3-family adapters (on the `aws-sdk` client) this routes through * `@aws-sdk/lib-storage` (an optional peer dependency) and is * **auto-engaged for unknown-length streams** even when this flag is unset. * OneDrive, GCS, Firebase, and Azure map it to their native chunking; other * adapters already stream or chunk transparently and ignore it. The one * exception is the S3 `fetch` client (`s3Fetch()`, and R2 / MinIO / RustFS * with `client: "fetch"`), which has no multipart path and **throws** rather * than silently buffering the body into a single PUT. */ multipart?: boolean | MultipartOptions; /** * Drive the upload through a pause-able, resumable session. Construct an * {@link UploadControl}, pass it here, and call `pause()` / `resume()` / * `abort()` on it; persist `control.toJSON()` and rehydrate with * `UploadControl.from(token)` to resume in a later process. * * Requires a body with a known length (`File`, `Blob`, `ArrayBuffer`, a typed * array, or `string`) — a `ReadableStream` can't be re-read to resume. * Supported on S3 and the S3-compatible adapters (on the `aws-sdk` * client), Bun's S3, GCS, Firebase Storage, Azure Blob, Google * Drive, OneDrive, SharePoint, Dropbox, Box, Vercel Blob, Supabase, * Appwrite, Cloudinary, FTP, SFTP, `fs`, and the in-memory adapter; others * (including the `fetch` S3 client) throw. Check * {@link AdapterCapabilities.resumable} to branch at runtime. Not available * in the array (bulk) form of `upload`. */ control?: UploadControl; } /** * An object's metadata, without its body — what `head()`, `list()`, * `listAll()`, and `search()` return, and what `upload()` resolves to. Reading * the bytes is always an explicit `download()`. */ export interface FileInfo { key: string; /** Size in bytes. */ size: number; /** The stored `Content-Type` (`application/octet-stream` when unknown). */ contentType: string; etag?: string; /** Last-modified time, in milliseconds since the epoch. */ lastModified?: number; /** * User metadata, where the adapter stores it and the call returns it * (`head()` always does; `list()` only on adapters whose listing carries it). */ metadata?: Record; } /** What `upload()` resolves to: the stored object's {@link FileInfo}. */ export type UploadResult = FileInfo; /** A conditional upload always returns the new strong ETag. */ export type ConditionalUploadResult = UploadResult & { etag: string; }; /** * A downloaded object: its {@link FileInfo} plus the body, behind `File`-like * accessors. `name` and `type` mirror `key` and `contentType`, so it can stand * in where a `File` is expected. */ export interface StoredFile extends FileInfo { /** Same as `key` (the `File`-like name). */ name: string; /** Same as `contentType` (the `File`-like MIME type). */ type: string; arrayBuffer: () => Promise; text: () => Promise; stream: () => ReadableStream; blob: () => Promise; } /** * A contiguous byte range to download, mirroring the HTTP `Range` header * (`bytes=start-end`) the supporting adapters issue under the hood. * * Both bounds are **0-based**, and `end` is **inclusive** — `{ start: 0, end: * 99 }` is the first 100 bytes, matching the wire semantics of S3, GCS, Azure, * and `fetch`. This is deliberately *not* `slice()` semantics (where `end` * would be exclusive); the bytes returned line up with what a `Range` request * would yield. */ export interface ByteRange { /** First byte to return, 0-based and inclusive. Must be a non-negative integer. */ start: number; /** * Last byte to return, 0-based and **inclusive**. Omit to read from `start` * to the end of the object (`bytes=start-`). When set, must be an integer * `>= start`. */ end?: number; } export interface DownloadOptions extends OperationOptions { /** Read only the exact object identified by this strong ETag. */ condition?: { etag: string; }; as?: "blob" | "stream"; /** * Download only a contiguous slice of the object instead of the whole thing * — the building block for video seeking and resumable downloads. The * returned {@link StoredFile} carries just the requested bytes, and its * `size` reflects the range length (not the full object). * * **Supported** by most adapters: S3 and the S3-compatible adapters (R2, * MinIO, DigitalOcean Spaces, Wasabi, Tigris, Backblaze B2, Storj, Hetzner, * Akamai, and the rest of the `s3()` family), Bun's S3, Google Cloud * Storage, Firebase Storage, Azure Blob, Google Drive, Dropbox, Box, * OneDrive, SharePoint, Cloudinary, WebDAV, UploadThing, PocketBase, Vercel * Blob (public mode), the local `fs` adapter, the in-memory adapter, and * SFTP / FTP. SFTP uses native read-stream offsets; FTP begins the transfer * at the REST start offset and trims a bounded `end` client-side (an * open-ended range transfers only what's needed). * * **Throws** a {@link FilesError} on adapters with no range primitive * (Appwrite, Bunny Storage, Convex, Netlify Blobs, Supabase, Vercel Blob in * private mode) rather than silently downloading the whole object and * slicing it — so the bandwidth saving is never quietly lost. Check * {@link AdapterCapabilities.rangeRead} to branch at runtime. */ range?: ByteRange; } /** Options passed to an adapter's native conditional upload primitive. */ export type AdapterUploadOptions = Omit; /** Options passed to an adapter's native exact-read primitive. */ export type AdapterDownloadOptions = Omit; /** Options for a single delete, including an optional native ETag predicate. */ export interface DeleteOptions extends OperationOptions { condition?: { etag: string; }; } /** The source and destination predicates for a native conditional copy. */ export interface CopyCondition { source: { etag: string; }; destination: UploadCondition; } /** Options for a copy, including optional atomic source/destination checks. */ export interface CopyOptions extends OperationOptions { condition?: CopyCondition; } /** The public conditional-upload overload, which excludes unsupported modes. */ export type ConditionalUploadOptions = AdapterUploadOptions & { condition: UploadCondition; }; export interface ListOptions extends OperationOptions { /** * Filter results to keys that start with this string. Omit to list * everything in the bucket. */ prefix?: string; /** * Continuation token from a prior result. Pass the `cursor` field of the * previous page back in to fetch the next page; omit on the first call. * * A cursor is only valid for the exact `prefix` **and** `delimiter` it was * produced with — hold both constant across a paginated sequence. */ cursor?: string; /** * Maximum number of items to return per page. Capped per-provider (most * providers max around 1000). Defaults to 1000. */ limit?: number; /** * Collapse keys at this boundary into "folders" (S3-style common prefixes), * the building block for a file-browser UI. With `delimiter: "/"` and * `prefix: "photos/"`, the page's `items` are only the direct files * (`photos/cover.jpg`) and {@link ListResult.prefixes} holds the subfolders * (`photos/2023/`, `photos/2024/`) — the keys nested deeper are folded into * those prefixes rather than listed. * * **Supported** by the object-store adapters with native common-prefix * listing (S3 and the whole `s3()` family, R2, Bun's S3, Google Cloud * Storage, Firebase Storage, Azure Blob), the local `fs`, in-memory, FTP, * SFTP, WebDAV, Google Drive, and Cloudinary adapters (any delimiter * string), plus the folder-based providers (Vercel Blob, Netlify Blobs, * Supabase, Dropbox, Box, OneDrive, SharePoint) which only accept `"/"`. * * **Throws** a {@link FilesError} on adapters with no folder concept * (UploadThing, Appwrite, PocketBase, Convex, Bunny Storage) rather than * silently returning a flat list. Check * {@link AdapterCapabilities.delimiter} to branch at runtime. Must be a * non-empty string. */ delimiter?: string; } export interface ListResult { items: FileInfo[]; /** * Common prefixes ("folders") when {@link ListOptions.delimiter} is set — * full keys including the trailing delimiter, e.g. `["photos/2023/", * "photos/2024/"]`. Omitted when no delimiter is set or none are found. When * the {@link Files} instance has a client `prefix`, these are scoped/stripped * identically to item keys. */ prefixes?: string[]; cursor?: string; } /** * How a string {@link SearchOptions.match | pattern} is interpreted by * {@link Files.search}. A `RegExp` pattern ignores this and always matches by * regex. * * - `"glob"` (default) — standard glob (via picomatch): `*` matches a run of * characters within a path segment, `**` spans segments, `?` is a single * non-`/` character, plus `[a-z]` classes, `{a,b}` braces, and a leading `!` * to negate. Dotfiles are matched (keys are opaque, not hidden files). A glob * with no wildcards (e.g. `"report.pdf"`) is an **exact** key match, not a * substring match. * - `"regex"` — the string is compiled to a `RegExp` (with the `u` flag) and * tested against the key. * - `"substring"` — matches keys that contain the pattern anywhere. * - `"exact"` — matches keys equal to the pattern. */ export type SearchMatch = "glob" | "regex" | "substring" | "exact"; export interface SearchOptions extends OperationOptions { /** * Restrict the underlying walk to keys starting with this prefix. For a glob * pattern a literal prefix is inferred automatically (`uploads/2024/*.pdf` * walks only the `uploads/2024` prefix); set this explicitly to bound a * `regex`, `substring`, or `caseInsensitive` search — which have no inferable * prefix — to part of a large bucket. Composes with the {@link Files} * instance `prefix` exactly like {@link ListOptions.prefix}. */ prefix?: string; /** * Page size for each underlying `list()` call — the walk's batch size, not a * cap on results. See {@link ListOptions.limit}. */ limit?: number; /** * Stop after yielding this many matches. Omit to yield every match (the walk * still pages lazily, so memory stays bounded). */ maxResults?: number; /** * How to interpret a string `pattern`. Defaults to `"glob"`. Ignored when * `pattern` is a `RegExp`. */ match?: SearchMatch; /** * Match case-insensitively. Adds the `i` flag for `glob` and `regex` and * lowercases both sides for `substring` and `exact`. A `RegExp` that already * sets its own `i` flag is honored regardless. When set, the automatic glob * prefix push-down is disabled (a provider prefix filter is case-sensitive), * so pass an explicit `prefix` to bound a case-insensitive search. Defaults * to `false`. */ caseInsensitive?: boolean; } /** * Shared controls for the array form of the bulk methods (`upload`, * `download`, `head`, `exists`, `delete`). */ export interface BulkOptions { /** * How many per-key operations run in parallel. Defaults to `8`. Ignored * when `stopOnError` is set — that path runs sequentially. * * A bulk `delete` on an adapter with a native batch primitive ignores it * too: S3 and the S3-compatible adapters (on the `aws-sdk` client), Azure, * Supabase, and UploadThing batch the keys into as few requests as the * provider allows, and FTP / SFTP delete sequentially over one connection. * With a wrapping plugin installed, or without a native batch, the SDK fans * out to per-key `delete()` calls under this concurrency. */ concurrency?: number; /** * When `true`, stop at the first failure and return immediately with the * results gathered so far plus that error. When `false` (default), process * every item and collect per-key failures in `errors`. * * It always runs the per-key calls one at a time, in input order — a bulk * `delete` included, which skips the adapter's native batch (a batch request * can't stop partway) — so it behaves the same with or without plugins. */ stopOnError?: boolean; } /** The options for a bulk `delete` — the shared {@link BulkOptions}. */ export type DeleteManyOptions = BulkOptions; /** A single per-key failure from the array form of a bulk method. */ export interface BulkError { key: string; error: FilesError; } /** * What the array form of every bulk method except `exists` resolves to. It * never throws on a partial failure: successes and per-key failures are * collected side by side, both in the order the items were supplied. */ export interface BulkResult { /** Successes, in the order their items were supplied. */ results: T[]; /** Per-key failures. Omitted entirely when every item succeeded. */ errors?: BulkError[]; } /** A bulk `delete`: the keys that were deleted. */ export type DeleteManyResult = BulkResult; /** One item in the array form of {@link Files.upload}. */ export interface UploadManyItem { key: string; body: Body; /** Per-item MIME type. See {@link UploadOptions.contentType}. */ contentType?: string; /** Per-item `Cache-Control`. See {@link UploadOptions.cacheControl}. */ cacheControl?: string; /** Per-item user metadata. See {@link UploadOptions.metadata}. */ metadata?: Record; /** Per-item multipart toggle/tuning. See {@link UploadOptions.multipart}. */ multipart?: boolean | MultipartOptions; } /** A bulk `upload`: each stored object's {@link FileInfo}. */ export type UploadManyResult = BulkResult; export interface UploadManyOptions extends BulkOptions { /** * Called as each item makes progress. Same semantics as * {@link UploadOptions.onProgress}, with the item's `key` added so callers * can attribute the report to a file when several upload concurrently. */ onProgress?: (progress: UploadProgress & { key: string; }) => void; } export interface DownloadManyOptions extends BulkOptions { /** Applied to every download. See {@link DownloadOptions.as}. */ as?: "blob" | "stream"; } /** A bulk `download`: each downloaded {@link StoredFile}. */ export type DownloadManyResult = BulkResult; /** A bulk `head`: each object's {@link FileInfo}. */ export type HeadManyResult = BulkResult; export interface ExistsManyResult { /** Keys that exist, in input order. */ existing: string[]; /** Keys the provider reports as missing, in input order. */ missing: string[]; /** * Keys whose existence couldn't be determined — a hard error (auth, * transport) rather than a clean present/absent answer. Omitted when none. */ errors?: BulkError[]; } export interface UrlOptions extends OperationOptions { /** * Ask for a URL that expires after this many seconds. * * Passing it always gets you an expiring URL or an error — never a * permanent link that ignores it: * * - On an instance that can sign ({@link SignedUrlCapability.supported}), * the URL is signed, **even when a `publicBaseUrl` is configured** (a plain * `url(key)` there still returns the permanent CDN link). With * `signedUrl.expiry: "exact"` it expires after exactly `expiresIn`; with * `"provider"` (Box, PocketBase, Dropbox temporary links) the provider sets * the lifetime and `expiresIn` is advisory. * - On an instance that can't sign (Vercel Blob public, UploadThing * public-read, Convex, Appwrite, the filesystem, …), it throws an * `Unsupported` {@link FilesError} before the adapter is called. * * Omit it for the adapter's default: a signed URL with its default expiry, * or the permanent link when {@link AdapterCapabilities.publicUrl} is set. */ expiresIn?: number; /** * Override the `Content-Disposition` header on the response. * * **Strongly recommended** for buckets that contain user-uploaded * content. Without this override, the browser uses the stored * Content-Type to decide whether to render or download, which means a * user-uploaded `.html` (or SVG with embedded scripts) will execute * inline at your bucket's origin — stored XSS in the trust context of * your domain. Pass `"attachment"` (or `'attachment; filename="..."'`) * to force a download. * * **Forces the signing path.** On signing adapters (S3, R2 HTTP, MinIO, * DigitalOcean Spaces, Storj, Hetzner, Akamai, Backblaze B2, Wasabi, * Tigris, R2 hybrid), passing this option always returns a * presigned URL — * even when `publicBaseUrl` is configured, because a permanent CDN URL * has no signature in which to bind the override. If `publicBaseUrl` * was the deliberate choice and you also need the security override, * the override wins (it's the safe default). * * **Throws** on Vercel Blob (no Content-Disposition primitive) and on * the R2 binding without HTTP credentials (can't sign). These cases * fail loudly rather than silently dropping the security ask. */ responseContentDisposition?: string; } export interface SignUploadOptions extends OperationOptions { /** * How long the signed URL stays valid, in seconds. After it elapses, the * URL stops working and the client must request a new one. */ expiresIn: number; /** * MIME type bound into the signature when the provider supports doing so. * Adapters that cannot enforce it at the signed URL layer throw rather * than returning an advisory header. */ contentType?: string; /** * Maximum upload size in bytes, enforced server-side. * * **Strongly recommended when supported.** When omitted, the adapter falls * back to a presigned PUT URL with no server-side size limit — anyone with * the URL can upload an arbitrarily large file until `expiresIn` elapses. * When set, supporting adapters use a presigned POST form (S3 and the * S3-compatible adapters on the `aws-sdk` client) that enforces the size via * a `content-length-range` policy. Adapters whose direct-upload primitive * cannot enforce this (R2, the `fetch` S3 client, …) fail closed. */ maxSize?: number; /** * Minimum upload size in bytes for the presigned POST policy. Defaults to * `1` — empty uploads are usually a sign of a broken client, and the most * common application assumption ("file present means real content") fails * silently when 0-byte objects can land. Pass `0` if you genuinely want to * allow empty uploads. Only used by adapters that can enforce `maxSize`; * adapters whose direct-upload primitive cannot enforce this fail closed. */ minSize?: number; } export type SignedUpload = { method: "PUT"; url: string; headers?: Record; } | { method: "POST"; url: string; fields: Record; }; /** How an adapter's {@link Adapter.url} produces a download URL. */ export interface SignedUrlCapability { /** * `true` when `url()` mints a signed or tokenized download URL that grants * access without the caller's own credentials and is more than a permanent * public link (an S3 SigV4 URL, an Azure SAS, a GCS signed URL, a Box or * PocketBase access-token URL, …). Whether such a URL honors * {@link UrlOptions.expiresIn} exactly is a separate, per-provider detail — * some providers pin the lifetime server-side and treat it as advisory; see * {@link SignedUrlCapability.expiry}. `false` when the adapter has no * signing primitive: a plain `url(key)` returns only a permanent URL (Vercel * Blob in public mode, Appwrite, Convex, the filesystem's `file://` / * `urlBaseUrl` URL) or throws because it cannot mint a URL at all (FTP/SFTP * without a `publicBaseUrl`, OneDrive / Google Drive outside their * public-link mode), and `url(key, { expiresIn })` is refused with an * `Unsupported` {@link FilesError} before the adapter runs — never answered * with a permanent link. When `false`, prefer `download()`. */ supported: boolean; /** * What {@link UrlOptions.expiresIn} does on this instance: * * - `"exact"` — the URL expires after exactly `expiresIn` seconds (SigV4, * SAS, GCS V4, Vercel Signed URLs). * - `"provider"` — the URL is tokenized and does expire, but the provider * sets the lifetime; `expiresIn` is advisory (Box, PocketBase, Dropbox's * 4-hour temporary links). * - `"none"` — `url()` returns a permanent link (or throws), so an * `expiresIn` is refused with `Unsupported` before the adapter runs. */ expiry: SignedUrlExpiry; /** * `true` when a {@link UrlOptions.responseContentDisposition} is bound into * the signed URL (S3's `response-content-disposition`, an Azure SAS `rscd`, * GCS's `responseDisposition`). When `false`, passing one throws — so the * `files-sdk/api` gateway, which forces `attachment` by default, proxies the * download instead of redirecting. */ disposition: boolean; /** * Hard upper bound on `expiresIn`, in seconds, when the adapter enforces one * in code: a longer `expiresIn` throws rather than being clamped (e.g. SigV4 * presigned URLs on the S3 family and an Azure user-delegation SAS are capped * at 7 days; Dropbox temporary links at 4 hours). Omitted when there is no * code-enforced cap. */ maxExpiresIn?: number; } /** See {@link SignedUrlCapability.expiry}. */ export type SignedUrlExpiry = "exact" | "provider" | "none"; /** How an adapter's {@link Adapter.signedUploadUrl} hands out direct uploads. */ export interface SignedUploadCapability { /** * `true` when `signedUploadUrl()` returns a presigned target a client can * upload to directly, without going through this process. `false` when it * always throws on this instance (no signing primitive, or a body-transforming * plugin that a direct upload would bypass) — upload through the instance * instead, as the `files-sdk/api` gateway's proxy path does. */ supported: boolean; /** * `true` when a {@link SignUploadOptions.maxSize} is enforced server-side * (e.g. an S3 POST policy's `content-length-range`). When `false`, passing * `maxSize` throws rather than minting an unbounded URL. */ maxSize: boolean; /** * `true` when a {@link SignUploadOptions.contentType} is bound into the * signature or policy. When `false`, passing `contentType` throws. */ contentType: boolean; /** Hard upper bound on `expiresIn`, in seconds, when enforced in code. */ maxExpiresIn?: number; } /** * Which {@link ListOptions.delimiter} values `list()` accepts: any non-empty * string, only `"/"` (folder-based providers), or none at all. */ export type DelimiterSupport = "any" | "slash" | false; /** A provider-native conditional copy and the destination predicates it honors. */ export interface AdapterConditionalCopy { /** The implementation always checks the supplied source ETag. */ readonly sourceEtag: true; /** Source and destination predicates settle atomically in one provider call. */ readonly atomicSourceDestination: true; /** Whether `destination: { type: "create" }` is supported atomically. */ readonly destinationCreate: boolean; /** Whether `destination: { type: "replace" }` is supported atomically. */ readonly destinationReplace: boolean; run: (from: string, to: string, condition: CopyCondition, opts?: OperationOptions) => Promise; } /** Optional native conditional primitives supplied by an adapter. */ export interface AdapterConditionalOperations { create?: (key: string, body: Body, opts?: AdapterUploadOptions) => Promise; replace?: (key: string, body: Body, etag: string, opts?: AdapterUploadOptions) => Promise; exactRead?: (key: string, etag: string, opts?: AdapterDownloadOptions) => Promise; delete?: (key: string, etag: string, opts?: OperationOptions) => Promise; copy?: AdapterConditionalCopy; } /** Queryable native conditional-operation support. */ export interface ConditionalAdapterCapabilities { create: boolean; replace: boolean; exactRead: boolean; delete: boolean; copy: { sourceEtag: boolean; atomicSourceDestination: boolean; destinationCreate: boolean; destinationReplace: boolean; }; /** Conditional multipart is intentionally outside the first API slice. */ multipart: { create: false; replace: false; }; } /** * A queryable snapshot of what the underlying adapter can do, exposed via * {@link Files.capabilities}. Lets callers, AI tool wrappers, and validators * branch on capability up front instead of relying on a throw at call time * (e.g. skip `range` planning when `rangeRead` is `false`, or fall back to * `download()` when `signedUrl.supported` is `false`). * * Every field mirrors an operation the unified API actually exposes. Most come * straight from the adapter's {@link Adapter.capabilities} declaration (with * the conservative value for anything it leaves out); `resumable` and * `conditional` are derived from the methods that implement them. The * {@link Files} wrapper gates on this same snapshot, so it can't drift from * runtime behavior. */ export interface AdapterCapabilities { /** `upload({ cacheControl })` stores a Cache-Control header. */ cacheControl: boolean; /** Native compare-and-set primitives. Derived from {@link Adapter.conditional}. */ conditional: ConditionalAdapterCapabilities; /** Which `list({ delimiter })` values return common prefixes. */ delimiter: DelimiterSupport; /** * The bucket-notification format this instance's provider sends, which * `files-sdk/events` parses, or `false` when it has none the SDK can read * (or none that's verified for this configuration). */ events: EventCapability | false; /** `upload({ metadata })` persists arbitrary user metadata. */ metadata: boolean; /** * A plain `url(key)` — no `expiresIn`, no `responseContentDisposition` — * returns a permanent link (a `publicBaseUrl` / CDN URL, a public blob, a * public share link). Pass `expiresIn` to get an expiring one instead, which * needs {@link SignedUrlCapability.supported}. */ publicUrl: boolean; /** `download({ range })` returns only the requested bytes. */ rangeRead: boolean; /** `upload({ control })` pause-able / resumable uploads. Derived from {@link Adapter.resumableUpload}. */ resumable: boolean; /** `copy()` runs server-side with no body re-transfer. Advisory; gates nothing. */ serverSideCopy: boolean; /** How `signedUploadUrl()` hands out direct uploads. */ signedUpload: SignedUploadCapability; /** How `url()` produces a download URL. */ signedUrl: SignedUrlCapability; /** `upload({ onProgress })` reports byte-level progress from the adapter itself. */ uploadProgress: boolean; } /** * What an adapter declares about itself as {@link Adapter.capabilities}. Every * field is optional and defaults to the conservative value — `false`, or * "unsupported" — so an adapter that says nothing never advertises a wrong * `true`. Declare it per instance: when a constructor option changes what the * adapter can do (a `publicBaseUrl`, an access mode, a client engine), the * declaration should reflect that instance's configuration. */ export interface AdapterCapabilityDeclaration { /** * `upload` stores {@link UploadOptions.cacheControl} on the object. Without * it, passing `cacheControl` throws before any provider call. */ cacheControl?: boolean; /** * `list` honors {@link ListOptions.delimiter} by returning common prefixes in * {@link ListResult.prefixes}: `"any"` for any non-empty string, `"slash"` * for `"/"` only. Without it, passing a delimiter throws before any provider * call, rather than silently returning a flat list. */ delimiter?: DelimiterSupport; /** * The bucket-notification format this provider sends (S3 `Records[]`, R2 * Queue messages, GCS Pub/Sub, …), for `files-sdk/events`. Declare it only * when the provider is verified to send that format for this configuration: * an S3-compatible endpoint that may or may not send S3-shaped events * declares nothing, and callers opt in with `events({ format })`. */ events?: EventCapability | false; /** * `upload` persists {@link UploadOptions.metadata}. Without it, a non-empty * `metadata` throws before any provider call rather than being dropped. */ metadata?: boolean; /** * A plain `url(key)` returns a permanent link on this instance (a configured * `publicBaseUrl`, a public bucket or blob, a public share link). When * `url()` is also given `expiresIn`, the adapter must sign instead — or, if * it can't (`signedUrl.supported` is `false`), the call is refused before * the adapter is reached. */ publicUrl?: boolean; /** * `download` honors {@link DownloadOptions.range} with a real byte-range * request. Without it, a `range` throws before any provider call rather than * downloading the whole object. */ rangeRead?: boolean; /** * `copy` runs server-side, never re-transferring the body through this * process (S3 `CopyObject`, Azure `syncCopyFromURL`, a filesystem rename). * Advisory: it doesn't gate `copy()`, which works on every adapter. */ serverSideCopy?: boolean; /** * What `signedUploadUrl` can do. Defaults to unsupported; `maxSize` and * `contentType` default to `false` when omitted. */ signedUpload?: Partial & { supported: boolean; }; /** * What `url` produces when it signs. Defaults to `{ supported: false, * expiry: "none" }`; when `supported` is `true`, `expiry` defaults to * `"exact"` and `disposition` to `false`. */ signedUrl?: Partial & { supported: boolean; }; /** * `upload` reports byte-level progress by calling `opts.onProgress` itself * (a provider's native progress hook), so the wrapper defers to it. Without * it, the wrapper reports progress generically: byte-level for * `ReadableStream` bodies, start/finish for buffered ones. */ uploadProgress?: boolean; } export interface Adapter { readonly name: string; readonly raw: Raw; /** * What this adapter instance can do — see {@link AdapterCapabilityDeclaration}. * Surfaced (with `resumable` and `conditional` derived from the methods * below) through {@link Files.capabilities}, and gated on by the * {@link Files} wrapper before any provider call. Omit it and every * capability reads as unsupported. */ readonly capabilities?: AdapterCapabilityDeclaration; /** * Native conditional primitives. Omitting an operation means it cannot be * implemented atomically and the public call fails before provider I/O. */ readonly conditional?: AdapterConditionalOperations; upload: (key: string, body: Body, opts?: Omit) => Promise; /** * Download an object's body and metadata. When {@link DownloadOptions.range} * is set, adapters that declare {@link AdapterCapabilityDeclaration.rangeRead} must return * only the requested bytes, with `size` set to the range length. */ download: (key: string, opts?: AdapterDownloadOptions) => Promise; /** * Fetch metadata only — a {@link FileInfo}, with no body. Must not transfer * the object's bytes. */ head: (key: string, opts?: OperationOptions) => Promise; /** * Check whether `key` exists without fetching its body. * * Returns `true` when the object exists, `false` when the provider reports * `NotFound`, and rethrows every other error (permissions, transport * failures, bad credentials, etc.). */ exists: (key: string, opts?: OperationOptions) => Promise; delete: (key: string, opts?: OperationOptions) => Promise; /** * Delete many keys in one call. Optional: when an adapter omits it, the * SDK fans out to `delete()` with bounded concurrency. Adapters that * implement it should use a native bulk primitive where one exists. */ deleteMany?: (keys: string[], opts?: DeleteManyOptions) => Promise; copy: (from: string, to: string, opts?: OperationOptions) => Promise; /** * Move (rename) `from` to `to`. Optional: when an adapter omits it, the SDK * falls back to `copy()` then `delete()`. Adapters should implement it when * the provider has a native rename that's atomic or avoids re-transferring * the body (the local filesystem, FTP, SFTP) — the copy+delete fallback on * those round-trips the bytes. */ move?: (from: string, to: string, opts?: OperationOptions) => Promise; list: (opts?: ListOptions) => Promise; /** * Return a URL the caller can use to fetch `key`. * * Adapters return the most direct URL they can produce: * * - **S3 / R2 (HTTP) / MinIO / DigitalOcean Spaces / Storj / Hetzner / Akamai / Backblaze B2 / Wasabi / Tigris** sign a `GetObject` request — the URL * expires after `opts.expiresIn` seconds (or the adapter's default, * typically 3600). If the adapter was constructed with * `publicBaseUrl`, a plain `url(key)` is built against that origin * instead and does not expire; an `expiresIn` or * `responseContentDisposition` still signs. * - **R2 (binding)** uses `publicBaseUrl` if configured, falls back to * HTTP signing if HTTP credentials were also passed (hybrid mode), * and otherwise throws. * - **Vercel Blob (public)** returns the permanent CDN URL. It never sees * an `expiresIn`: it declares `signedUrl.supported: false`, so * {@link Files} refuses one with `Unsupported` first. * - **Vercel Blob (private)** mints a Vercel Signed URL (presigned GET) * scoped to the key, honoring `expiresIn`. * * An adapter that declares {@link AdapterCapabilityDeclaration.signedUrl} * `supported: false` is never called with `expiresIn` — the wrapper refuses * it before any provider I/O. * * **Keys are passed raw.** The built-in adapters build URLs against a * `publicBaseUrl` (or Vercel Blob's fast path) by `encodeURIComponent`-ing * each `/`-separated key segment and neutralizing `.` / `..` segments so * they can't resolve as path traversal; a custom adapter should do the * same. A key that is already percent-encoded is encoded again (`%20` * becomes `%2520`). */ url: (key: string, opts?: UrlOptions) => Promise; signedUploadUrl: (key: string, opts: SignUploadOptions) => Promise; /** * Build a {@link ResumableDriver} for a pause-able / resumable upload of * `key`. Optional: only adapters whose provider exposes a resumable or * multipart-with-listable-parts primitive implement it. When omitted, an * `upload()` call that passes {@link UploadOptions.control} throws an * unsupported-operation error before any provider call (mirroring the * `rangeRead` gate). The returned driver is synchronous to * construct; it establishes the provider session lazily in `begin()`. */ resumableUpload?: (key: string, opts: ResumableDriverOptions) => ResumableDriver; } /** The public {@link Files} method a hook event describes. */ export type FilesActionType = "upload" | "download" | "head" | "exists" | "delete" | "copy" | "move" | "list" | "url" | "signedUploadUrl" | "abortUpload"; /** * Delivered to {@link FilesHooks.onAction} once when a public operation * settles — on success and on failure. The array form of an operation reports * the caller's `keys` and emits a single event carrying the aggregated * `result` (any per-item failures live in that result's `errors`); the single * form reports `key`, or `from` / `to` for `copy` and `move`. Keys are always * the ones the caller passed, never the internal prefixed path. */ export interface FilesActionEvent { type: FilesActionType; /** Conditional primitive used, without exposing its ETag predicate. */ condition?: ConditionalActionType; /** Caller-facing key, for single-key operations. */ key?: string; /** Caller-facing keys, for the array form. */ keys?: string[]; /** `copy` source / destination, as passed by the caller. */ from?: string; to?: string; status: "success" | "error"; /** The resolved value, on success. */ result?: unknown; /** The error, on failure — also delivered to {@link FilesHooks.onError}. */ error?: FilesError; /** Wall-clock duration of the public call, in milliseconds. */ durationMs: number; /** * A provenance {@link Receipt} for the call, present **only** when the * `receipts` option is on and `type` is a mutating verb (`upload`, `delete`, * `copy`, `move`) that **succeeded**. Absent on reads, on failures, on * `signedUploadUrl`, and on every instance with receipts off — so an existing * `onAction` consumer that never opted in sees the exact payload it always * has. See {@link FilesOptions.receipts}. */ receipt?: Receipt; } /** * Delivered to {@link FilesHooks.onError} when a public call rejects, just * before the matching `onAction({ status: "error" })`. Partial failures * collected in a bulk result's `errors[]` are not rejections and do not fire * it. */ export interface FilesErrorEvent { type: FilesActionType; condition?: ConditionalActionType; key?: string; keys?: string[]; from?: string; to?: string; error: FilesError; durationMs: number; } /** * Delivered to {@link FilesHooks.onRetry} each time the SDK schedules a retry * for a single-operation call. Not fired on the first attempt, for * non-retryable errors, or for stream uploads (which never retry). A bulk * call's own per-item verb does not retry, so it never fires it — but a * sub-operation a plugin re-routes an item to (e.g. `softDelete()` turning a * bulk `delete` into a `move`) runs the full single-operation path, so it * does retry and fires `onRetry`. */ export interface FilesRetryEvent { type: FilesActionType; condition?: ConditionalActionType; key?: string; from?: string; to?: string; /** The retry about to be scheduled — `1` is the first retry. */ attempt: number; /** Total retries allowed for this call. */ maxRetries: number; /** Milliseconds the SDK will wait before that attempt. */ delayMs: number; /** The error that triggered the retry. */ error: FilesError; } /** * Observability callbacks for a {@link Files} instance, passed as * `new Files({ hooks })`. Each mirrors the lightweight * {@link UploadOptions.onProgress} style — caller-facing payloads, no internal * adapter detail — and is fire-and-forget: the SDK calls it but does not await * it, and a hook that throws can never fail the operation it observes. */ export interface FilesHooks { onAction?: (event: FilesActionEvent) => void; onError?: (event: FilesErrorEvent) => void; onRetry?: (event: FilesRetryEvent) => void; } export interface FilesOptions extends OperationOptions { adapter: A; prefix?: string; /** * When `true`, block every write surface on this instance (`upload`, * `delete`, `copy`, `move`, `signedUploadUrl`, `abortUpload`, and the write * helpers on `file(key)`) with `FilesError("ReadOnly", ...)`. */ readonly?: boolean; /** Observability callbacks — see {@link FilesHooks}. */ hooks?: FilesHooks; /** * Opt into provenance {@link Receipt}s for mutating calls (`upload`, `delete`, * `copy`, `move`). **Off by default** — when unset or `false`, the instance * behaves exactly as before: no receipt is built and nothing is hashed. * * - `true` — attach a {@link Receipt} (without `sha256`) to the success * {@link FilesActionEvent} of each mutating call. Every field is derived * from work the SDK already does, so this adds no per-call cost. * - `{ sha256: true }` — additionally fingerprint the upload body, taken * **as passed to `upload()`** (before any plugin transform). This is the one * field with a real per-call cost, so it stays off until asked for by name. * The `sha256` is present only on an `upload` of a buffered body; a * streaming upload (which the SDK never buffers) omits it. With a * body-transforming plugin (`encryption`, `compression`) the stored bytes * differ from this hash, but it matches what `download` returns. * * Receipts ride on the existing {@link FilesHooks.onAction} event as an * additive `receipt` field, so reading them is `new Files({ receipts: true, * hooks: { onAction: (e) => e.receipt && record(e.receipt) } })` — no new * operation, callback, or return-shape change. */ receipts?: boolean | { sha256?: boolean; }; /** * Ordered list of {@link FilesPlugin}s wrapping this instance. `plugins[0]` * is the outermost layer of the onion. For plugins that add methods via * `extend`, use {@link createFiles} so the new surface shows up on the type. */ plugins?: readonly FilesPlugin[]; } export interface FileHandle { readonly key: string; upload: { (body: Body, opts: ConditionalUploadOptions): Promise; (body: Body, opts?: UploadOptions): Promise; }; download: (opts?: DownloadOptions) => Promise; head: (opts?: OperationOptions) => Promise; exists: (opts?: OperationOptions) => Promise; delete: (opts?: DeleteOptions) => Promise; url: (opts?: UrlOptions) => Promise; signedUploadUrl: (opts: SignUploadOptions) => Promise; copyTo: (destinationKey: string, opts?: CopyOptions) => Promise; copyFrom: (sourceKey: string, opts?: CopyOptions) => Promise; /** Move this key to `destinationKey`. See {@link Files.move}. */ moveTo: (destinationKey: string, opts?: OperationOptions) => Promise; /** Move `sourceKey` onto this key. See {@link Files.move}. */ moveFrom: (sourceKey: string, opts?: OperationOptions) => Promise; } /** * The {@link FilesOperation} variants that carry a native conditional * predicate (an `upload` / `copy` / `delete` ETag or create check, or an exact * read), told apart by `mode`. The predicate is a capability boundary: a * plugin may transform the rest of the op, but every `next()` must hand the * predicate on unchanged. See {@link isConditionalOperation}. */ export type ConditionalFilesOperation = { kind: "upload"; mode: "create"; key: string; body: Body; options?: AdapterUploadOptions; } | { kind: "upload"; mode: "replace"; etag: string; key: string; body: Body; options?: AdapterUploadOptions; } | { kind: "download"; mode: "exact"; etag: string; key: string; options?: AdapterDownloadOptions; } | { kind: "delete"; mode: "match"; etag: string; key: string; options?: OperationOptions; } | { kind: "copy"; mode: "conditional"; from: string; to: string; source: { etag: string; }; destination: UploadCondition; options?: OperationOptions; }; /** * A single in-flight operation handed to a {@link FilesPlugin}. One variant per * public verb (mirroring {@link FilesActionType}, except `abortUpload`, which * reports through the hooks but never reaches a `wrap`), carrying the * caller-facing, **un-prefixed** inputs — a plugin never sees the internal * prefixed path, the same rule {@link FilesHooks} follow. * * The array form of `upload` / `download` / `head` / `exists` / `delete` fans * out to one op per item, each marked `bulk: true`, so a plugin can tell a * single call from one element of a batch. `copy`, `move`, `list`, `url`, and * `signedUploadUrl` have no array form and are always single. */ export type FilesOperation = ConditionalFilesOperation | { kind: "upload"; mode?: undefined; key: string; body: Body; options?: Omit; bulk?: true; } | { kind: "download"; mode?: undefined; key: string; options?: AdapterDownloadOptions; bulk?: true; } | { kind: "head"; key: string; options?: OperationOptions; bulk?: true; } | { kind: "exists"; key: string; options?: OperationOptions; bulk?: true; } | { kind: "delete"; mode?: undefined; key: string; options?: OperationOptions; bulk?: true; } | { kind: "copy"; mode?: undefined; from: string; to: string; options?: OperationOptions; } | { kind: "move"; from: string; to: string; options?: OperationOptions; } | { kind: "list"; options?: ListOptions; } | { kind: "url"; key: string; options?: UrlOptions; } | { kind: "signedUploadUrl"; key: string; options?: SignUploadOptions; }; /** Whether an operation carries a native conditional predicate. */ export declare const isConditionalOperation: (op: FilesOperation) => op is ConditionalFilesOperation; /** * The value a given {@link FilesOperation} resolves to — the result map that * keeps a plugin's `wrap` / `next` fully typed per verb. Mirrors the return * type of the matching {@link Files} method; `delete` / `copy` / `move` resolve * to no value (`undefined`). */ export type OperationResult = O extends { kind: "upload"; } ? O extends { mode: "create" | "replace"; } ? ConditionalUploadResult : UploadResult : O extends { kind: "download"; } ? StoredFile : O extends { kind: "head"; } ? FileInfo : O extends { kind: "exists"; } ? boolean : O extends { kind: "list"; } ? ListResult : O extends { kind: "url"; } ? string : O extends { kind: "signedUploadUrl"; } ? SignedUpload : undefined; /** * Continue inward through the plugin onion. Call it from a `wrap` to run the * next plugin (and ultimately the real operation), optionally passing a * transformed op. For a single operation `next` is retry-wrapped, so calling it * more than once re-enters the retry loop. */ export type PluginNext = (op: O) => Promise>; /** * An opt-in extension to a {@link Files} instance, passed as * `new Files({ plugins: [...] })`. Plugins compose as an **ordered onion** — * `plugins[0]` is outermost — and offer two independent capabilities: * * - `wrap` intercepts every operation: transform the op, veto it by throwing, * or observe it. This is the interceptable superset of {@link FilesHooks}, * which can only observe. Reach for {@link handlers} to author a per-verb * `wrap` with auto-passthrough instead of a raw one. * - `extend` contributes new namespaced surface (e.g. `files.usage()`). It runs * once at construction against the fully-wrapped instance, so an extension * method that calls back into `files.upload(...)` also goes through the onion. * Use {@link createFiles} to surface the added methods on the static type. * * Plugins run **inside** the `onAction` / `onError` hooks but **outside** retry * and prefixing: a `wrap` runs once on caller-facing keys, and retries resend * whatever body it produced. */ export interface FilesPlugin = Record> { /** Identifies the plugin in collision errors and diagnostics. */ readonly name: string; /** Tier A/B: wrap any operation. Call `next` to continue inward. */ wrap?: (op: O, next: PluginNext) => Promise>; /** Tier C: contribute namespaced surface. The only part that changes the type. */ extend?: (files: Files) => Ext; /** * Narrow what the instance advertises through {@link Files.capabilities}. * Receives the snapshot so far — the adapter-derived flags, already folded * through every earlier plugin's hook in `plugins` order — and returns the * snapshot to advertise. A body-transforming plugin uses it to turn off what * it can't honor end to end: `signedUrl.supported` (a presigned URL would * serve the stored, transformed bytes), `signedUpload.supported` (a direct * upload would skip the transform), and `rangeRead` (a byte range of the * stored bytes isn't a range of the caller's), so gateways and callers that * branch on capabilities pick the path that works instead of hitting a * fail-closed throw. * * The core also gates on the narrowed snapshot: an option a plugin turns * off (`range`, `metadata`, `cacheControl`, `control`, `delimiter`, an * `expiresIn` on `url()`) is refused before any plugin's `wrap` runs, with * an error naming the plugin — whether or not that plugin wraps anything. * * Return a new object rather than mutating the argument. Only narrow — the * hook changes what is advertised, not what the adapter can do, so widening * a flag the core gates on doesn't make that option work. Carried into * {@link Files.readonly} clones with the rest of the plugin. */ capabilities?: (caps: AdapterCapabilities) => AdapterCapabilities; /** * Map a provider storage event (from `files-sdk/events`) into what this * plugin's callers see: rewrite it, or return `null` to drop it. Receives the * event with the instance `prefix` already stripped, folded through every * plugin inside this one first (`plugins` order reversed, innermost first, the * way the stored bytes travel back out). A plugin that stores keys or bytes * its callers never addressed uses it: `dedup()` drops its content-addressed * blobs, `encryption()` / `compression()` clear `size` (the stored size, not * the caller's), `versioning()` / `softDelete()` drop their own prefixes. * Events from the gateway or this instance's own writes are already * caller-facing and skip it. */ event?: (event: FileEvent) => FileEvent | null; } /** * A per-verb handler map for {@link handlers}: list only the operations you * care about, each typed to its own op and `next`. Verbs you omit pass straight * through untouched. */ export type PluginHandlers = { [K in FilesOperation["kind"]]?: (op: Extract, next: (op: Extract) => Promise>>) => Promise>>; }; /** Distribute a union into the intersection of its members. */ type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; /** * The combined surface every plugin's `extend` contributes, as one * intersection — what {@link createFiles} grafts onto the {@link Files} type. */ export type ExtensionsOf

= UnionToIntersection<{ [K in keyof P]: NonNullable extends (files: Files) => infer E ? E : unknown; }[number]>; export declare class Files { #private; constructor(opts: FilesOptions); get raw(): A["raw"]; get adapter(): A; /** * The instance's key prefix (normalized — no leading/trailing slashes), or * `""` when none was configured. Plugins that build an internal * {@link Files} over {@link Files.adapter} (e.g. `extend` methods that * address the same store directly) must re-apply it, or their keys won't * line up with operations that ran through the instance. */ get prefix(): string; /** * `true` on a read-only instance — one built with `readonly: true`, or * returned by {@link Files.readonly} — whose write verbs throw `ReadOnly`. * Plugins whose `extend` methods write through an internal {@link Files} * (which doesn't inherit the flag) check it to refuse those writes the same * way. */ get isReadOnly(): boolean; /** * The constructor-level `timeout` / `retries` / `signal` defaults, as a * fresh copy. Plugins that drive a second adapter through an internal * {@link Files} (`failover()` secondaries, the `tiering()` cold tier) spread * these into it, so the instance's defaults govern every backend rather * than only the primary. */ get defaults(): OperationOptions; /** * A queryable snapshot of what the underlying adapter can do — see * {@link AdapterCapabilities}. Lets callers branch on capability up front * (e.g. skip a `range` request when `rangeRead` is `false`, or fall back to * `download()` when `signedUrl.supported` is `false`) instead of discovering * the limit by catching a throw at call time. * * Built live on each read from the adapter's * {@link Adapter.capabilities} declaration (conservative values for anything * it leaves out), with `resumable` and `conditional` derived from the * methods that implement them. Each installed plugin's * {@link FilesPlugin.capabilities} hook is then folded over that snapshot in * `plugins` order, so a plugin that can't honor a capability end to end (e.g. * a body transform and presigned URLs) narrows what is advertised. The * wrapper gates options on this same snapshot before any plugin runs. */ get capabilities(): AdapterCapabilities; readonly(): Files; file(key: string): FileHandle; /** * Upload one object or many. * * - `upload(key, body, opts)` stores a single object and resolves to its * {@link UploadResult}. A failure **throws** a {@link FilesError}. * - `upload(items)` stores many in one call — each item carries its own * `key`, `body`, and optional `contentType` / `cacheControl` / `metadata` * — and resolves to an {@link UploadManyResult}. It does **not** throw on * partial failure: successes land in `results`, per-item failures * (including invalid keys) in `errors`, both in the order supplied. The * SDK fans out with bounded `concurrency` (default 8); `stopOnError` * short-circuits at the first failure. * * Both forms honor the client's `prefix`; the array form reports the keys * the caller passed, not the internal prefixed paths. */ upload(key: string, body: Body, opts: ConditionalUploadOptions): Promise; upload(key: string, body: Body, opts?: UploadOptions): Promise; upload(items: UploadManyItem[], opts?: UploadManyOptions): Promise; /** * Download one object or many. * * - `download(key, opts)` resolves to a single {@link StoredFile}; a missing * key (or any failure) **throws** a {@link FilesError}. * - `download(keys, opts)` resolves to a {@link DownloadManyResult} and does * **not** throw on partial failure: successes land in `results`, * per-key failures (a missing key included) in `errors`, both in input * order. `as` applies to every download; the SDK fans out with bounded * `concurrency` (default 8) and `stopOnError` stops at the first failure. * * Both forms honor the client's `prefix` and report the caller's keys. */ download(key: string, opts?: DownloadOptions): Promise; download(keys: string[], opts?: DownloadManyOptions): Promise; /** * Fetch metadata only — does not transfer the body. Pass one key for a * single {@link FileInfo} (throws on failure), or an array for a * {@link HeadManyResult} (`results` + per-key `errors`, never throws on * partial failure; honors `concurrency` / `stopOnError`). To read the bytes, * call {@link Files.download}. */ head(key: string, opts?: OperationOptions): Promise; head(keys: string[], opts?: BulkOptions): Promise; /** * Check whether one key or many exist, without fetching bodies. * * - `exists(key)` resolves to `true` when the object exists and `false` when * the adapter reports `NotFound`. Other failures still propagate so * callers do not treat auth or transport errors as "missing file". * - `exists(keys)` resolves to an {@link ExistsManyResult}: keys split into * `existing` / `missing` (both in input order), with hard errors (auth, * transport) collected in `errors` rather than thrown. The SDK fans out * with bounded `concurrency` (default 8); `stopOnError` stops at the first * hard error. */ exists(key: string, opts?: OperationOptions): Promise; exists(keys: string[], opts?: BulkOptions): Promise; /** * Remove one key or many. * * - `delete(key)` removes a single object and resolves to `void`. A failure * (including a missing key on providers that don't treat delete as * idempotent) **throws** a {@link FilesError}. * - `delete(keys)` removes many in one call and resolves to a * {@link DeleteManyResult}. It does **not** throw on partial failure — * per-key failures (and invalid keys) are collected in `errors`, deleted * keys in `results`, both in the order supplied. The adapter's native * bulk primitive is used when available, otherwise the SDK fans out to * single deletes with bounded `concurrency`. With `stopOnError`, keys are * deleted one at a time (skipping the native batch, which can't stop * partway) and the first failure returns the keys deleted so far plus * that error. * * Both forms honor the client's `prefix`; the array form reports the keys * the caller passed, not the internal prefixed paths. */ delete(key: string, opts?: DeleteOptions): Promise; delete(keys: string[], opts?: DeleteManyOptions): Promise; copy(from: string, to: string, opts?: CopyOptions): Promise; /** * Move (rename) `from` to `to`, resolving to `void`. A failure (e.g. a * missing source) **throws** a {@link FilesError}. * * Uses the adapter's native rename when it has one (the local filesystem, * FTP, SFTP, WebDAV, Cloudinary, and the in-memory adapter) and otherwise * falls back to `copy()` then `delete()` — the same two-step every object * store does, since none offer an atomic move. The * fallback is therefore **not atomic**: a crash between the copy and the * delete can leave the object at both keys. * * Moving a key onto itself (`from === to`, after the client `prefix` is * applied) is a no-op — the fallback would otherwise copy the object onto * itself and then delete it, destroying it. * * Honors the client's `prefix`; the action hook reports the keys the caller * passed, not the internal prefixed paths. */ move(from: string, to: string, opts?: OperationOptions): Promise; list(opts?: ListOptions): Promise; /** * Iterate every object, transparently following the cursor across pages. * * `list()` returns one page plus a `cursor`; most callers actually want * "walk everything under this prefix", which means a manual cursor loop. * This is that loop as an async iterable: * * ```ts * for await (const file of files.listAll({ prefix: "avatars/" })) { * console.log(file.key, file.size); * } * ``` * * `prefix` scopes the walk and `limit` sets the page size (how many keys * each underlying `list()` fetches), not a total cap — pass a `cursor` to * resume from a prior position. Each page is a real `list()` call, so it * honors the client `prefix`, retries/timeouts, and fires one `onAction` * `list` hook per page. Stop early by `break`ing out of the loop; no further * pages are fetched. * * @yields {FileInfo} each stored object's metadata (no body), one page at a time, following the cursor. */ listAll(opts?: ListOptions): AsyncGenerator; /** * Find objects whose key matches `pattern`, walking every page like * {@link listAll}. * * `pattern` is a glob by default (standard glob via picomatch: `*` stays * within a path segment, `**` spans segments, `?` is one character, plus * `[a-z]` classes, `{a,b}` braces, and `!` negation). Pass a `RegExp`, or * `match: "regex" | "substring" | "exact"`, to switch how a string is * interpreted (see {@link SearchMatch}). Matching is against the * caller-facing key, so the instance `prefix` is already stripped. * * ```ts * for await (const file of files.search("photos/*.jpg")) { * console.log(file.key, file.size); * } * // "**" spans path segments — every object under uploads/, at any depth: * const all = await Array.fromAsync(files.search("uploads/**")); * ``` * * For a glob, the literal head is pushed down as a `list()` prefix so the * walk is scoped (`uploads/2024/*.pdf` only lists the `uploads/2024` prefix); * for other modes pass {@link SearchOptions.prefix} to bound it. Search reads * every page under that prefix — use {@link SearchOptions.maxResults} or * `break` to stop early. Matching against `file.key` reuses the same page walk * as {@link listAll}, so retries/timeouts and the `onAction` `list` hook apply * per page. * * @yields {FileInfo} each matching object, following the cursor across pages. */ search(pattern: string | RegExp, opts?: SearchOptions): AsyncGenerator; /** * Return a URL the caller can use to fetch `key`. * * The exact URL kind depends on the adapter — see {@link Adapter.url} * for the per-provider behavior. In short: signing adapters (S3, R2 * HTTP, MinIO, DigitalOcean Spaces, Storj, Hetzner, Akamai, Backblaze B2, * Wasabi, Tigris) return an expiring presigned URL by default; * Vercel-Blob-public returns its permanent CDN URL; Vercel-Blob-private * mints a presigned GET; configurations with no URL primitive (R2 * binding without `publicBaseUrl`/HTTP creds) throw. * * **Pass raw keys.** URLs built against a `publicBaseUrl` or Vercel Blob's * fast path percent-encode each key segment (and neutralize `.` / `..` * segments) for you, so a pre-encoded key is double-encoded. */ url(key: string, opts?: UrlOptions): Promise; signedUploadUrl(key: string, opts: SignUploadOptions): Promise; /** * Discard the provider-side session behind a persisted resumable-upload * token (`control.toJSON()`) without finishing the upload — the * cross-process counterpart to {@link UploadControl.abort}. A control * rebuilt with `UploadControl.from(token)` has no adapter until it's passed * to `upload()`, so its own `abort()` can't reach the provider; call this * instead. `key` is the same logical key the upload targeted, and the token * must match it (and this instance's adapter), or this throws. * * Does what `abort()` does mid-upload: S3 and the S3-compatible adapters * issue `AbortMultipartUpload`; GCS, Firebase Storage, Google Drive, * OneDrive, SharePoint, and Supabase cancel the session URL; `fs`, FTP, and * SFTP remove the staged partial; Appwrite deletes the partial file. * Providers with no cancel primitive (Azure, Dropbox, Cloudinary, Vercel * Blob) let the session expire on its own, and in-process sessions (Box, * Bun S3, memory) don't outlive their process, so for those this resolves * without a provider call. A session that's already gone (completed or * discarded) is not an error, but a cancel the provider refuses rejects. * Throws on an adapter without resumable uploads, and `ReadOnly` on a * {@link Files.readonly} view. * * Reports through {@link FilesHooks} like the other write verbs — one * `onAction` with `type: "abortUpload"` and the caller's `key`, `onError` on * a rejection, `onRetry` per retried discard. It isn't a * {@link FilesOperation}, though, so plugin `wrap`s don't see it. */ abortUpload(key: string, session: ResumableUploadSession, opts?: OperationOptions): Promise; /** * Map a provider event's storage key onto this instance, for * `files-sdk/events`: `null` when it falls outside the instance `prefix` * (a multi-tenant bucket must not leak another tenant's keys), else the * prefix stripped and the event folded through each plugin's * {@link FilesPlugin.event} hook, innermost first. Symbol-keyed, so it stays * off the instance's public surface. */ [FOLD_PROVIDER_EVENT](event: FileEvent): FileEvent | null; } /** * Author a {@link FilesPlugin.wrap} as a per-verb map instead of a single * function over the whole {@link FilesOperation} union. List only the verbs you * care about — each handler is typed to its own op and a same-kind `next` — and * every other verb passes straight through untouched. * * ```ts * const encryption = (key: CryptoKey): FilesPlugin => ({ * name: "encryption", * wrap: handlers({ * upload: (op, next) => * seal(op.body, key).then(({ body, iv }) => * next({ ...op, body, options: { ...op.options, metadata: { ...op.options?.metadata, iv } } }) * ), * download: (op, next) => next(op).then((file) => unseal(file, key)), * }), * }); * ``` * * The internal casts are confined here so plugin authors never write one. */ export declare const handlers: (map: PluginHandlers) => NonNullable; /** * Construct a {@link Files} instance whose static type includes the methods * contributed by each plugin's `extend`. Identical to `new Files(opts)` at * runtime — a class constructor can't return `this & Ext` keyed off its * arguments, so this factory is the seam that surfaces e.g. `files.usage()` or * `files.image.resize(...)` on the type. * * ```ts * const files = createFiles({ * adapter: s3({ bucket: "uploads" }), * plugins: [usage()], * }); * files.usage(); // typed, from usage().extend * ``` * * Plugins that only `wrap` (no `extend`) work with plain `new Files({ plugins })` * — they add no surface, so there is nothing extra to type. */ export declare const createFiles: (opts: FilesOptions & { plugins?: P; }) => Files & ExtensionsOf

; //# sourceMappingURL=index.d.ts.map