type FilesErrorCode = "NotFound" | "Unauthorized" | "Conflict" | "Provider"; declare class FilesError extends Error { readonly code: FilesErrorCode; /** * The original provider error, preserved for debugging. * * **Logging note:** provider errors (especially from `@aws-sdk`) can carry * fields like request IDs, response headers, and partial request metadata. * If you serialize `FilesError` into logs that cross a trust boundary, * consider stripping `cause` or whitelisting fields rather than * `JSON.stringify`-ing the whole thing. */ readonly cause?: unknown; constructor(code: FilesErrorCode, message: string, cause?: unknown); static wrap(err: unknown, fallbackCode?: FilesErrorCode): FilesError; } interface StoredFileMeta { key: string; size: number; type: string; lastModified?: number; etag?: string; metadata?: Record; } type BodySource = { kind: "buffer"; data: Uint8Array; } | { kind: "stream"; factory: () => ReadableStream; } | { kind: "lazy"; factory: () => Promise; }; declare const createStoredFile: (meta: StoredFileMeta, body: BodySource) => StoredFile; type Body = Blob | File | ReadableStream | ArrayBuffer | ArrayBufferView | Uint8Array | string; interface UploadOptions { contentType?: string; cacheControl?: string; metadata?: Record; } interface UploadResult { key: string; size: number; contentType: string; etag?: string; lastModified?: number; } interface StoredFile { name: string; size: number; type: string; lastModified?: number; arrayBuffer(): Promise; text(): Promise; stream(): ReadableStream; blob(): Promise; key: string; etag?: string; metadata?: Record; } interface DownloadOptions { as?: "blob" | "stream"; } interface ListOptions { prefix?: string; cursor?: string; limit?: number; } interface ListResult { items: StoredFile[]; cursor?: string; } interface UrlOptions { /** * Override the adapter's default URL expiry, in seconds. * * **Honored** by adapters that sign (S3, Cloudflare R2 over HTTP, MinIO, * DigitalOcean Spaces, Storj, Hetzner, Akamai, Backblaze B2, Wasabi, * Tigris, and the R2 binding when HTTP credentials are also configured) — those * adapters return a presigned URL that expires after `expiresIn` seconds. * * **Ignored** by Vercel Blob (public): the underlying CDN URL has no * expiry, and the adapter returns it unchanged. If you need expiring * URLs there, you'll need a different provider — Vercel Blob has no * signing primitive. * * **N/A** for adapters where `url()` throws (Vercel Blob private; the * R2 binding without `publicBaseUrl` and without HTTP credentials). */ 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; } interface SignUploadOptions { expiresIn: number; contentType?: string; /** * Maximum upload size in bytes, enforced server-side. * * **Strongly recommended.** 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, * the adapter uses a presigned POST form (S3/R2) that enforces the size * via a `content-length-range` policy. */ 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 when `maxSize` is set (otherwise the * adapter falls back to a presigned PUT, which has no policy at all). */ minSize?: number; } type SignedUpload = { method: "PUT"; url: string; headers?: Record; } | { method: "POST"; url: string; fields: Record; }; interface Adapter { readonly name: string; readonly raw: Raw; upload(key: string, body: Body, opts?: UploadOptions): Promise; download(key: string, opts?: DownloadOptions): Promise; /** * Fetch metadata only — does not transfer the body. * * **Note:** the returned `StoredFile` still exposes `text()` / * `arrayBuffer()` / `blob()` / `stream()`, but those accessors lazily * issue a full GET on first use. If you only want metadata, don't call * the body accessors. They are not free. */ head(key: string): 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): Promise; delete(key: string): Promise; copy(from: string, to: string): 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`, the URL is built against that origin instead and * does not expire. * - **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. * `expiresIn` is ignored. * - **Vercel Blob (private)** throws — there is no URL primitive for * private blobs. Use `download()` instead. * * **Caller is responsible for URL-encoding.** Adapters do not escape * special characters in keys when building URLs against a * `publicBaseUrl` or Vercel Blob's fast path — the key is embedded * literally. If `key` is derived from untrusted input, callers should * validate or `encodeURIComponent`-style escape segments before * passing it in. */ url(key: string, opts?: UrlOptions): Promise; signedUploadUrl(key: string, opts: SignUploadOptions): Promise; } interface FilesOptions { adapter: A; } interface FileHandle { readonly key: string; upload(body: Body, opts?: UploadOptions): Promise; download(opts?: DownloadOptions): Promise; head(): Promise; exists(): Promise; delete(): Promise; url(opts?: UrlOptions): Promise; signedUploadUrl(opts: SignUploadOptions): Promise; copyTo(destinationKey: string): Promise; copyFrom(sourceKey: string): Promise; } declare class Files { #private; constructor(opts: FilesOptions); get raw(): A["raw"]; get adapter(): A; file(key: string): FileHandle; upload(key: string, body: Body, opts?: UploadOptions): Promise; download(key: string, opts?: DownloadOptions): Promise; /** * Fetch metadata only — does not transfer the body. * * **Note:** the returned `StoredFile` still exposes `text()` / * `arrayBuffer()` / `blob()` / `stream()`, but those accessors lazily * issue a full GET on first use. If you only want metadata, don't call * the body accessors. They are not free. */ head(key: string): Promise; /** * Check whether `key` exists without fetching its body. * * Returns `true` when the object exists and `false` when the adapter * reports `NotFound`. Other failures still propagate so callers do not * accidentally treat auth or transport errors as "missing file". */ exists(key: string): Promise; delete(key: string): Promise; copy(from: string, to: string): Promise; list(opts?: ListOptions): Promise; /** * 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; configurations * with no URL primitive (Vercel-Blob-private, R2 binding without * `publicBaseUrl`/HTTP creds) throw. * * **Caller is responsible for URL-encoding.** Adapters do not escape * special characters in keys when building URLs against a * `publicBaseUrl` or Vercel Blob's fast path. If `key` is derived * from untrusted input, callers should validate or escape it. */ url(key: string, opts?: UrlOptions): Promise; signedUploadUrl(key: string, opts: SignUploadOptions): Promise; } export { type Adapter, type Body, type BodySource, type DownloadOptions, type FileHandle, Files, FilesError, type FilesErrorCode, type FilesOptions, type ListOptions, type ListResult, type SignUploadOptions, type SignedUpload, type StoredFile, type StoredFileMeta, type UploadOptions, type UploadResult, type UrlOptions, createStoredFile };