/** * `@nifrajs/image/backends` - the codec seam + the official {@link ImageBackend} implementations, kept * **dependency-free and edge-safe** (no `node:` imports), so the WASM backend can run on Workers / * Vercel-Edge / Deno-Deploy. `createImageHandler` (in `@nifrajs/image/server`, Node-only - it touches the * filesystem) consumes this seam and owns all request-level security; a backend only * decodes/resizes/encodes and translates codec failures into {@link ImageProcessingError}. * * Pick a backend for your runtime: * - {@link bunImageBackend} - `Bun.Image` (libjpeg-turbo / libspng / libwebp). Bun servers. * - {@link sharpImageBackend} - pass your `sharp` import. Node servers. * - {@link wasmImageBackend} - pass WASM codecs (e.g. jSquash). Any runtime, including the edge. */ /** Output formats nifra's endpoint can emit. AVIF is intentionally excluded - `Bun.Image` reports * `ERR_IMAGE_FORMAT_UNSUPPORTED` for AVIF encode on common platforms, so offering it would 500. */ export type OutputFormat = "webp" | "jpeg" | "png"; export declare const CONTENT_TYPE: Record; /** Header-only probe of a source image: intrinsic dimensions + decoded format. Must be cheap (no full * decode) - it gates the decompression-bomb and no-upscale checks before the expensive resize. */ export interface ImageProbe { readonly width: number; readonly height: number; /** Lowercased source format as the codec sees it (`"png"`, `"jpeg"`, `"webp"`, `"gif"`, …). */ readonly format: string; } export interface ResizeInput { readonly bytes: Uint8Array; /** Target width in px - already clamped to `[1, maxWidth]` **and** to the source's intrinsic width * (the handler never asks a backend to upscale). Aspect ratio is preserved. */ readonly width: number; /** Encoder quality `1..100` (ignored for lossless `png`). */ readonly quality: number; readonly format: OutputFormat; } export interface ResizeOutput { readonly bytes: Uint8Array; readonly contentType: string; readonly format: OutputFormat; } /** * The codec seam. The handler owns all request-level security (validation, SSRF, byte/pixel caps, * concurrency, caching); a backend only decodes/resizes/encodes. Backends MUST translate codec failures * into {@link ImageProcessingError} so the handler can map them to stable HTTP statuses. */ export interface ImageBackend { /** Cheap, header-only metadata read. Throws {@link ImageProcessingError} (`decode` / `too_large`). */ probe(bytes: Uint8Array): Promise; /** Decode → resize to `input.width` (aspect-preserving) → encode to `input.format`. */ transform(input: ResizeInput): Promise; } /** Normalized, backend-agnostic processing failure. Lets the handler map codec errors to HTTP status * without coupling to any one codec's error codes. */ export declare class ImageProcessingError extends Error { readonly kind: "decode" | "too_large" | "unsupported"; constructor(kind: "decode" | "too_large" | "unsupported", message: string); } /** * {@link ImageBackend} backed by `Bun.Image` (libjpeg-turbo / libspng / libwebp, decoded off the main * thread). Requires the Bun runtime. The default backend of `createImageHandler`. */ export declare function bunImageBackend(): ImageBackend; /** The slice of a [sharp](https://sharp.pixelplumbing.com) instance this backend uses. Declared * structurally so `@nifrajs/image` has no dependency on sharp - pass your own `sharp` import. */ export type SharpLike = (input: Uint8Array) => SharpInstance; interface SharpInstance { metadata(): Promise<{ width?: number; height?: number; format?: string; }>; resize(options: { width: number; withoutEnlargement?: boolean; }): SharpInstance; webp(options: { quality: number; }): SharpInstance; jpeg(options: { quality: number; }): SharpInstance; png(): SharpInstance; toBuffer(): Promise; } /** * {@link ImageBackend} backed by [sharp](https://sharp.pixelplumbing.com) (libvips) for Node servers. * Pass your `sharp` import - `@nifrajs/image` never imports it, so it stays dependency-free and you control * the version: * * ```ts * import sharp from "sharp" * createImageHandler({ backend: sharpImageBackend(sharp), root: "./public" }) * ``` */ export declare function sharpImageBackend(sharp: SharpLike): ImageBackend; /** A decoded image: RGBA pixels + dimensions - the lingua franca of WASM codecs (jSquash, Photon, …). */ export interface DecodedImage { /** RGBA bytes, length `width * height * 4`. */ readonly data: Uint8Array; readonly width: number; readonly height: number; } /** * Pluggable WASM codec set - decode/resize/encode. Declared structurally so `@nifrajs/image` depends on no * WASM library; wire your own (jSquash is the common pure-WASM, edge-safe choice). The handler probes * dimensions from the source header (bomb-safe), so `decode` runs only inside `transform`. */ export interface WasmImageCodecs { /** Decode any supported encoded image → RGBA. Throw on undecodable input. */ decode(bytes: Uint8Array): Promise | DecodedImage; /** Resize RGBA to the target dimensions (the handler keeps the aspect ratio). */ resize(image: DecodedImage, width: number, height: number): Promise | DecodedImage; /** Encode RGBA → the target format's bytes. */ encode(image: DecodedImage, format: OutputFormat, quality: number): Promise | Uint8Array; } /** * {@link ImageBackend} backed by injected WASM codecs - the only backend that runs on the **edge** * (Workers / Vercel-Edge / Deno-Deploy), where neither `Bun.Image` nor sharp exists. `probe` reads the * source header via nifra's dependency-free reader (so decompression bombs are rejected before any * decode); `transform` decodes → resizes (aspect-preserving) → encodes through your codecs. * * ```ts * import decode from "@jsquash/jpeg/decode"; import resize from "@jsquash/resize" * import encodeWebp from "@jsquash/webp/encode" // …+ png/jpeg encoders * const backend = wasmImageBackend({ decode, resize: (img, w, h) => resize(img, { width: w, height: h }), encode }) * ``` */ export declare function wasmImageBackend(codecs: WasmImageCodecs): ImageBackend; export {}; //# sourceMappingURL=backend.d.ts.map