import type { FilesPlugin } from "../index.js"; import { FilesError } from "../internal/errors.js"; /** * A key-naming rule for {@link validation}. Either a {@link RegExp} the key must * match (anchor it yourself, e.g. `/^[\w.-]+$/`; don't use the `g` flag) or a * predicate that returns `true` for keys you allow. */ export type KeyRule = RegExp | ((key: string) => boolean); /** * Which rule a {@link ValidationError} failed: the `key` naming rule, the * `size` bounds (`maxSize` / `minSize` — the message says which), or the * `allowedTypes` list (`type`). */ export type ValidationReason = "key" | "size" | "type"; /** * Thrown by {@link validation} when a write fails one of its rules. A regular * {@link FilesError} (`code: "Invalid"`) with a {@link ValidationReason} * discriminant, so callers can branch on *which* rule failed without parsing * the message: * * ```ts * try { * await files.upload(key, body); * } catch (e) { * if (e instanceof ValidationError && e.reason === "type") { * // reject with "unsupported file type" * } * } * ``` * * Note the `signedUploadUrl()` fail-closed throw is **not** a * `ValidationError` — that's the plugin refusing an unenforceable operation, * not the file failing a rule (it has `code: "Unsupported"`). */ export declare class ValidationError extends FilesError { readonly reason: ValidationReason; constructor(reason: ValidationReason, message: string); } export interface ValidationOptions { /** Reject uploads larger than this many bytes. */ maxSize?: number; /** Reject uploads smaller than this many bytes — e.g. `1` to refuse empties. */ minSize?: number; /** * Allowed MIME types. Each entry is an exact type (`"image/png"`) or a group * wildcard (`"image/*"`). Matching is case-insensitive and ignores any * `; charset=…` parameter. The type checked is `options.contentType` when you * pass it, else a `Blob`/`File`'s own `.type`, else the type inferred from the * key's extension — and that checked type is forwarded as the upload's * `contentType`, so it's the type that gets stored. A type that isn't exactly * one well-formed media type (`"image/png, text/html"`, a missing subtype, a * control character) is rejected outright: a browser reads a * comma-separated list as its *last* entry, so it would render a type the * check never saw. */ allowedTypes?: string[]; /** * Constrain the key — a {@link RegExp} it must match, or a predicate that * returns `true` for allowed keys. Enforced on `upload` and on the * destination of `copy` / `move`. */ key?: KeyRule; } /** * A fail-closed guard that vets writes **before they happen** — a max/min size, * an allowed-MIME-type list, and a key-naming rule. It rejects a bad `upload` * (and a `copy` / `move` to a disallowed key) by throwing a * {@link ValidationError} — its `reason` says which rule failed — so no bytes * ever reach the adapter. * * Unlike `compression()` / `encryption()`, it never transforms the body or * writes metadata, so **reads and `url()` pass straight through** — there's * nothing to undo — and `copy` / `move` only have their destination key * checked. The size check is the one rule that has to see the bytes: * known-length bodies (strings, byte arrays, `Blob`s and `File`s) are measured * without being read, but an unknown-length stream is buffered to measure it * (the same trade-off the buffering plugins make), so reach for it before * streaming-only setups. Key and type rules never touch the body. With * `allowedTypes` set, the checked type is forwarded as the upload's * `contentType`, so the object is stored as the type that was approved. * * Plugins run in array order (`plugins[0]` is outermost), so placement matters: * - Put it **before** `versioning()`, `softDelete()`, `dedup()`, and any * body-transforming plugin, so it vets the caller's original key and bytes. * Those plugins' own housekeeping writes (`.versions/…`, `.trash/…`, * `.dedup/…`, and dedup's empty pointer bodies) only pass through the * plugins after them — so a `key` or `minSize` rule placed after them would * reject their internal writes and break every overwrite, delete, or upload. * - Put `contentType()` **before** it. `validation()` checks the type the * client *claims*; a `contentType()` placed after it can still relabel the * approved upload from its bytes (a `.png` that's really HTML becomes * `text/html`), slipping past `allowedTypes`. With `contentType()` first, * `validation()` checks the corrected type. * * `plugins: [contentType(), validation({ maxSize, allowedTypes }), versioning(), * compression(), encryption(key)]`. * * `signedUploadUrl()` hands upload capability to a client that writes directly, * bypassing the plugin — so when a size or type rule is set it **fails closed** * (a key-only policy still mints the URL, after checking the key). * * @param options `maxSize`, `minSize`, `allowedTypes`, and/or `key` — any * combination; with none set the plugin is a no-op pass-through. * @example * ```ts * import { createFiles } from "files-sdk"; * import { s3 } from "files-sdk/s3"; * import { validation } from "files-sdk/validation"; * * const files = createFiles({ * adapter: s3({ bucket: "uploads" }), * plugins: [ * validation({ * maxSize: 10 * 1024 * 1024, // 10 MiB * allowedTypes: ["image/*", "application/pdf"], * key: /^[\w.-]+$/, * }), * ], * }); * * await files.upload("photo.png", bytes); // ok * await files.upload("notes.txt", "…"); // throws: type not allowed * ``` */ export declare const validation: (options?: ValidationOptions) => FilesPlugin; //# sourceMappingURL=index.d.ts.map