@happyvertical/images 0.74.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/shared/errors.ts","../src/adapters/imgproxy.ts","../src/adapters/jimp.ts","../src/adapters/sharp.ts","../src/headline-card.ts","../src/shared/factory.ts","../src/index.ts"],"sourcesContent":["/**\n * Error classes for the images library\n */\n\n/**\n * Base error class for image processing operations\n */\nexport class ImageError extends Error {\n constructor(\n message: string,\n public code: string,\n public adapter?: string,\n ) {\n super(message);\n this.name = 'ImageError';\n }\n}\n\n/**\n * Error thrown when an image file is not found\n */\nexport class ImageNotFoundError extends ImageError {\n constructor(path: string, adapter?: string) {\n super(`Image not found: ${path}`, 'IMAGE_NOT_FOUND', adapter);\n this.name = 'ImageNotFoundError';\n }\n}\n\n/**\n * Error thrown when image format is not supported\n */\nexport class UnsupportedFormatError extends ImageError {\n constructor(format: string, adapter?: string) {\n super(`Unsupported image format: ${format}`, 'UNSUPPORTED_FORMAT', adapter);\n this.name = 'UnsupportedFormatError';\n }\n}\n\n/**\n * Error thrown when an operation is not supported by the adapter\n */\nexport class OperationNotSupportedError extends ImageError {\n constructor(operation: string, adapter?: string) {\n super(\n `Operation not supported: ${operation}`,\n 'OPERATION_NOT_SUPPORTED',\n adapter,\n );\n this.name = 'OperationNotSupportedError';\n }\n}\n\n/**\n * Error thrown when image processing fails\n */\nexport class ProcessingError extends ImageError {\n constructor(\n message: string,\n adapter?: string,\n public cause?: Error,\n ) {\n super(message, 'PROCESSING_ERROR', adapter);\n this.name = 'ProcessingError';\n }\n}\n\n/**\n * Error thrown when adapter type is invalid\n */\nexport class InvalidAdapterError extends ImageError {\n constructor(type: string) {\n super(`Invalid adapter type: ${type}`, 'INVALID_ADAPTER');\n this.name = 'InvalidAdapterError';\n }\n}\n\n/**\n * Error thrown when remote service (e.g., imgproxy) fails\n */\nexport class RemoteServiceError extends ImageError {\n constructor(\n message: string,\n adapter?: string,\n public statusCode?: number,\n ) {\n super(message, 'REMOTE_SERVICE_ERROR', adapter);\n this.name = 'RemoteServiceError';\n }\n}\n","/**\n * imgproxy adapter for remote image processing\n *\n * Uses a self-hosted imgproxy server for scalable image processing.\n * Ideal for enterprise deployments where processing is offloaded to dedicated servers.\n *\n * @see https://imgproxy.net/\n */\n\nimport { createHmac } from 'node:crypto';\nimport {\n OperationNotSupportedError,\n ProcessingError,\n RemoteServiceError,\n} from '../shared/errors.js';\nimport type {\n ConvertOptions,\n HashAlgorithm,\n ImageDimensions,\n ImageFormat,\n ImageInput,\n ImageMetadata,\n ImageProcessorInterface,\n ImgproxyOptions,\n ResizeOptions,\n ThumbnailOptions,\n} from '../shared/types.js';\n\n/**\n * Options for generating signed imgproxy URLs\n */\nexport interface SignedUrlOptions {\n /**\n * Width in pixels (0 for auto)\n */\n width?: number;\n\n /**\n * Height in pixels (0 for auto)\n */\n height?: number;\n\n /**\n * Resize mode\n * - 'cover': Fill dimensions, crop excess\n * - 'contain': Fit within dimensions, may have empty space\n * - 'fill': Force exact dimensions (may distort)\n */\n fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';\n\n /**\n * Output quality (1-100)\n */\n quality?: number;\n\n /**\n * Output format (jpeg, png, webp, avif, etc.)\n */\n format?: string;\n}\n\n/**\n * Generate a signed imgproxy URL\n *\n * Standalone function for generating signed URLs without creating an adapter instance.\n * Useful for client-side usage or when you only need URL generation.\n *\n * @param source - Source image URL (must be http/https)\n * @param config - imgproxy server configuration\n * @param options - Processing options\n * @returns Signed imgproxy URL\n *\n * @example\n * ```typescript\n * import { signImgproxyUrl } from '@happyvertical/images';\n *\n * const url = signImgproxyUrl(\n * 'https://example.com/image.jpg',\n * {\n * baseUrl: 'https://imgproxy.example.com',\n * key: 'hex-encoded-key',\n * salt: 'hex-encoded-salt'\n * },\n * { width: 300, height: 200, format: 'webp' }\n * );\n * ```\n */\nexport function signImgproxyUrl(\n source: string,\n config: {\n baseUrl: string;\n key?: string;\n salt?: string;\n },\n options: SignedUrlOptions = {},\n): string {\n const adapter = new ImgproxyAdapter({\n type: 'imgproxy',\n baseUrl: config.baseUrl,\n key: config.key,\n salt: config.salt,\n });\n return adapter.getSignedUrl(source, options);\n}\n\n/**\n * imgproxy adapter for remote image processing\n *\n * Offloads image processing to a self-hosted imgproxy server.\n * Supports URL signing for secure access.\n *\n * Features:\n * - Scalable remote processing\n * - URL-based API\n * - Signed URLs for security\n * - CDN-friendly outputs\n *\n * Limitations:\n * - Requires network access to imgproxy server\n * - Input must be accessible via URL (not local paths)\n * - Hash computation not directly supported (requires fetching image)\n *\n * @example\n * ```typescript\n * const processor = new ImgproxyAdapter({\n * type: 'imgproxy',\n * baseUrl: 'https://imgproxy.example.com',\n * key: 'hex-encoded-key',\n * salt: 'hex-encoded-salt'\n * });\n *\n * await processor.thumbnail(\n * 'https://example.com/image.jpg',\n * '/tmp/thumb.jpg',\n * { maxWidth: 300 }\n * );\n * ```\n */\nexport class ImgproxyAdapter implements ImageProcessorInterface {\n private baseUrl: string;\n private key?: Buffer;\n private salt?: Buffer;\n\n constructor(options: ImgproxyOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n\n if (options.key && options.salt) {\n this.key = Buffer.from(options.key, 'hex');\n this.salt = Buffer.from(options.salt, 'hex');\n }\n }\n\n /**\n * Generate a signed imgproxy URL without fetching the image\n *\n * This is useful for:\n * - Client-side usage where you want to display images directly\n * - Debugging URL signing issues\n * - Pre-generating URLs for batch processing\n *\n * @param source - Source image URL (must be http/https)\n * @param options - Processing options\n * @returns Signed imgproxy URL\n *\n * @example\n * ```typescript\n * const adapter = new ImgproxyAdapter({\n * type: 'imgproxy',\n * baseUrl: 'https://imgproxy.example.com',\n * key: 'hex-key',\n * salt: 'hex-salt'\n * });\n *\n * // Generate thumbnail URL\n * const url = adapter.getSignedUrl('https://example.com/image.jpg', {\n * width: 300,\n * height: 200,\n * fit: 'cover',\n * format: 'webp'\n * });\n * // Returns: https://imgproxy.example.com/SIGNATURE/rs:fill:300:200/aHR0cHM6Ly9.../image.webp\n * ```\n */\n getSignedUrl(\n source: string,\n options: {\n width?: number;\n height?: number;\n fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';\n quality?: number;\n format?: string;\n } = {},\n ): string {\n // Validate source URL\n if (!source.startsWith('http://') && !source.startsWith('https://')) {\n throw new ProcessingError(\n 'imgproxy requires HTTP/HTTPS URLs as source. ' +\n `Got: ${source.substring(0, 50)}...`,\n 'imgproxy',\n );\n }\n\n const resizeType = this.mapFitToResizeType(options.fit || 'cover');\n const processing = this.buildProcessingString({\n resize: resizeType,\n width: options.width || 0,\n height: options.height || 0,\n quality: options.quality,\n format: options.format,\n });\n\n return this.buildUrl(source, processing, options.format);\n }\n\n /**\n * Sign a path using HMAC-SHA256\n */\n private sign(path: string): string {\n if (!this.key || !this.salt) {\n return 'unsafe';\n }\n\n const hmac = createHmac('sha256', this.key);\n hmac.update(this.salt);\n hmac.update(path);\n\n // imgproxy expects base64url with padding stripped\n return hmac\n .digest('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '')\n .substring(0, 32);\n }\n\n /**\n * Encode source URL for imgproxy\n */\n private encodeSource(source: string): string {\n // imgproxy expects base64url encoded source\n return Buffer.from(source)\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n }\n\n /**\n * Build imgproxy URL\n */\n private buildUrl(\n source: string,\n processing: string,\n extension?: string,\n ): string {\n const encodedSource = this.encodeSource(source);\n const ext = extension ? `.${extension}` : '';\n const path = `/${processing}/${encodedSource}${ext}`;\n const signature = this.sign(path);\n return `${this.baseUrl}/${signature}${path}`;\n }\n\n /**\n * Convert ImageInput to URL string\n */\n private toUrl(input: ImageInput): string {\n if (Buffer.isBuffer(input)) {\n throw new ProcessingError(\n 'imgproxy adapter requires URL input, not Buffer. ' +\n 'Upload the buffer to a storage service first.',\n 'imgproxy',\n );\n }\n\n // Check if it's a valid URL\n if (!input.startsWith('http://') && !input.startsWith('https://')) {\n throw new ProcessingError(\n 'imgproxy adapter requires HTTP/HTTPS URLs as input. ' +\n `Got: ${input.substring(0, 50)}...`,\n 'imgproxy',\n );\n }\n\n return input;\n }\n\n async getDimensions(input: ImageInput): Promise<ImageDimensions> {\n const url = this.toUrl(input);\n\n try {\n // Use imgproxy's /info endpoint\n const infoUrl = this.buildUrl(url, 'info:1');\n\n const response = await fetch(infoUrl);\n if (!response.ok) {\n throw new RemoteServiceError(\n `imgproxy info request failed: ${response.statusText}`,\n 'imgproxy',\n response.status,\n );\n }\n\n const info = await response.json();\n return {\n width: info.width,\n height: info.height,\n };\n } catch (error) {\n if (\n error instanceof ProcessingError ||\n error instanceof RemoteServiceError\n ) {\n throw error;\n }\n throw new ProcessingError(\n `Failed to get dimensions: ${error instanceof Error ? error.message : String(error)}`,\n 'imgproxy',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n async thumbnail(\n input: ImageInput,\n output: string,\n options: ThumbnailOptions = {},\n ): Promise<void> {\n const url = this.toUrl(input);\n\n try {\n const processing = this.buildProcessingString({\n resize: 'fit',\n width: options.maxWidth || 0,\n height: options.maxHeight || 0,\n quality: options.quality,\n format: options.format,\n });\n\n const ext = options.format || this.inferFormat(output);\n const imgUrl = this.buildUrl(url, processing, ext);\n\n await this.fetchAndSave(imgUrl, output);\n } catch (error) {\n if (\n error instanceof ProcessingError ||\n error instanceof RemoteServiceError\n ) {\n throw error;\n }\n throw new ProcessingError(\n `Thumbnail generation failed: ${error instanceof Error ? error.message : String(error)}`,\n 'imgproxy',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n async convert(\n input: ImageInput,\n output: string,\n options: ConvertOptions = {},\n ): Promise<void> {\n const url = this.toUrl(input);\n\n try {\n const format = options.format || this.inferFormat(output);\n const processing = this.buildProcessingString({\n quality: options.quality,\n format,\n });\n\n const imgUrl = this.buildUrl(url, processing, format);\n await this.fetchAndSave(imgUrl, output);\n } catch (error) {\n if (\n error instanceof ProcessingError ||\n error instanceof RemoteServiceError\n ) {\n throw error;\n }\n throw new ProcessingError(\n `Format conversion failed: ${error instanceof Error ? error.message : String(error)}`,\n 'imgproxy',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n async getMetadata(input: ImageInput): Promise<ImageMetadata> {\n const url = this.toUrl(input);\n\n try {\n // Use imgproxy's /info endpoint\n const infoUrl = this.buildUrl(url, 'info:1');\n\n const response = await fetch(infoUrl);\n if (!response.ok) {\n throw new RemoteServiceError(\n `imgproxy info request failed: ${response.statusText}`,\n 'imgproxy',\n response.status,\n );\n }\n\n const info = await response.json();\n\n return {\n width: info.width,\n height: info.height,\n format: info.type || 'unknown',\n // imgproxy returns limited metadata\n // Additional EXIF/IPTC/XMP not typically available\n };\n } catch (error) {\n if (\n error instanceof ProcessingError ||\n error instanceof RemoteServiceError\n ) {\n throw error;\n }\n throw new ProcessingError(\n `Failed to get metadata: ${error instanceof Error ? error.message : String(error)}`,\n 'imgproxy',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n async hash(\n _input: ImageInput,\n _algorithm: HashAlgorithm = 'perceptual',\n ): Promise<string> {\n // imgproxy doesn't support hashing directly\n // Would need to fetch the image and compute hash locally\n throw new OperationNotSupportedError(\n 'hash (imgproxy does not support direct hashing - fetch image first)',\n 'imgproxy',\n );\n }\n\n async resize(\n input: ImageInput,\n output: string,\n options: ResizeOptions,\n ): Promise<void> {\n const url = this.toUrl(input);\n\n try {\n // Map fit modes to imgproxy resize types\n const resizeType = this.mapFitToResizeType(options.fit || 'cover');\n\n const processing = this.buildProcessingString({\n resize: resizeType,\n width: options.width || 0,\n height: options.height || 0,\n quality: options.quality,\n format: options.format,\n });\n\n const ext = options.format || this.inferFormat(output);\n const imgUrl = this.buildUrl(url, processing, ext);\n\n await this.fetchAndSave(imgUrl, output);\n } catch (error) {\n if (\n error instanceof ProcessingError ||\n error instanceof RemoteServiceError\n ) {\n throw error;\n }\n throw new ProcessingError(\n `Resize failed: ${error instanceof Error ? error.message : String(error)}`,\n 'imgproxy',\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Build imgproxy processing string\n */\n private buildProcessingString(opts: {\n resize?: string;\n width?: number;\n height?: number;\n quality?: number;\n format?: ImageFormat | string;\n }): string {\n const parts: string[] = [];\n\n // Resize: rs:<type>:<width>:<height>\n if (opts.resize) {\n const w = opts.width || 0;\n const h = opts.height || 0;\n parts.push(`rs:${opts.resize}:${w}:${h}`);\n }\n\n // Quality: q:<quality>\n if (opts.quality) {\n parts.push(`q:${opts.quality}`);\n }\n\n // Format is handled via extension, not processing string\n\n return parts.length > 0 ? parts.join('/') : 'raw:1';\n }\n\n /**\n * Map fit mode to imgproxy resize type\n */\n private mapFitToResizeType(fit: string): string {\n const map: Record<string, string> = {\n cover: 'fill', // Fill and crop\n contain: 'fit', // Fit within\n fill: 'force', // Force exact size\n inside: 'fit', // Same as contain\n outside: 'fill', // Same as cover\n };\n return map[fit] || 'fit';\n }\n\n /**\n * Fetch image from imgproxy and save to file\n */\n private async fetchAndSave(url: string, output: string): Promise<void> {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new RemoteServiceError(\n `imgproxy request failed: ${response.statusText}`,\n 'imgproxy',\n response.status,\n );\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n\n const { writeFile } = await import('node:fs/promises');\n await writeFile(output, buffer);\n }\n\n /**\n * Infer image format from file extension\n */\n private inferFormat(path: string): ImageFormat {\n const ext = path.split('.').pop()?.toLowerCase();\n const map: Record<string, ImageFormat> = {\n jpg: 'jpeg',\n jpeg: 'jpeg',\n png: 'png',\n webp: 'webp',\n avif: 'avif',\n gif: 'gif',\n tiff: 'tiff',\n tif: 'tiff',\n };\n return map[ext || ''] || 'jpeg';\n }\n}\n","/**\n * Jimp adapter for image processing\n * Uses pure JavaScript for image processing (no native dependencies)\n * Ideal for serverless/edge environments where Sharp cannot be used\n */\n\nimport {\n ImageNotFoundError,\n ProcessingError,\n UnsupportedFormatError,\n} from '../shared/errors.js';\nimport type {\n ConvertOptions,\n HashAlgorithm,\n ImageDimensions,\n ImageFormat,\n ImageInput,\n ImageMetadata,\n ImageProcessorInterface,\n JimpOptions,\n ResizeOptions,\n ThumbnailOptions,\n} from '../shared/types.js';\n\n// Jimp v1 type definitions\ninterface JimpImage {\n width: number;\n height: number;\n bitmap: {\n width: number;\n height: number;\n data: Buffer;\n };\n resize(options: { w?: number; h?: number }): JimpImage;\n scaleToFit(options: { w: number; h: number }): JimpImage;\n scale(factor: number): JimpImage;\n contain(options: { w: number; h: number }): JimpImage;\n cover(options: { w: number; h: number }): JimpImage;\n greyscale(): JimpImage;\n hash(base?: number): string;\n getBuffer(mime: string, options?: { quality?: number }): Promise<Buffer>;\n write(path: string): Promise<void>;\n clone(): JimpImage;\n}\n\ninterface JimpStatic {\n read(\n input: string | Buffer | { data: Buffer; mime: string },\n ): Promise<JimpImage>;\n}\n\n/**\n * Jimp adapter for pure JavaScript image processing\n *\n * Provides a fallback option when Sharp (native library) is not available.\n * Useful for:\n * - Serverless environments (AWS Lambda, Vercel Edge)\n * - Environments without native compilation support\n * - Cross-platform compatibility\n *\n * Limitations compared to Sharp:\n * - Slower processing speed\n * - Limited EXIF/metadata extraction\n * - No AVIF support\n *\n * @example\n * ```typescript\n * const processor = new JimpAdapter();\n * const dims = await processor.getDimensions('/path/to/image.jpg');\n * console.log(dims); // { width: 1920, height: 1080 }\n * ```\n */\nexport class JimpAdapter implements ImageProcessorInterface {\n private jimpStatic: JimpStatic | null = null;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Stored for API compatibility and future extensibility\n private options: JimpOptions;\n\n /**\n * Create a new Jimp adapter\n * @param options - Jimp adapter options\n */\n constructor(options: JimpOptions = { type: 'jimp' }) {\n this.options = options;\n }\n\n /**\n * Lazily load Jimp module\n */\n private async getJimp(): Promise<JimpStatic> {\n if (!this.jimpStatic) {\n try {\n const jimpModule = await import('jimp');\n this.jimpStatic = jimpModule.Jimp as unknown as JimpStatic;\n } catch (error) {\n throw new ProcessingError(\n 'Jimp library is not installed. Install it with: npm install jimp',\n 'jimp',\n error instanceof Error ? error : undefined,\n );\n }\n }\n return this.jimpStatic;\n }\n\n async getDimensions(input: ImageInput): Promise<ImageDimensions> {\n const Jimp = await this.getJimp();\n try {\n const image = await Jimp.read(input);\n return { width: image.width, height: image.height };\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async thumbnail(\n input: ImageInput,\n output: string,\n options: ThumbnailOptions = {},\n ): Promise<void> {\n const Jimp = await this.getJimp();\n try {\n const image = await Jimp.read(input);\n\n // Use scaleToFit to maintain aspect ratio within bounds\n if (options.maxWidth && options.maxHeight) {\n image.scaleToFit({ w: options.maxWidth, h: options.maxHeight });\n } else if (options.maxWidth) {\n const scale = options.maxWidth / image.width;\n if (scale < 1) {\n image.scale(scale);\n }\n } else if (options.maxHeight) {\n const scale = options.maxHeight / image.height;\n if (scale < 1) {\n image.scale(scale);\n }\n }\n\n await this.writeWithOptions(image, output, options.quality);\n } catch (error) {\n if (\n error instanceof ProcessingError ||\n error instanceof ImageNotFoundError\n ) {\n throw error;\n }\n throw this.mapError(error, input);\n }\n }\n\n async convert(\n input: ImageInput,\n output: string,\n options: ConvertOptions = {},\n ): Promise<void> {\n const Jimp = await this.getJimp();\n try {\n const image = await Jimp.read(input);\n await this.writeWithOptions(image, output, options.quality);\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async getMetadata(input: ImageInput): Promise<ImageMetadata> {\n const Jimp = await this.getJimp();\n try {\n const image = await Jimp.read(input);\n const mime = this.getMimeFromInput(input);\n const format = this.formatFromMime(mime);\n\n return {\n width: image.width,\n height: image.height,\n format,\n channels: 4, // Jimp uses RGBA internally\n hasAlpha: true, // Jimp always works with alpha channel\n // Jimp has limited metadata extraction\n // EXIF/IPTC/XMP not directly supported\n };\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async hash(\n input: ImageInput,\n algorithm: HashAlgorithm = 'perceptual',\n ): Promise<string> {\n const Jimp = await this.getJimp();\n\n try {\n if (algorithm === 'md5' || algorithm === 'sha256') {\n const { createHash } = await import('node:crypto');\n const image = await Jimp.read(input);\n const buffer = await image.getBuffer('image/png');\n return createHash(algorithm).update(buffer).digest('hex');\n }\n\n // Jimp has built-in perceptual hash\n const image = await Jimp.read(input);\n // Jimp's hash returns a 64-bit hash as a base64 string\n // We'll compute our own for consistency with Sharp adapter\n const resized = image.clone().resize({ w: 8, h: 8 }).greyscale();\n\n // Compute average hash manually\n const data = resized.bitmap.data;\n let sum = 0;\n const pixels: number[] = [];\n\n // Extract grayscale values (every 4th byte is alpha, we want R channel)\n for (let i = 0; i < data.length; i += 4) {\n const gray = data[i]; // R channel (grayscale means R=G=B)\n pixels.push(gray);\n sum += gray;\n }\n\n const avg = sum / pixels.length;\n let hash = '';\n for (const val of pixels) {\n hash += val >= avg ? '1' : '0';\n }\n return BigInt(`0b${hash}`).toString(16).padStart(16, '0');\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async resize(\n input: ImageInput,\n output: string,\n options: ResizeOptions,\n ): Promise<void> {\n const Jimp = await this.getJimp();\n try {\n const image = await Jimp.read(input);\n\n const fit = options.fit || 'cover';\n\n if (options.width && options.height) {\n if (fit === 'cover') {\n image.cover({ w: options.width, h: options.height });\n } else if (fit === 'contain' || fit === 'inside') {\n image.contain({ w: options.width, h: options.height });\n } else {\n // 'fill' - resize to exact dimensions\n image.resize({ w: options.width, h: options.height });\n }\n } else if (options.width) {\n const scale = options.width / image.width;\n image.scale(scale);\n } else if (options.height) {\n const scale = options.height / image.height;\n image.scale(scale);\n }\n\n await this.writeWithOptions(image, output, options.quality);\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n /**\n * Write image with quality settings based on output format\n */\n private async writeWithOptions(\n image: JimpImage,\n output: string,\n quality?: number,\n ): Promise<void> {\n const format = this.inferFormat(output);\n const mime = this.mimeFromFormat(format);\n\n if (format === 'avif') {\n throw new UnsupportedFormatError('avif', 'jimp');\n }\n\n const buffer = await image.getBuffer(mime, {\n quality: quality ?? 80,\n });\n\n const { writeFile } = await import('node:fs/promises');\n await writeFile(output, buffer);\n }\n\n /**\n * Infer image format from file extension\n */\n private inferFormat(path: string): ImageFormat {\n const ext = path.split('.').pop()?.toLowerCase();\n const map: Record<string, ImageFormat> = {\n jpg: 'jpeg',\n jpeg: 'jpeg',\n png: 'png',\n webp: 'webp',\n avif: 'avif',\n gif: 'gif',\n tiff: 'tiff',\n tif: 'tiff',\n bmp: 'png', // Convert BMP to PNG\n };\n return map[ext || ''] || 'jpeg';\n }\n\n /**\n * Get MIME type from format\n */\n private mimeFromFormat(format: ImageFormat): string {\n const map: Record<ImageFormat, string> = {\n jpeg: 'image/jpeg',\n png: 'image/png',\n webp: 'image/webp',\n avif: 'image/avif',\n gif: 'image/gif',\n tiff: 'image/tiff',\n };\n return map[format] || 'image/jpeg';\n }\n\n /**\n * Get format from MIME type\n */\n private formatFromMime(mime: string): string {\n const map: Record<string, string> = {\n 'image/jpeg': 'jpeg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/tiff': 'tiff',\n 'image/bmp': 'bmp',\n };\n return map[mime] || 'unknown';\n }\n\n /**\n * Try to determine MIME type from input\n */\n private getMimeFromInput(input: ImageInput): string {\n if (typeof input === 'string') {\n const format = this.inferFormat(input);\n return this.mimeFromFormat(format);\n }\n // For buffers, we'd need to check magic bytes\n // For simplicity, default to png\n return 'image/png';\n }\n\n /**\n * Map Jimp errors to our error types\n */\n private mapError(error: unknown, input: ImageInput): ProcessingError {\n const message = error instanceof Error ? error.message : String(error);\n\n // Check for common error patterns\n if (\n message.includes('no such file') ||\n message.includes('ENOENT') ||\n message.includes('Could not find')\n ) {\n const path = typeof input === 'string' ? input : '<buffer>';\n throw new ImageNotFoundError(path, 'jimp');\n }\n\n if (\n message.includes('not supported') ||\n message.includes('Could not find MIME')\n ) {\n throw new UnsupportedFormatError('unknown', 'jimp');\n }\n\n return new ProcessingError(\n `Image processing failed: ${message}`,\n 'jimp',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","/**\n * Sharp adapter for image processing\n * Uses the native sharp library for fast image processing\n */\n\nimport { ImageNotFoundError, ProcessingError } from '../shared/errors.js';\nimport type {\n ConvertOptions,\n HashAlgorithm,\n ImageDimensions,\n ImageFormat,\n ImageInput,\n ImageMetadata,\n ImageProcessorInterface,\n ResizeOptions,\n SharpOptions,\n ThumbnailOptions,\n} from '../shared/types.js';\n\n/**\n * Sharp adapter for high-performance image processing\n *\n * Uses the native sharp library which provides:\n * - Fast image resizing and format conversion\n * - Rich metadata extraction including EXIF, IPTC, XMP\n * - Perceptual hashing for image deduplication\n *\n * @example\n * ```typescript\n * const processor = new SharpAdapter();\n * const dims = await processor.getDimensions('/path/to/image.jpg');\n * console.log(dims); // { width: 1920, height: 1080 }\n * ```\n */\nexport class SharpAdapter implements ImageProcessorInterface {\n private sharp: typeof import('sharp') | null = null;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Stored for API compatibility and future extensibility\n private options: SharpOptions;\n\n /**\n * Create a new Sharp adapter\n * @param options - Sharp adapter options\n */\n constructor(options: SharpOptions = {}) {\n this.options = options;\n }\n\n /**\n * Lazily load sharp module\n */\n private async getSharp(): Promise<typeof import('sharp')> {\n if (!this.sharp) {\n try {\n this.sharp = (await import('sharp')).default;\n } catch (error) {\n throw new ProcessingError(\n 'Sharp library is not installed. Install it with: npm install sharp',\n 'sharp',\n error instanceof Error ? error : undefined,\n );\n }\n }\n return this.sharp;\n }\n\n async getDimensions(input: ImageInput): Promise<ImageDimensions> {\n const sharp = await this.getSharp();\n try {\n const metadata = await sharp(input).metadata();\n if (!metadata.width || !metadata.height) {\n throw new ProcessingError(\n 'Could not determine image dimensions',\n 'sharp',\n );\n }\n return { width: metadata.width, height: metadata.height };\n } catch (error) {\n if (error instanceof ProcessingError) throw error;\n throw this.mapError(error, input);\n }\n }\n\n async thumbnail(\n input: ImageInput,\n output: string,\n options: ThumbnailOptions = {},\n ): Promise<void> {\n const sharp = await this.getSharp();\n try {\n let pipeline = sharp(input).resize({\n width: options.maxWidth,\n height: options.maxHeight,\n fit: options.fit || 'inside',\n withoutEnlargement: true,\n });\n\n if (options.format) {\n pipeline = pipeline.toFormat(options.format, {\n quality: options.quality,\n });\n } else if (options.quality) {\n // Apply quality to inferred format from output path\n const format = this.inferFormat(output);\n pipeline = pipeline.toFormat(format, { quality: options.quality });\n }\n\n await pipeline.toFile(output);\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async convert(\n input: ImageInput,\n output: string,\n options: ConvertOptions = {},\n ): Promise<void> {\n const sharp = await this.getSharp();\n try {\n const format = options.format || this.inferFormat(output);\n await sharp(input)\n .toFormat(format, { quality: options.quality })\n .toFile(output);\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async getMetadata(input: ImageInput): Promise<ImageMetadata> {\n const sharp = await this.getSharp();\n try {\n const meta = await sharp(input).metadata();\n if (!meta.width || !meta.height || !meta.format) {\n throw new ProcessingError('Could not extract image metadata', 'sharp');\n }\n return {\n width: meta.width,\n height: meta.height,\n format: meta.format,\n space: meta.space,\n channels: meta.channels,\n depth: meta.depth,\n density: meta.density,\n hasAlpha: meta.hasAlpha,\n orientation: meta.orientation,\n exif: meta.exif ? this.parseExifBuffer(meta.exif) : undefined,\n iptc: meta.iptc ? this.parseIptcBuffer(meta.iptc) : undefined,\n xmp: meta.xmp ? this.parseXmpBuffer(meta.xmp) : undefined,\n };\n } catch (error) {\n if (error instanceof ProcessingError) throw error;\n throw this.mapError(error, input);\n }\n }\n\n async hash(\n input: ImageInput,\n algorithm: HashAlgorithm = 'perceptual',\n ): Promise<string> {\n const sharp = await this.getSharp();\n\n try {\n if (algorithm === 'md5' || algorithm === 'sha256') {\n const { createHash } = await import('node:crypto');\n const buffer = await sharp(input).toBuffer();\n return createHash(algorithm).update(buffer).digest('hex');\n }\n\n // Perceptual hash: resize to 8x8 grayscale, compute average hash\n const { data } = await sharp(input)\n .resize(8, 8, { fit: 'fill' })\n .grayscale()\n .raw()\n .toBuffer({ resolveWithObject: true });\n\n const avg = data.reduce((sum, val) => sum + val, 0) / data.length;\n let hash = '';\n for (const val of data) {\n hash += val >= avg ? '1' : '0';\n }\n // Convert binary string to hex\n return BigInt(`0b${hash}`).toString(16).padStart(16, '0');\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n async resize(\n input: ImageInput,\n output: string,\n options: ResizeOptions,\n ): Promise<void> {\n const sharp = await this.getSharp();\n try {\n let pipeline = sharp(input).resize({\n width: options.width,\n height: options.height,\n fit: options.fit || 'cover',\n });\n\n if (options.format) {\n pipeline = pipeline.toFormat(options.format, {\n quality: options.quality,\n });\n } else if (options.quality) {\n const format = this.inferFormat(output);\n pipeline = pipeline.toFormat(format, { quality: options.quality });\n }\n\n await pipeline.toFile(output);\n } catch (error) {\n throw this.mapError(error, input);\n }\n }\n\n /**\n * Infer image format from file extension\n */\n private inferFormat(path: string): ImageFormat {\n const ext = path.split('.').pop()?.toLowerCase();\n const map: Record<string, ImageFormat> = {\n jpg: 'jpeg',\n jpeg: 'jpeg',\n png: 'png',\n webp: 'webp',\n avif: 'avif',\n gif: 'gif',\n tiff: 'tiff',\n tif: 'tiff',\n };\n return map[ext || ''] || 'jpeg';\n }\n\n /**\n * Parse EXIF buffer to object (basic parsing)\n */\n private parseExifBuffer(buffer: Buffer): Record<string, unknown> {\n // For now, return basic info - could be expanded with exif-reader\n return { raw: `${buffer.toString('base64').slice(0, 100)}...` };\n }\n\n /**\n * Parse IPTC buffer to object (basic parsing)\n */\n private parseIptcBuffer(buffer: Buffer): Record<string, unknown> {\n return { raw: `${buffer.toString('base64').slice(0, 100)}...` };\n }\n\n /**\n * Parse XMP buffer to object (basic parsing)\n */\n private parseXmpBuffer(buffer: Buffer): Record<string, unknown> {\n // XMP is XML, try to extract as string\n try {\n const xmpString = buffer.toString('utf8');\n return { raw: `${xmpString.slice(0, 500)}...` };\n } catch {\n return { raw: `${buffer.toString('base64').slice(0, 100)}...` };\n }\n }\n\n /**\n * Map sharp errors to our error types\n */\n private mapError(error: unknown, input: ImageInput): ProcessingError {\n const message = error instanceof Error ? error.message : String(error);\n\n // Check for common error patterns\n if (message.includes('Input file is missing')) {\n const path = typeof input === 'string' ? input : '<buffer>';\n throw new ImageNotFoundError(path, 'sharp');\n }\n\n if (message.includes('Input buffer contains unsupported image format')) {\n throw new ProcessingError('Unsupported image format', 'sharp');\n }\n\n return new ProcessingError(\n `Image processing failed: ${message}`,\n 'sharp',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","/**\n * Headline Card Generator\n *\n * Generates branded social media / OG images with article titles.\n * Uses Satori (Vercel's JSX-to-SVG) and resvg for PNG output.\n *\n * @example\n * ```typescript\n * import { generateHeadlineCard } from '@happyvertical/images';\n *\n * const result = await generateHeadlineCard('Breaking: Major Discovery', {\n * brandColor: '#1a56db',\n * subtitle: 'Science News',\n * template: 'news'\n * });\n *\n * await writeFile('og-image.png', result.buffer);\n * ```\n */\n\n/**\n * Satori-compatible element structure (React-like JSX representation)\n * Satori uses this format without needing actual React\n */\ntype SatoriElement = {\n type: string;\n props: {\n style?: Record<string, string | number | undefined>;\n children?: SatoriChildren;\n [key: string]: any;\n };\n};\n\ntype SatoriChildren =\n | SatoriElement\n | SatoriElement[]\n | string\n | null\n | undefined\n | (SatoriElement | null | undefined)[];\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Template style for headline cards\n */\nexport type HeadlineCardTemplate = 'default' | 'news' | 'minimal';\n\n/**\n * Options for headline card generation\n */\nexport interface HeadlineCardOptions {\n /**\n * Width in pixels\n * @default 1200\n */\n width?: number;\n\n /**\n * Height in pixels\n * @default 630\n */\n height?: number;\n\n /**\n * Primary brand color (hex)\n * @default '#3b82f6'\n */\n brandColor?: string;\n\n /**\n * Background color (hex)\n * @default '#ffffff'\n */\n backgroundColor?: string;\n\n /**\n * Secondary text color (hex)\n * @default '#64748b'\n */\n textColor?: string;\n\n /**\n * Optional subtitle/category\n */\n subtitle?: string;\n\n /**\n * Optional logo URL (will be fetched and embedded)\n */\n logoUrl?: string;\n\n /**\n * Logo data as base64 or buffer (alternative to logoUrl)\n */\n logoData?: string | Buffer;\n\n /**\n * Card template style\n * @default 'default'\n */\n template?: HeadlineCardTemplate;\n\n /**\n * Font family name (must be available via Google Fonts or provide fontData)\n * @default 'Inter'\n */\n fontFamily?: string;\n\n /**\n * Custom font data (loaded font buffer)\n */\n fontData?: ArrayBuffer;\n}\n\n/**\n * Result from headline card generation\n */\nexport interface HeadlineCardResult {\n /** PNG image buffer */\n buffer: Buffer;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n /** MIME type (always image/png) */\n mimeType: 'image/png';\n}\n\n// ============================================================================\n// Font Loading\n// ============================================================================\n\n/**\n * Cached font data\n */\nlet cachedFontData: ArrayBuffer | null = null;\nlet cachedFontName: string | null = null;\n\n/**\n * Load font from Google Fonts\n *\n * Note: Satori requires TTF/OTF/WOFF format, NOT WOFF2.\n * We use an old browser User-Agent to get WOFF format from Google Fonts.\n */\nasync function loadGoogleFont(fontName: string): Promise<ArrayBuffer> {\n // Return cached font if same font\n if (cachedFontData && cachedFontName === fontName) {\n return cachedFontData;\n }\n\n // Fetch font from Google Fonts\n const fontUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontName)}:wght@400;600;700&display=swap`;\n\n const cssResponse = await fetch(fontUrl, {\n headers: {\n // Use an old user agent to get WOFF format (Satori doesn't support woff2)\n 'User-Agent':\n 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',\n },\n });\n\n if (!cssResponse.ok) {\n throw new Error(`Failed to fetch font CSS: ${cssResponse.statusText}`);\n }\n\n const css = await cssResponse.text();\n\n // Extract font URL from CSS - look for format('woff') or format('truetype')\n // Google Fonts URLs don't have file extensions, so match by format declaration\n // Priority: truetype (TTF) > woff > any URL (avoid woff2)\n let urlMatch = css.match(\n /src:\\s*url\\(([^)]+)\\)\\s*format\\(['\"]truetype['\"]\\)/,\n );\n if (!urlMatch) {\n urlMatch = css.match(/src:\\s*url\\(([^)]+)\\)\\s*format\\(['\"]woff['\"]\\)/);\n }\n if (!urlMatch) {\n // Fallback: just get any URL that's not followed by woff2\n urlMatch = css.match(/src:\\s*url\\(([^)]+)\\)(?!\\s*format\\(['\"]woff2['\"]\\))/);\n }\n if (!urlMatch) {\n throw new Error(\n `Could not find compatible font URL in CSS for ${fontName}. CSS: ${css.slice(0, 300)}...`,\n );\n }\n\n const fontFileUrl = urlMatch[1];\n const fontResponse = await fetch(fontFileUrl);\n\n if (!fontResponse.ok) {\n throw new Error(`Failed to fetch font file: ${fontResponse.statusText}`);\n }\n\n cachedFontData = await fontResponse.arrayBuffer();\n cachedFontName = fontName;\n\n return cachedFontData;\n}\n\n// ============================================================================\n// Template Renderers\n// ============================================================================\n\ninterface TemplateProps {\n title: string;\n options: Required<\n Pick<\n HeadlineCardOptions,\n | 'width'\n | 'height'\n | 'brandColor'\n | 'backgroundColor'\n | 'textColor'\n | 'fontFamily'\n >\n > &\n HeadlineCardOptions;\n}\n\n/**\n * Default template - clean with accent bar\n */\nfunction renderDefaultTemplate({\n title,\n options,\n}: TemplateProps): SatoriElement {\n const {\n width,\n height,\n brandColor,\n backgroundColor,\n textColor,\n subtitle,\n fontFamily,\n } = options;\n\n return {\n type: 'div',\n props: {\n style: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n width: '100%',\n height: '100%',\n backgroundColor,\n padding: '60px',\n fontFamily,\n },\n children: [\n // Top accent bar\n {\n type: 'div',\n props: {\n style: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n height: '8px',\n backgroundColor: brandColor,\n },\n },\n },\n // Subtitle\n subtitle\n ? {\n type: 'div',\n props: {\n style: {\n fontSize: '24px',\n fontWeight: 600,\n color: brandColor,\n marginBottom: '20px',\n textTransform: 'uppercase',\n letterSpacing: '2px',\n },\n children: subtitle,\n },\n }\n : null,\n // Title\n {\n type: 'div',\n props: {\n style: {\n fontSize: Math.min(72, Math.floor(width / (title.length * 0.6))),\n fontWeight: 700,\n color: '#1e293b',\n lineHeight: 1.2,\n maxWidth: '90%',\n },\n children: title,\n },\n },\n // Bottom accent\n {\n type: 'div',\n props: {\n style: {\n position: 'absolute',\n bottom: '60px',\n left: '60px',\n width: '80px',\n height: '4px',\n backgroundColor: brandColor,\n },\n },\n },\n ].filter(Boolean),\n },\n };\n}\n\n/**\n * News template - bold with side accent\n */\nfunction renderNewsTemplate({ title, options }: TemplateProps): SatoriElement {\n const {\n width,\n height,\n brandColor,\n backgroundColor,\n textColor,\n subtitle,\n fontFamily,\n } = options;\n\n return {\n type: 'div',\n props: {\n style: {\n display: 'flex',\n flexDirection: 'row',\n width: '100%',\n height: '100%',\n backgroundColor,\n fontFamily,\n },\n children: [\n // Left accent bar\n {\n type: 'div',\n props: {\n style: {\n width: '16px',\n height: '100%',\n backgroundColor: brandColor,\n },\n },\n },\n // Content area\n {\n type: 'div',\n props: {\n style: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n flex: 1,\n padding: '60px',\n },\n children: [\n // Category badge (using flex with alignSelf since Satori doesn't support inline-flex)\n subtitle\n ? {\n type: 'div',\n props: {\n style: {\n display: 'flex',\n alignSelf: 'flex-start',\n backgroundColor: brandColor,\n color: '#ffffff',\n fontSize: '18px',\n fontWeight: 600,\n padding: '8px 16px',\n borderRadius: '4px',\n marginBottom: '24px',\n textTransform: 'uppercase',\n letterSpacing: '1px',\n },\n children: subtitle,\n },\n }\n : null,\n // Title\n {\n type: 'div',\n props: {\n style: {\n fontSize: Math.min(\n 64,\n Math.floor((width - 100) / (title.length * 0.5)),\n ),\n fontWeight: 700,\n color: '#0f172a',\n lineHeight: 1.15,\n },\n children: title,\n },\n },\n ].filter(Boolean),\n },\n },\n ],\n },\n };\n}\n\n/**\n * Minimal template - simple and elegant\n */\nfunction renderMinimalTemplate({\n title,\n options,\n}: TemplateProps): SatoriElement {\n const { width, height, brandColor, backgroundColor, textColor, fontFamily } =\n options;\n\n return {\n type: 'div',\n props: {\n style: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n height: '100%',\n backgroundColor,\n padding: '80px',\n fontFamily,\n },\n children: [\n // Decorative line\n {\n type: 'div',\n props: {\n style: {\n width: '60px',\n height: '3px',\n backgroundColor: brandColor,\n marginBottom: '40px',\n },\n },\n },\n // Title\n {\n type: 'div',\n props: {\n style: {\n fontSize: Math.min(56, Math.floor(width / (title.length * 0.55))),\n fontWeight: 600,\n color: '#334155',\n lineHeight: 1.3,\n textAlign: 'center',\n maxWidth: '85%',\n },\n children: title,\n },\n },\n // Decorative line\n {\n type: 'div',\n props: {\n style: {\n width: '60px',\n height: '3px',\n backgroundColor: brandColor,\n marginTop: '40px',\n },\n },\n },\n ],\n },\n };\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Generate a headline card image\n *\n * Creates a branded social media / OG image with the article title.\n * Supports multiple templates and customization options.\n *\n * @param title - The headline text to display\n * @param options - Customization options\n * @returns Promise resolving to PNG image result\n *\n * @example Basic usage\n * ```typescript\n * const result = await generateHeadlineCard('Breaking News: AI Advances');\n * await fs.writeFile('og-image.png', result.buffer);\n * ```\n *\n * @example With branding\n * ```typescript\n * const result = await generateHeadlineCard('Local Council Approves Budget', {\n * brandColor: '#1a56db',\n * subtitle: 'Town News',\n * template: 'news'\n * });\n * ```\n *\n * @example Full customization\n * ```typescript\n * const result = await generateHeadlineCard('The Future of Technology', {\n * width: 1200,\n * height: 630,\n * brandColor: '#059669',\n * backgroundColor: '#f8fafc',\n * subtitle: 'Technology',\n * template: 'minimal',\n * fontFamily: 'Roboto'\n * });\n * ```\n */\nexport async function generateHeadlineCard(\n title: string,\n options: HeadlineCardOptions = {},\n): Promise<HeadlineCardResult> {\n // Apply defaults\n const width = options.width ?? 1200;\n const height = options.height ?? 630;\n const brandColor = options.brandColor ?? '#3b82f6';\n const backgroundColor = options.backgroundColor ?? '#ffffff';\n const textColor = options.textColor ?? '#64748b';\n const template = options.template ?? 'default';\n const fontFamily = options.fontFamily ?? 'Inter';\n\n const resolvedOptions = {\n ...options,\n width,\n height,\n brandColor,\n backgroundColor,\n textColor,\n fontFamily,\n };\n\n // Load satori and resvg dynamically\n const [{ default: satori }, { Resvg }] = await Promise.all([\n import('satori'),\n import('@resvg/resvg-js'),\n ]);\n\n // Load font\n const fontData = options.fontData ?? (await loadGoogleFont(fontFamily));\n\n // Select template renderer\n let content: SatoriElement;\n switch (template) {\n case 'news':\n content = renderNewsTemplate({ title, options: resolvedOptions });\n break;\n case 'minimal':\n content = renderMinimalTemplate({ title, options: resolvedOptions });\n break;\n default:\n content = renderDefaultTemplate({ title, options: resolvedOptions });\n }\n\n // Generate SVG with satori\n // Cast to any because satori expects React elements but accepts our structure\n const svg = await satori(content as any, {\n width,\n height,\n fonts: [\n {\n name: fontFamily,\n data: fontData,\n weight: 400,\n style: 'normal',\n },\n {\n name: fontFamily,\n data: fontData,\n weight: 600,\n style: 'normal',\n },\n {\n name: fontFamily,\n data: fontData,\n weight: 700,\n style: 'normal',\n },\n ],\n });\n\n // Convert SVG to PNG with resvg\n const resvg = new Resvg(svg, {\n fitTo: {\n mode: 'width',\n value: width,\n },\n });\n\n const pngData = resvg.render();\n const buffer = Buffer.from(pngData.asPng());\n\n return {\n buffer,\n width,\n height,\n mimeType: 'image/png',\n };\n}\n\n/**\n * Reset cached font data (useful for testing)\n */\nexport function resetFontCache(): void {\n cachedFontData = null;\n cachedFontName = null;\n}\n","/**\n * Factory function for creating image processor instances\n */\n\nimport { InvalidAdapterError } from './errors.js';\nimport type {\n GetImageProcessorOptions,\n ImageProcessorInterface,\n ImgproxyOptions,\n JimpOptions,\n SharpOptions,\n} from './types.js';\n\n/**\n * Type guards for processor options\n */\n\n/**\n * Checks if the options are for Sharp adapter\n */\nfunction isSharpOptions(\n options: GetImageProcessorOptions,\n): options is SharpOptions {\n return !options.type || options.type === 'sharp';\n}\n\n/**\n * Checks if the options are for Jimp adapter\n */\nfunction isJimpOptions(\n options: GetImageProcessorOptions,\n): options is JimpOptions {\n return options.type === 'jimp';\n}\n\n/**\n * Checks if the options are for imgproxy adapter\n */\nfunction isImgproxyOptions(\n options: GetImageProcessorOptions,\n): options is ImgproxyOptions {\n return options.type === 'imgproxy';\n}\n\n/**\n * Load environment variables for configuration\n * Pattern: HAVE_IMAGES_*\n */\nfunction loadEnvConfig(\n options: GetImageProcessorOptions,\n): GetImageProcessorOptions {\n // Only available in Node.js environments\n if (typeof process === 'undefined' || !process.env) {\n return options;\n }\n\n const env = process.env;\n\n // Build defaults from environment\n const envDefaults: Record<string, unknown> = {};\n\n // HAVE_IMAGES_TYPE or HAVE_IMAGES_ADAPTER\n const typeEnv = env.HAVE_IMAGES_TYPE || env.HAVE_IMAGES_ADAPTER;\n if (typeEnv && !options.type) {\n envDefaults.type = typeEnv;\n }\n\n // imgproxy-specific options\n if (env.HAVE_IMAGES_BASE_URL) {\n envDefaults.baseUrl = env.HAVE_IMAGES_BASE_URL;\n }\n if (env.HAVE_IMAGES_KEY) {\n envDefaults.key = env.HAVE_IMAGES_KEY;\n }\n if (env.HAVE_IMAGES_SALT) {\n envDefaults.salt = env.HAVE_IMAGES_SALT;\n }\n\n // Merge environment defaults with user options (user options take precedence)\n return { ...envDefaults, ...options } as GetImageProcessorOptions;\n}\n\n/**\n * Creates an image processor instance based on the provided options.\n *\n * Supports environment variable configuration using the pattern:\n * - HAVE_IMAGES_TYPE → adapter type ('sharp', 'jimp', 'imgproxy')\n * - HAVE_IMAGES_BASE_URL → imgproxy base URL\n * - HAVE_IMAGES_KEY → imgproxy signing key (hex)\n * - HAVE_IMAGES_SALT → imgproxy signing salt (hex)\n *\n * User-provided options always take precedence over environment variables.\n *\n * @param options - Configuration options for the image processor\n * @returns Promise resolving to an image processor instance\n * @throws {InvalidAdapterError} When the adapter type is unsupported\n *\n * @example\n * ```typescript\n * // Create Sharp processor (default)\n * const processor = await getImageProcessor();\n *\n * // Create Jimp processor\n * const jimpProcessor = await getImageProcessor({ type: 'jimp' });\n *\n * // Create imgproxy processor\n * const imgproxyProcessor = await getImageProcessor({\n * type: 'imgproxy',\n * baseUrl: 'https://imgproxy.example.com',\n * key: 'abcd1234...',\n * salt: 'efgh5678...'\n * });\n *\n * // Use environment variables (HAVE_IMAGES_TYPE=sharp)\n * const autoProcessor = await getImageProcessor({});\n * ```\n */\nexport async function getImageProcessor(\n options: GetImageProcessorOptions = {},\n): Promise<ImageProcessorInterface> {\n // Load environment variables\n options = loadEnvConfig(options);\n\n if (isSharpOptions(options)) {\n const { SharpAdapter } = await import('../adapters/sharp.js');\n return new SharpAdapter(options);\n }\n\n if (isJimpOptions(options)) {\n const { JimpAdapter } = await import('../adapters/jimp.js');\n return new JimpAdapter(options);\n }\n\n if (isImgproxyOptions(options)) {\n const { ImgproxyAdapter } = await import('../adapters/imgproxy.js');\n return new ImgproxyAdapter(options);\n }\n\n throw new InvalidAdapterError(\n (options as { type?: string }).type || 'unknown',\n );\n}\n\n/**\n * Get a list of available adapter types\n */\nexport function getAvailableAdapters(): string[] {\n return ['sharp', 'jimp', 'imgproxy'];\n}\n","/**\n * @happyvertical/images - Image processing utilities with adapter pattern\n *\n * Provides a unified interface for image processing that scales from\n * static sites to enterprise deployments:\n *\n * - **Sharp adapter**: Fast native processing (default)\n * - **Jimp adapter**: Pure JavaScript fallback (serverless/edge)\n * - **imgproxy adapter**: Self-hosted remote processing (scale)\n *\n * @example Basic usage with auto-detection\n * ```typescript\n * import { getDimensions, generateThumbnail } from '@happyvertical/images';\n *\n * // Automatically uses Sharp if available, falls back to Jimp\n * const dims = await getDimensions('/path/to/image.jpg');\n * await generateThumbnail('/path/to/image.jpg', '/path/to/thumb.jpg', {\n * maxWidth: 300,\n * maxHeight: 300\n * });\n * ```\n *\n * @example Using factory directly\n * ```typescript\n * import { getImageProcessor } from '@happyvertical/images';\n *\n * const processor = await getImageProcessor({ type: 'sharp' });\n * const metadata = await processor.getMetadata('/path/to/image.jpg');\n * ```\n *\n * @example Using imgproxy for remote processing\n * ```typescript\n * import { getImageProcessor } from '@happyvertical/images';\n *\n * const processor = await getImageProcessor({\n * type: 'imgproxy',\n * baseUrl: 'https://imgproxy.example.com',\n * key: 'signing-key',\n * salt: 'signing-salt'\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n ImgproxyAdapter,\n type SignedUrlOptions,\n signImgproxyUrl,\n} from './adapters/imgproxy.js';\nexport { JimpAdapter } from './adapters/jimp.js';\n// Export adapters for direct use\nexport { SharpAdapter } from './adapters/sharp.js';\n// Export headline card generation\nexport {\n generateHeadlineCard,\n type HeadlineCardOptions,\n type HeadlineCardResult,\n type HeadlineCardTemplate,\n resetFontCache,\n} from './headline-card.js';\n// Export all errors\nexport * from './shared/errors.js';\n// Export factory\nexport { getAvailableAdapters, getImageProcessor } from './shared/factory.js';\n// Export all types\nexport * from './shared/types.js';\n\nimport type {\n ConvertOptions,\n GetImageProcessorOptions,\n HashAlgorithm,\n ImageDimensions,\n ImageInput,\n ImageMetadata,\n ImageProcessorInterface,\n ResizeOptions,\n ThumbnailOptions,\n} from './shared/types.js';\n\n/**\n * Cached processor instance for auto-detection\n */\nlet cachedProcessor: ImageProcessorInterface | null = null;\nlet detectionAttempted = false;\n\n/**\n * Get a default processor with auto-detection\n *\n * Auto-detection priority:\n * 1. If explicit adapter options provided, use those\n * 2. Try to load Sharp (fast native processing)\n * 3. Fall back to Jimp (pure JavaScript)\n *\n * The processor is cached for subsequent calls to avoid repeated detection.\n */\nasync function getDefaultProcessor(\n adapterOptions?: GetImageProcessorOptions,\n): Promise<ImageProcessorInterface> {\n // If explicit options provided with a type, use them directly\n if (adapterOptions?.type) {\n const { getImageProcessor } = await import('./shared/factory.js');\n return getImageProcessor(adapterOptions);\n }\n\n // Use cached processor if available\n if (cachedProcessor && !adapterOptions) {\n return cachedProcessor;\n }\n\n // Auto-detect: try sharp first, fall back to jimp\n if (!detectionAttempted) {\n detectionAttempted = true;\n\n try {\n // Try to load sharp\n await import('sharp');\n const { SharpAdapter } = await import('./adapters/sharp.js');\n cachedProcessor = new SharpAdapter({});\n } catch {\n // Sharp not available, use jimp\n const { JimpAdapter } = await import('./adapters/jimp.js');\n cachedProcessor = new JimpAdapter({ type: 'jimp' });\n }\n }\n\n if (!cachedProcessor) {\n throw new Error('No image processor available. Install sharp or jimp.');\n }\n return cachedProcessor;\n}\n\n/**\n * Reset the cached processor (useful for testing)\n */\nexport function resetProcessor(): void {\n cachedProcessor = null;\n detectionAttempted = false;\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Get image dimensions\n *\n * Uses auto-detection to select the best available processor.\n *\n * @param input - Image path or Buffer\n * @param adapterOptions - Optional adapter configuration to override auto-detection\n * @returns Promise resolving to image dimensions\n *\n * @example\n * ```typescript\n * import { getDimensions } from '@happyvertical/images';\n *\n * // Auto-detect adapter\n * const dims = await getDimensions('/path/to/image.jpg');\n * console.log(`${dims.width}x${dims.height}`);\n *\n * // Force specific adapter\n * const jimp = await getDimensions(buffer, { type: 'jimp' });\n * ```\n */\nexport async function getDimensions(\n input: ImageInput,\n adapterOptions?: GetImageProcessorOptions,\n): Promise<ImageDimensions> {\n const processor = await getDefaultProcessor(adapterOptions);\n return processor.getDimensions(input);\n}\n\n/**\n * Generate a thumbnail image\n *\n * Creates a smaller version of the image while maintaining aspect ratio.\n * Uses auto-detection to select the best available processor.\n *\n * @param input - Source image path or Buffer\n * @param output - Output file path\n * @param options - Thumbnail options (maxWidth, maxHeight, quality, format)\n * @param adapterOptions - Optional adapter configuration to override auto-detection\n *\n * @example\n * ```typescript\n * import { generateThumbnail } from '@happyvertical/images';\n *\n * // Create 300x300 max thumbnail\n * await generateThumbnail('/path/to/image.jpg', '/path/to/thumb.jpg', {\n * maxWidth: 300,\n * maxHeight: 300,\n * quality: 85\n * });\n *\n * // Convert to WebP while thumbnailing\n * await generateThumbnail('/path/to/image.jpg', '/path/to/thumb.webp', {\n * maxWidth: 200,\n * format: 'webp'\n * });\n * ```\n */\nexport async function generateThumbnail(\n input: ImageInput,\n output: string,\n options?: ThumbnailOptions,\n adapterOptions?: GetImageProcessorOptions,\n): Promise<void> {\n const processor = await getDefaultProcessor(adapterOptions);\n return processor.thumbnail(input, output, options);\n}\n\n/**\n * Convert image format\n *\n * Converts an image to a different format with optional quality settings.\n * Uses auto-detection to select the best available processor.\n *\n * @param input - Source image path or Buffer\n * @param output - Output file path (format inferred from extension)\n * @param options - Conversion options (format, quality)\n * @param adapterOptions - Optional adapter configuration to override auto-detection\n *\n * @example\n * ```typescript\n * import { convertFormat } from '@happyvertical/images';\n *\n * // Convert PNG to JPEG\n * await convertFormat('/path/to/image.png', '/path/to/image.jpg', {\n * quality: 90\n * });\n *\n * // Convert to WebP with explicit format\n * await convertFormat('/path/to/image.jpg', '/path/to/image.webp', {\n * format: 'webp',\n * quality: 80\n * });\n * ```\n */\nexport async function convertFormat(\n input: ImageInput,\n output: string,\n options?: ConvertOptions,\n adapterOptions?: GetImageProcessorOptions,\n): Promise<void> {\n const processor = await getDefaultProcessor(adapterOptions);\n return processor.convert(input, output, options);\n}\n\n/**\n * Get image metadata\n *\n * Extracts comprehensive metadata including dimensions, format, color space,\n * and optionally EXIF/IPTC/XMP data.\n * Uses auto-detection to select the best available processor.\n *\n * @param input - Image path or Buffer\n * @param adapterOptions - Optional adapter configuration to override auto-detection\n * @returns Promise resolving to metadata object\n *\n * @example\n * ```typescript\n * import { getImageMetadata } from '@happyvertical/images';\n *\n * const metadata = await getImageMetadata('/path/to/photo.jpg');\n * console.log(`Format: ${metadata.format}`);\n * console.log(`Dimensions: ${metadata.width}x${metadata.height}`);\n * console.log(`Has alpha: ${metadata.hasAlpha}`);\n * if (metadata.exif) {\n * console.log('EXIF data:', metadata.exif);\n * }\n * ```\n */\nexport async function getImageMetadata(\n input: ImageInput,\n adapterOptions?: GetImageProcessorOptions,\n): Promise<ImageMetadata> {\n const processor = await getDefaultProcessor(adapterOptions);\n return processor.getMetadata(input);\n}\n\n/**\n * Compute image hash\n *\n * Generates a hash of the image for deduplication or comparison.\n * Uses auto-detection to select the best available processor.\n *\n * @param input - Image path or Buffer\n * @param algorithm - Hash algorithm: 'perceptual' (default), 'md5', 'sha256'\n * @param adapterOptions - Optional adapter configuration to override auto-detection\n * @returns Promise resolving to hash string\n *\n * @example\n * ```typescript\n * import { getImageHash } from '@happyvertical/images';\n *\n * // Perceptual hash for finding similar images\n * const pHash = await getImageHash('/path/to/image.jpg');\n * console.log(`Perceptual hash: ${pHash}`);\n *\n * // Cryptographic hash for exact matching\n * const sha256 = await getImageHash('/path/to/image.jpg', 'sha256');\n * console.log(`SHA-256: ${sha256}`);\n * ```\n */\nexport async function getImageHash(\n input: ImageInput,\n algorithm: HashAlgorithm = 'perceptual',\n adapterOptions?: GetImageProcessorOptions,\n): Promise<string> {\n const processor = await getDefaultProcessor(adapterOptions);\n return processor.hash(input, algorithm);\n}\n\n/**\n * Resize image with full control\n *\n * Resizes an image to specific dimensions with control over fit mode.\n * Uses auto-detection to select the best available processor.\n *\n * @param input - Source image path or Buffer\n * @param output - Output file path\n * @param options - Resize options (width, height, fit, quality, format)\n * @param adapterOptions - Optional adapter configuration to override auto-detection\n *\n * @example\n * ```typescript\n * import { resizeImage } from '@happyvertical/images';\n *\n * // Resize to exact dimensions (may crop)\n * await resizeImage('/path/to/image.jpg', '/path/to/resized.jpg', {\n * width: 800,\n * height: 600,\n * fit: 'cover' // Crop to fill\n * });\n *\n * // Resize to fit within dimensions (no crop)\n * await resizeImage('/path/to/image.jpg', '/path/to/resized.jpg', {\n * width: 800,\n * height: 600,\n * fit: 'inside' // Fit within, may be smaller\n * });\n * ```\n */\nexport async function resizeImage(\n input: ImageInput,\n output: string,\n options: ResizeOptions,\n adapterOptions?: GetImageProcessorOptions,\n): Promise<void> {\n const processor = await getDefaultProcessor(adapterOptions);\n return processor.resize(input, output, options);\n}\n"],"names":["image","sharp","SharpAdapter","JimpAdapter","ImgproxyAdapter","getImageProcessor"],"mappings":";AAOO,MAAM,mBAAmB,MAAM;AAAA,EACpC,YACE,SACO,MACA,SACP;AACA,UAAM,OAAO;AAHN,SAAA,OAAA;AACA,SAAA,UAAA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,2BAA2B,WAAW;AAAA,EACjD,YAAY,MAAc,SAAkB;AAC1C,UAAM,oBAAoB,IAAI,IAAI,mBAAmB,OAAO;AAC5D,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,+BAA+B,WAAW;AAAA,EACrD,YAAY,QAAgB,SAAkB;AAC5C,UAAM,6BAA6B,MAAM,IAAI,sBAAsB,OAAO;AAC1E,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,mCAAmC,WAAW;AAAA,EACzD,YAAY,WAAmB,SAAkB;AAC/C;AAAA,MACE,4BAA4B,SAAS;AAAA,MACrC;AAAA,MACA;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,wBAAwB,WAAW;AAAA,EAC9C,YACE,SACA,SACO,OACP;AACA,UAAM,SAAS,oBAAoB,OAAO;AAFnC,SAAA,QAAA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,4BAA4B,WAAW;AAAA,EAClD,YAAY,MAAc;AACxB,UAAM,yBAAyB,IAAI,IAAI,iBAAiB;AACxD,SAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAM,2BAA2B,WAAW;AAAA,EACjD,YACE,SACA,SACO,YACP;AACA,UAAM,SAAS,wBAAwB,OAAO;AAFvC,SAAA,aAAA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;ACDO,SAAS,gBACd,QACA,QAKA,UAA4B,CAAA,GACpB;AACR,QAAM,UAAU,IAAI,gBAAgB;AAAA,IAClC,MAAM;AAAA,IACN,SAAS,OAAO;AAAA,IAChB,KAAK,OAAO;AAAA,IACZ,MAAM,OAAO;AAAA,EAAA,CACd;AACD,SAAO,QAAQ,aAAa,QAAQ,OAAO;AAC7C;AAmCO,MAAM,gBAAmD;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAA0B;AACpC,SAAK,UAAU,QAAQ,QAAQ,QAAQ,OAAO,EAAE;AAEhD,QAAI,QAAQ,OAAO,QAAQ,MAAM;AAC/B,WAAK,MAAM,OAAO,KAAK,QAAQ,KAAK,KAAK;AACzC,WAAK,OAAO,OAAO,KAAK,QAAQ,MAAM,KAAK;AAAA,IAC7C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCA,aACE,QACA,UAMI,IACI;AAER,QAAI,CAAC,OAAO,WAAW,SAAS,KAAK,CAAC,OAAO,WAAW,UAAU,GAAG;AACnE,YAAM,IAAI;AAAA,QACR,qDACU,OAAO,UAAU,GAAG,EAAE,CAAC;AAAA,QACjC;AAAA,MAAA;AAAA,IAEJ;AAEA,UAAM,aAAa,KAAK,mBAAmB,QAAQ,OAAO,OAAO;AACjE,UAAM,aAAa,KAAK,sBAAsB;AAAA,MAC5C,QAAQ;AAAA,MACR,OAAO,QAAQ,SAAS;AAAA,MACxB,QAAQ,QAAQ,UAAU;AAAA,MAC1B,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,IAAA,CACjB;AAED,WAAO,KAAK,SAAS,QAAQ,YAAY,QAAQ,MAAM;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKQ,KAAK,MAAsB;AACjC,QAAI,CAAC,KAAK,OAAO,CAAC,KAAK,MAAM;AAC3B,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,WAAW,UAAU,KAAK,GAAG;AAC1C,SAAK,OAAO,KAAK,IAAI;AACrB,SAAK,OAAO,IAAI;AAGhB,WAAO,KACJ,OAAO,QAAQ,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE,EACjB,UAAU,GAAG,EAAE;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,QAAwB;AAE3C,WAAO,OAAO,KAAK,MAAM,EACtB,SAAS,QAAQ,EACjB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKQ,SACN,QACA,YACA,WACQ;AACR,UAAM,gBAAgB,KAAK,aAAa,MAAM;AAC9C,UAAM,MAAM,YAAY,IAAI,SAAS,KAAK;AAC1C,UAAM,OAAO,IAAI,UAAU,IAAI,aAAa,GAAG,GAAG;AAClD,UAAM,YAAY,KAAK,KAAK,IAAI;AAChC,WAAO,GAAG,KAAK,OAAO,IAAI,SAAS,GAAG,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAM,OAA2B;AACvC,QAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,QAEA;AAAA,MAAA;AAAA,IAEJ;AAGA,QAAI,CAAC,MAAM,WAAW,SAAS,KAAK,CAAC,MAAM,WAAW,UAAU,GAAG;AACjE,YAAM,IAAI;AAAA,QACR,4DACU,MAAM,UAAU,GAAG,EAAE,CAAC;AAAA,QAChC;AAAA,MAAA;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cAAc,OAA6C;AAC/D,UAAM,MAAM,KAAK,MAAM,KAAK;AAE5B,QAAI;AAEF,YAAM,UAAU,KAAK,SAAS,KAAK,QAAQ;AAE3C,YAAM,WAAW,MAAM,MAAM,OAAO;AACpC,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,iCAAiC,SAAS,UAAU;AAAA,UACpD;AAAA,UACA,SAAS;AAAA,QAAA;AAAA,MAEb;AAEA,YAAM,OAAO,MAAM,SAAS,KAAA;AAC5B,aAAO;AAAA,QACL,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MAAA;AAAA,IAEjB,SAAS,OAAO;AACd,UACE,iBAAiB,mBACjB,iBAAiB,oBACjB;AACA,cAAM;AAAA,MACR;AACA,YAAM,IAAI;AAAA,QACR,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACnF;AAAA,QACA,iBAAiB,QAAQ,QAAQ;AAAA,MAAA;AAAA,IAErC;AAAA,EACF;AAAA,EAEA,MAAM,UACJ,OACA,QACA,UAA4B,CAAA,GACb;AACf,UAAM,MAAM,KAAK,MAAM,KAAK;AAE5B,QAAI;AACF,YAAM,aAAa,KAAK,sBAAsB;AAAA,QAC5C,QAAQ;AAAA,QACR,OAAO,QAAQ,YAAY;AAAA,QAC3B,QAAQ,QAAQ,aAAa;AAAA,QAC7B,SAAS,QAAQ;AAAA,QACjB,QAAQ,QAAQ;AAAA,MAAA,CACjB;AAED,YAAM,MAAM,QAAQ,UAAU,KAAK,YAAY,MAAM;AACrD,YAAM,SAAS,KAAK,SAAS,KAAK,YAAY,GAAG;AAEjD,YAAM,KAAK,aAAa,QAAQ,MAAM;AAAA,IACxC,SAAS,OAAO;AACd,UACE,iBAAiB,mBACjB,iBAAiB,oBACjB;AACA,cAAM;AAAA,MACR;AACA,YAAM,IAAI;AAAA,QACR,gCAAgC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACtF;AAAA,QACA,iBAAiB,QAAQ,QAAQ;AAAA,MAAA;AAAA,IAErC;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,OACA,QACA,UAA0B,CAAA,GACX;AACf,UAAM,MAAM,KAAK,MAAM,KAAK;AAE5B,QAAI;AACF,YAAM,SAAS,QAAQ,UAAU,KAAK,YAAY,MAAM;AACxD,YAAM,aAAa,KAAK,sBAAsB;AAAA,QAC5C,SAAS,QAAQ;AAAA,QACjB;AAAA,MAAA,CACD;AAED,YAAM,SAAS,KAAK,SAAS,KAAK,YAAY,MAAM;AACpD,YAAM,KAAK,aAAa,QAAQ,MAAM;AAAA,IACxC,SAAS,OAAO;AACd,UACE,iBAAiB,mBACjB,iBAAiB,oBACjB;AACA,cAAM;AAAA,MACR;AACA,YAAM,IAAI;AAAA,QACR,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACnF;AAAA,QACA,iBAAiB,QAAQ,QAAQ;AAAA,MAAA;AAAA,IAErC;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAA2C;AAC3D,UAAM,MAAM,KAAK,MAAM,KAAK;AAE5B,QAAI;AAEF,YAAM,UAAU,KAAK,SAAS,KAAK,QAAQ;AAE3C,YAAM,WAAW,MAAM,MAAM,OAAO;AACpC,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,iCAAiC,SAAS,UAAU;AAAA,UACpD;AAAA,UACA,SAAS;AAAA,QAAA;AAAA,MAEb;AAEA,YAAM,OAAO,MAAM,SAAS,KAAA;AAE5B,aAAO;AAAA,QACL,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK,QAAQ;AAAA;AAAA;AAAA,MAAA;AAAA,IAIzB,SAAS,OAAO;AACd,UACE,iBAAiB,mBACjB,iBAAiB,oBACjB;AACA,cAAM;AAAA,MACR;AACA,YAAM,IAAI;AAAA,QACR,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACjF;AAAA,QACA,iBAAiB,QAAQ,QAAQ;AAAA,MAAA;AAAA,IAErC;AAAA,EACF;AAAA,EAEA,MAAM,KACJ,QACA,aAA4B,cACX;AAGjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,OACJ,OACA,QACA,SACe;AACf,UAAM,MAAM,KAAK,MAAM,KAAK;AAE5B,QAAI;AAEF,YAAM,aAAa,KAAK,mBAAmB,QAAQ,OAAO,OAAO;AAEjE,YAAM,aAAa,KAAK,sBAAsB;AAAA,QAC5C,QAAQ;AAAA,QACR,OAAO,QAAQ,SAAS;AAAA,QACxB,QAAQ,QAAQ,UAAU;AAAA,QAC1B,SAAS,QAAQ;AAAA,QACjB,QAAQ,QAAQ;AAAA,MAAA,CACjB;AAED,YAAM,MAAM,QAAQ,UAAU,KAAK,YAAY,MAAM;AACrD,YAAM,SAAS,KAAK,SAAS,KAAK,YAAY,GAAG;AAEjD,YAAM,KAAK,aAAa,QAAQ,MAAM;AAAA,IACxC,SAAS,OAAO;AACd,UACE,iBAAiB,mBACjB,iBAAiB,oBACjB;AACA,cAAM;AAAA,MACR;AACA,YAAM,IAAI;AAAA,QACR,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACxE;AAAA,QACA,iBAAiB,QAAQ,QAAQ;AAAA,MAAA;AAAA,IAErC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,MAMnB;AACT,UAAM,QAAkB,CAAA;AAGxB,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,KAAK,SAAS;AACxB,YAAM,IAAI,KAAK,UAAU;AACzB,YAAM,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE;AAAA,IAC1C;AAGA,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,KAAK,KAAK,OAAO,EAAE;AAAA,IAChC;AAIA,WAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,KAAqB;AAC9C,UAAM,MAA8B;AAAA,MAClC,OAAO;AAAA;AAAA,MACP,SAAS;AAAA;AAAA,MACT,MAAM;AAAA;AAAA,MACN,QAAQ;AAAA;AAAA,MACR,SAAS;AAAA;AAAA,IAAA;AAEX,WAAO,IAAI,GAAG,KAAK;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAAa,KAAa,QAA+B;AACrE,UAAM,WAAW,MAAM,MAAM,GAAG;AAEhC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,4BAA4B,SAAS,UAAU;AAAA,QAC/C;AAAA,QACA,SAAS;AAAA,MAAA;AAAA,IAEb;AAEA,UAAM,SAAS,OAAO,KAAK,MAAM,SAAS,aAAa;AAEvD,UAAM,EAAE,UAAA,IAAc,MAAM,OAAO,kBAAkB;AACrD,UAAM,UAAU,QAAQ,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,MAA2B;AAC7C,UAAM,MAAM,KAAK,MAAM,GAAG,EAAE,IAAA,GAAO,YAAA;AACnC,UAAM,MAAmC;AAAA,MACvC,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,IAAA;AAEP,WAAO,IAAI,OAAO,EAAE,KAAK;AAAA,EAC3B;AACF;;;;;;ACteO,MAAM,YAA+C;AAAA,EAClD,aAAgC;AAAA;AAAA,EAEhC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,UAAuB,EAAE,MAAM,UAAU;AACnD,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,UAA+B;AAC3C,QAAI,CAAC,KAAK,YAAY;AACpB,UAAI;AACF,cAAM,aAAa,MAAM,OAAO,MAAM;AACtC,aAAK,aAAa,WAAW;AAAA,MAC/B,SAAS,OAAO;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA,iBAAiB,QAAQ,QAAQ;AAAA,QAAA;AAAA,MAErC;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,cAAc,OAA6C;AAC/D,UAAM,OAAO,MAAM,KAAK,QAAA;AACxB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AACnC,aAAO,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAA;AAAA,IAC7C,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,UACJ,OACA,QACA,UAA4B,CAAA,GACb;AACf,UAAM,OAAO,MAAM,KAAK,QAAA;AACxB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AAGnC,UAAI,QAAQ,YAAY,QAAQ,WAAW;AACzC,cAAM,WAAW,EAAE,GAAG,QAAQ,UAAU,GAAG,QAAQ,WAAW;AAAA,MAChE,WAAW,QAAQ,UAAU;AAC3B,cAAM,QAAQ,QAAQ,WAAW,MAAM;AACvC,YAAI,QAAQ,GAAG;AACb,gBAAM,MAAM,KAAK;AAAA,QACnB;AAAA,MACF,WAAW,QAAQ,WAAW;AAC5B,cAAM,QAAQ,QAAQ,YAAY,MAAM;AACxC,YAAI,QAAQ,GAAG;AACb,gBAAM,MAAM,KAAK;AAAA,QACnB;AAAA,MACF;AAEA,YAAM,KAAK,iBAAiB,OAAO,QAAQ,QAAQ,OAAO;AAAA,IAC5D,SAAS,OAAO;AACd,UACE,iBAAiB,mBACjB,iBAAiB,oBACjB;AACA,cAAM;AAAA,MACR;AACA,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,OACA,QACA,UAA0B,CAAA,GACX;AACf,UAAM,OAAO,MAAM,KAAK,QAAA;AACxB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AACnC,YAAM,KAAK,iBAAiB,OAAO,QAAQ,QAAQ,OAAO;AAAA,IAC5D,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAA2C;AAC3D,UAAM,OAAO,MAAM,KAAK,QAAA;AACxB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AACnC,YAAM,OAAO,KAAK,iBAAiB,KAAK;AACxC,YAAM,SAAS,KAAK,eAAe,IAAI;AAEvC,aAAO;AAAA,QACL,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM;AAAA,QACd;AAAA,QACA,UAAU;AAAA;AAAA,QACV,UAAU;AAAA;AAAA;AAAA;AAAA,MAAA;AAAA,IAId,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,KACJ,OACA,YAA2B,cACV;AACjB,UAAM,OAAO,MAAM,KAAK,QAAA;AAExB,QAAI;AACF,UAAI,cAAc,SAAS,cAAc,UAAU;AACjD,cAAM,EAAE,WAAA,IAAe,MAAM,OAAO,aAAa;AACjD,cAAMA,SAAQ,MAAM,KAAK,KAAK,KAAK;AACnC,cAAM,SAAS,MAAMA,OAAM,UAAU,WAAW;AAChD,eAAO,WAAW,SAAS,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAAA,MAC1D;AAGA,YAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AAGnC,YAAM,UAAU,MAAM,MAAA,EAAQ,OAAO,EAAE,GAAG,GAAG,GAAG,EAAA,CAAG,EAAE,UAAA;AAGrD,YAAM,OAAO,QAAQ,OAAO;AAC5B,UAAI,MAAM;AACV,YAAM,SAAmB,CAAA;AAGzB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,cAAM,OAAO,KAAK,CAAC;AACnB,eAAO,KAAK,IAAI;AAChB,eAAO;AAAA,MACT;AAEA,YAAM,MAAM,MAAM,OAAO;AACzB,UAAI,OAAO;AACX,iBAAW,OAAO,QAAQ;AACxB,gBAAQ,OAAO,MAAM,MAAM;AAAA,MAC7B;AACA,aAAO,OAAO,KAAK,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,SAAS,IAAI,GAAG;AAAA,IAC1D,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACA,QACA,SACe;AACf,UAAM,OAAO,MAAM,KAAK,QAAA;AACxB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AAEnC,YAAM,MAAM,QAAQ,OAAO;AAE3B,UAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,YAAI,QAAQ,SAAS;AACnB,gBAAM,MAAM,EAAE,GAAG,QAAQ,OAAO,GAAG,QAAQ,QAAQ;AAAA,QACrD,WAAW,QAAQ,aAAa,QAAQ,UAAU;AAChD,gBAAM,QAAQ,EAAE,GAAG,QAAQ,OAAO,GAAG,QAAQ,QAAQ;AAAA,QACvD,OAAO;AAEL,gBAAM,OAAO,EAAE,GAAG,QAAQ,OAAO,GAAG,QAAQ,QAAQ;AAAA,QACtD;AAAA,MACF,WAAW,QAAQ,OAAO;AACxB,cAAM,QAAQ,QAAQ,QAAQ,MAAM;AACpC,cAAM,MAAM,KAAK;AAAA,MACnB,WAAW,QAAQ,QAAQ;AACzB,cAAM,QAAQ,QAAQ,SAAS,MAAM;AACrC,cAAM,MAAM,KAAK;AAAA,MACnB;AAEA,YAAM,KAAK,iBAAiB,OAAO,QAAQ,QAAQ,OAAO;AAAA,IAC5D,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBACZ,OACA,QACA,SACe;AACf,UAAM,SAAS,KAAK,YAAY,MAAM;AACtC,UAAM,OAAO,KAAK,eAAe,MAAM;AAEvC,QAAI,WAAW,QAAQ;AACrB,YAAM,IAAI,uBAAuB,QAAQ,MAAM;AAAA,IACjD;AAEA,UAAM,SAAS,MAAM,MAAM,UAAU,MAAM;AAAA,MACzC,SAAS,WAAW;AAAA,IAAA,CACrB;AAED,UAAM,EAAE,UAAA,IAAc,MAAM,OAAO,kBAAkB;AACrD,UAAM,UAAU,QAAQ,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,MAA2B;AAC7C,UAAM,MAAM,KAAK,MAAM,GAAG,EAAE,IAAA,GAAO,YAAA;AACnC,UAAM,MAAmC;AAAA,MACvC,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA;AAAA,IAAA;AAEP,WAAO,IAAI,OAAO,EAAE,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAA6B;AAClD,UAAM,MAAmC;AAAA,MACvC,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,IAAA;AAER,WAAO,IAAI,MAAM,KAAK;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,MAAsB;AAC3C,UAAM,MAA8B;AAAA,MAClC,cAAc;AAAA,MACd,aAAa;AAAA,MACb,cAAc;AAAA,MACd,aAAa;AAAA,MACb,cAAc;AAAA,MACd,aAAa;AAAA,IAAA;AAEf,WAAO,IAAI,IAAI,KAAK;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,OAA2B;AAClD,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,SAAS,KAAK,YAAY,KAAK;AACrC,aAAO,KAAK,eAAe,MAAM;AAAA,IACnC;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,OAAgB,OAAoC;AACnE,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAGrE,QACE,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,QAAQ,KACzB,QAAQ,SAAS,gBAAgB,GACjC;AACA,YAAM,OAAO,OAAO,UAAU,WAAW,QAAQ;AACjD,YAAM,IAAI,mBAAmB,MAAM,MAAM;AAAA,IAC3C;AAEA,QACE,QAAQ,SAAS,eAAe,KAChC,QAAQ,SAAS,qBAAqB,GACtC;AACA,YAAM,IAAI,uBAAuB,WAAW,MAAM;AAAA,IACpD;AAEA,WAAO,IAAI;AAAA,MACT,4BAA4B,OAAO;AAAA,MACnC;AAAA,MACA,iBAAiB,QAAQ,QAAQ;AAAA,IAAA;AAAA,EAErC;AACF;;;;;ACtVO,MAAM,aAAgD;AAAA,EACnD,QAAuC;AAAA;AAAA,EAEvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,UAAwB,IAAI;AACtC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,WAA4C;AACxD,QAAI,CAAC,KAAK,OAAO;AACf,UAAI;AACF,aAAK,SAAS,MAAM,OAAO,OAAO,GAAG;AAAA,MACvC,SAAS,OAAO;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA,iBAAiB,QAAQ,QAAQ;AAAA,QAAA;AAAA,MAErC;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,cAAc,OAA6C;AAC/D,UAAMC,SAAQ,MAAM,KAAK,SAAA;AACzB,QAAI;AACF,YAAM,WAAW,MAAMA,OAAM,KAAK,EAAE,SAAA;AACpC,UAAI,CAAC,SAAS,SAAS,CAAC,SAAS,QAAQ;AACvC,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AACA,aAAO,EAAE,OAAO,SAAS,OAAO,QAAQ,SAAS,OAAA;AAAA,IACnD,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAiB,OAAM;AAC5C,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,UACJ,OACA,QACA,UAA4B,CAAA,GACb;AACf,UAAMA,SAAQ,MAAM,KAAK,SAAA;AACzB,QAAI;AACF,UAAI,WAAWA,OAAM,KAAK,EAAE,OAAO;AAAA,QACjC,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,QAChB,KAAK,QAAQ,OAAO;AAAA,QACpB,oBAAoB;AAAA,MAAA,CACrB;AAED,UAAI,QAAQ,QAAQ;AAClB,mBAAW,SAAS,SAAS,QAAQ,QAAQ;AAAA,UAC3C,SAAS,QAAQ;AAAA,QAAA,CAClB;AAAA,MACH,WAAW,QAAQ,SAAS;AAE1B,cAAM,SAAS,KAAK,YAAY,MAAM;AACtC,mBAAW,SAAS,SAAS,QAAQ,EAAE,SAAS,QAAQ,SAAS;AAAA,MACnE;AAEA,YAAM,SAAS,OAAO,MAAM;AAAA,IAC9B,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,OACA,QACA,UAA0B,CAAA,GACX;AACf,UAAMA,SAAQ,MAAM,KAAK,SAAA;AACzB,QAAI;AACF,YAAM,SAAS,QAAQ,UAAU,KAAK,YAAY,MAAM;AACxD,YAAMA,OAAM,KAAK,EACd,SAAS,QAAQ,EAAE,SAAS,QAAQ,QAAA,CAAS,EAC7C,OAAO,MAAM;AAAA,IAClB,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAA2C;AAC3D,UAAMA,SAAQ,MAAM,KAAK,SAAA;AACzB,QAAI;AACF,YAAM,OAAO,MAAMA,OAAM,KAAK,EAAE,SAAA;AAChC,UAAI,CAAC,KAAK,SAAS,CAAC,KAAK,UAAU,CAAC,KAAK,QAAQ;AAC/C,cAAM,IAAI,gBAAgB,oCAAoC,OAAO;AAAA,MACvE;AACA,aAAO;AAAA,QACL,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,QAAQ,KAAK;AAAA,QACb,OAAO,KAAK;AAAA,QACZ,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ,SAAS,KAAK;AAAA,QACd,UAAU,KAAK;AAAA,QACf,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK,OAAO,KAAK,gBAAgB,KAAK,IAAI,IAAI;AAAA,QACpD,MAAM,KAAK,OAAO,KAAK,gBAAgB,KAAK,IAAI,IAAI;AAAA,QACpD,KAAK,KAAK,MAAM,KAAK,eAAe,KAAK,GAAG,IAAI;AAAA,MAAA;AAAA,IAEpD,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAiB,OAAM;AAC5C,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,KACJ,OACA,YAA2B,cACV;AACjB,UAAMA,SAAQ,MAAM,KAAK,SAAA;AAEzB,QAAI;AACF,UAAI,cAAc,SAAS,cAAc,UAAU;AACjD,cAAM,EAAE,WAAA,IAAe,MAAM,OAAO,aAAa;AACjD,cAAM,SAAS,MAAMA,OAAM,KAAK,EAAE,SAAA;AAClC,eAAO,WAAW,SAAS,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAAA,MAC1D;AAGA,YAAM,EAAE,SAAS,MAAMA,OAAM,KAAK,EAC/B,OAAO,GAAG,GAAG,EAAE,KAAK,OAAA,CAAQ,EAC5B,YACA,IAAA,EACA,SAAS,EAAE,mBAAmB,MAAM;AAEvC,YAAM,MAAM,KAAK,OAAO,CAAC,KAAK,QAAQ,MAAM,KAAK,CAAC,IAAI,KAAK;AAC3D,UAAI,OAAO;AACX,iBAAW,OAAO,MAAM;AACtB,gBAAQ,OAAO,MAAM,MAAM;AAAA,MAC7B;AAEA,aAAO,OAAO,KAAK,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,SAAS,IAAI,GAAG;AAAA,IAC1D,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACA,QACA,SACe;AACf,UAAMA,SAAQ,MAAM,KAAK,SAAA;AACzB,QAAI;AACF,UAAI,WAAWA,OAAM,KAAK,EAAE,OAAO;AAAA,QACjC,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,QAChB,KAAK,QAAQ,OAAO;AAAA,MAAA,CACrB;AAED,UAAI,QAAQ,QAAQ;AAClB,mBAAW,SAAS,SAAS,QAAQ,QAAQ;AAAA,UAC3C,SAAS,QAAQ;AAAA,QAAA,CAClB;AAAA,MACH,WAAW,QAAQ,SAAS;AAC1B,cAAM,SAAS,KAAK,YAAY,MAAM;AACtC,mBAAW,SAAS,SAAS,QAAQ,EAAE,SAAS,QAAQ,SAAS;AAAA,MACnE;AAEA,YAAM,SAAS,OAAO,MAAM;AAAA,IAC9B,SAAS,OAAO;AACd,YAAM,KAAK,SAAS,OAAO,KAAK;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,MAA2B;AAC7C,UAAM,MAAM,KAAK,MAAM,GAAG,EAAE,IAAA,GAAO,YAAA;AACnC,UAAM,MAAmC;AAAA,MACvC,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,IAAA;AAEP,WAAO,IAAI,OAAO,EAAE,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,QAAyC;AAE/D,WAAO,EAAE,KAAK,GAAG,OAAO,SAAS,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAA;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,QAAyC;AAC/D,WAAO,EAAE,KAAK,GAAG,OAAO,SAAS,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAA;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAyC;AAE9D,QAAI;AACF,YAAM,YAAY,OAAO,SAAS,MAAM;AACxC,aAAO,EAAE,KAAK,GAAG,UAAU,MAAM,GAAG,GAAG,CAAC,MAAA;AAAA,IAC1C,QAAQ;AACN,aAAO,EAAE,KAAK,GAAG,OAAO,SAAS,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAA;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,OAAgB,OAAoC;AACnE,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAGrE,QAAI,QAAQ,SAAS,uBAAuB,GAAG;AAC7C,YAAM,OAAO,OAAO,UAAU,WAAW,QAAQ;AACjD,YAAM,IAAI,mBAAmB,MAAM,OAAO;AAAA,IAC5C;AAEA,QAAI,QAAQ,SAAS,gDAAgD,GAAG;AACtE,YAAM,IAAI,gBAAgB,4BAA4B,OAAO;AAAA,IAC/D;AAEA,WAAO,IAAI;AAAA,MACT,4BAA4B,OAAO;AAAA,MACnC;AAAA,MACA,iBAAiB,QAAQ,QAAQ;AAAA,IAAA;AAAA,EAErC;AACF;;;;;ACjJA,IAAI,iBAAqC;AACzC,IAAI,iBAAgC;AAQpC,eAAe,eAAe,UAAwC;AAEpE,MAAI,kBAAkB,mBAAmB,UAAU;AACjD,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,4CAA4C,mBAAmB,QAAQ,CAAC;AAExF,QAAM,cAAc,MAAM,MAAM,SAAS;AAAA,IACvC,SAAS;AAAA;AAAA,MAEP,cACE;AAAA,IAAA;AAAA,EACJ,CACD;AAED,MAAI,CAAC,YAAY,IAAI;AACnB,UAAM,IAAI,MAAM,6BAA6B,YAAY,UAAU,EAAE;AAAA,EACvE;AAEA,QAAM,MAAM,MAAM,YAAY,KAAA;AAK9B,MAAI,WAAW,IAAI;AAAA,IACjB;AAAA,EAAA;AAEF,MAAI,CAAC,UAAU;AACb,eAAW,IAAI,MAAM,gDAAgD;AAAA,EACvE;AACA,MAAI,CAAC,UAAU;AAEb,eAAW,IAAI,MAAM,qDAAqD;AAAA,EAC5E;AACA,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR,iDAAiD,QAAQ,UAAU,IAAI,MAAM,GAAG,GAAG,CAAC;AAAA,IAAA;AAAA,EAExF;AAEA,QAAM,cAAc,SAAS,CAAC;AAC9B,QAAM,eAAe,MAAM,MAAM,WAAW;AAE5C,MAAI,CAAC,aAAa,IAAI;AACpB,UAAM,IAAI,MAAM,8BAA8B,aAAa,UAAU,EAAE;AAAA,EACzE;AAEA,mBAAiB,MAAM,aAAa,YAAA;AACpC,mBAAiB;AAEjB,SAAO;AACT;AAyBA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM;AAAA,IACJ;AAAA,IAEA;AAAA,IACA;AAAA,IAEA;AAAA,IACA;AAAA,EAAA,IACE;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,OAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR;AAAA,QACA,SAAS;AAAA,QACT;AAAA,MAAA;AAAA,MAEF,UAAU;AAAA;AAAA,QAER;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,UAAU;AAAA,cACV,KAAK;AAAA,cACL,MAAM;AAAA,cACN,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,iBAAiB;AAAA,YAAA;AAAA,UACnB;AAAA,QACF;AAAA;AAAA,QAGF,WACI;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,OAAO;AAAA,cACP,cAAc;AAAA,cACd,eAAe;AAAA,cACf,eAAe;AAAA,YAAA;AAAA,YAEjB,UAAU;AAAA,UAAA;AAAA,QACZ,IAEF;AAAA;AAAA,QAEJ;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM,SAAS,MAAM,SAAS,IAAI,CAAC;AAAA,cAC/D,YAAY;AAAA,cACZ,OAAO;AAAA,cACP,YAAY;AAAA,cACZ,UAAU;AAAA,YAAA;AAAA,YAEZ,UAAU;AAAA,UAAA;AAAA,QACZ;AAAA;AAAA,QAGF;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,UAAU;AAAA,cACV,QAAQ;AAAA,cACR,MAAM;AAAA,cACN,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,iBAAiB;AAAA,YAAA;AAAA,UACnB;AAAA,QACF;AAAA,MACF,EACA,OAAO,OAAO;AAAA,IAAA;AAAA,EAClB;AAEJ;AAKA,SAAS,mBAAmB,EAAE,OAAO,WAAyC;AAC5E,QAAM;AAAA,IACJ;AAAA,IAEA;AAAA,IACA;AAAA,IAEA;AAAA,IACA;AAAA,EAAA,IACE;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,OAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,OAAO;AAAA,QACP,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,UAAU;AAAA;AAAA,QAER;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,iBAAiB;AAAA,YAAA;AAAA,UACnB;AAAA,QACF;AAAA;AAAA,QAGF;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,SAAS;AAAA,cACT,eAAe;AAAA,cACf,gBAAgB;AAAA,cAChB,MAAM;AAAA,cACN,SAAS;AAAA,YAAA;AAAA,YAEX,UAAU;AAAA;AAAA,cAER,WACI;AAAA,gBACE,MAAM;AAAA,gBACN,OAAO;AAAA,kBACL,OAAO;AAAA,oBACL,SAAS;AAAA,oBACT,WAAW;AAAA,oBACX,iBAAiB;AAAA,oBACjB,OAAO;AAAA,oBACP,UAAU;AAAA,oBACV,YAAY;AAAA,oBACZ,SAAS;AAAA,oBACT,cAAc;AAAA,oBACd,cAAc;AAAA,oBACd,eAAe;AAAA,oBACf,eAAe;AAAA,kBAAA;AAAA,kBAEjB,UAAU;AAAA,gBAAA;AAAA,cACZ,IAEF;AAAA;AAAA,cAEJ;AAAA,gBACE,MAAM;AAAA,gBACN,OAAO;AAAA,kBACL,OAAO;AAAA,oBACL,UAAU,KAAK;AAAA,sBACb;AAAA,sBACA,KAAK,OAAO,QAAQ,QAAQ,MAAM,SAAS,IAAI;AAAA,oBAAA;AAAA,oBAEjD,YAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,YAAY;AAAA,kBAAA;AAAA,kBAEd,UAAU;AAAA,gBAAA;AAAA,cACZ;AAAA,YACF,EACA,OAAO,OAAO;AAAA,UAAA;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEJ;AAKA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM,EAAE,OAAe,YAAY,iBAA4B,eAC7D;AAEF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,OAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR;AAAA,QACA,SAAS;AAAA,QACT;AAAA,MAAA;AAAA,MAEF,UAAU;AAAA;AAAA,QAER;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,iBAAiB;AAAA,cACjB,cAAc;AAAA,YAAA;AAAA,UAChB;AAAA,QACF;AAAA;AAAA,QAGF;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM,SAAS,MAAM,SAAS,KAAK,CAAC;AAAA,cAChE,YAAY;AAAA,cACZ,OAAO;AAAA,cACP,YAAY;AAAA,cACZ,WAAW;AAAA,cACX,UAAU;AAAA,YAAA;AAAA,YAEZ,UAAU;AAAA,UAAA;AAAA,QACZ;AAAA;AAAA,QAGF;AAAA,UACE,MAAM;AAAA,UACN,OAAO;AAAA,YACL,OAAO;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,iBAAiB;AAAA,cACjB,WAAW;AAAA,YAAA;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEJ;AA4CA,eAAsB,qBACpB,OACA,UAA+B,IACF;AAE7B,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,kBAAkB,QAAQ,mBAAmB;AACjC,UAAQ,aAAa;AACvC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,aAAa,QAAQ,cAAc;AAEzC,QAAM,kBAAkB;AAAA,IACtB,GAAG;AAAA,IACH;AAAA,IAEA;AAAA,IACA;AAAA,IAEA;AAAA,EAAA;AAIF,QAAM,CAAC,EAAE,SAAS,OAAA,GAAU,EAAE,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzD,OAAO,QAAQ;AAAA,IACf,OAAO,iBAAiB;AAAA,EAAA,CACzB;AAGD,QAAM,WAAW,QAAQ,YAAa,MAAM,eAAe,UAAU;AAGrE,MAAI;AACJ,UAAQ,UAAA;AAAA,IACN,KAAK;AACH,gBAAU,mBAAmB,EAAE,OAAO,SAAS,iBAAiB;AAChE;AAAA,IACF,KAAK;AACH,gBAAU,sBAAsB,EAAE,OAAO,SAAS,iBAAiB;AACnE;AAAA,IACF;AACE,gBAAU,sBAAsB,EAAE,OAAO,SAAS,iBAAiB;AAAA,EAAA;AAKvE,QAAM,MAAM,MAAM,OAAO,SAAgB;AAAA,IACvC;AAAA,IACA;AAAA,IACA,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,OAAO;AAAA,MAAA;AAAA,MAET;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,OAAO;AAAA,MAAA;AAAA,MAET;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,EACF,CACD;AAGD,QAAM,QAAQ,IAAI,MAAM,KAAK;AAAA,IAC3B,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,IAAA;AAAA,EACT,CACD;AAED,QAAM,UAAU,MAAM,OAAA;AACtB,QAAM,SAAS,OAAO,KAAK,QAAQ,OAAO;AAE1C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,EAAA;AAEd;AAKO,SAAS,iBAAuB;AACrC,mBAAiB;AACjB,mBAAiB;AACnB;ACxlBA,SAAS,eACP,SACyB;AACzB,SAAO,CAAC,QAAQ,QAAQ,QAAQ,SAAS;AAC3C;AAKA,SAAS,cACP,SACwB;AACxB,SAAO,QAAQ,SAAS;AAC1B;AAKA,SAAS,kBACP,SAC4B;AAC5B,SAAO,QAAQ,SAAS;AAC1B;AAMA,SAAS,cACP,SAC0B;AAE1B,MAAI,OAAO,YAAY,eAAe,CAAC,QAAQ,KAAK;AAClD,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,QAAQ;AAGpB,QAAM,cAAuC,CAAA;AAG7C,QAAM,UAAU,IAAI,oBAAoB,IAAI;AAC5C,MAAI,WAAW,CAAC,QAAQ,MAAM;AAC5B,gBAAY,OAAO;AAAA,EACrB;AAGA,MAAI,IAAI,sBAAsB;AAC5B,gBAAY,UAAU,IAAI;AAAA,EAC5B;AACA,MAAI,IAAI,iBAAiB;AACvB,gBAAY,MAAM,IAAI;AAAA,EACxB;AACA,MAAI,IAAI,kBAAkB;AACxB,gBAAY,OAAO,IAAI;AAAA,EACzB;AAGA,SAAO,EAAE,GAAG,aAAa,GAAG,QAAA;AAC9B;AAqCA,eAAsB,kBACpB,UAAoC,IACF;AAElC,YAAU,cAAc,OAAO;AAE/B,MAAI,eAAe,OAAO,GAAG;AAC3B,UAAM,EAAE,cAAAC,cAAA,IAAiB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,KAAA;AAC/B,WAAO,IAAIA,cAAa,OAAO;AAAA,EACjC;AAEA,MAAI,cAAc,OAAO,GAAG;AAC1B,UAAM,EAAE,aAAAC,aAAA,IAAgB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,IAAA;AAC9B,WAAO,IAAIA,aAAY,OAAO;AAAA,EAChC;AAEA,MAAI,kBAAkB,OAAO,GAAG;AAC9B,UAAM,EAAE,iBAAAC,iBAAA,IAAoB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,QAAA;AAClC,WAAO,IAAIA,iBAAgB,OAAO;AAAA,EACpC;AAEA,QAAM,IAAI;AAAA,IACP,QAA8B,QAAQ;AAAA,EAAA;AAE3C;AAKO,SAAS,uBAAiC;AAC/C,SAAO,CAAC,SAAS,QAAQ,UAAU;AACrC;;;;;;ACjEA,IAAI,kBAAkD;AACtD,IAAI,qBAAqB;AAYzB,eAAe,oBACb,gBACkC;AAElC,MAAI,gBAAgB,MAAM;AACxB,UAAM,EAAE,mBAAAC,mBAAAA,IAAsB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,OAAA;AACpC,WAAOA,mBAAkB,cAAc;AAAA,EACzC;AAGA,MAAI,mBAAmB,CAAC,gBAAgB;AACtC,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,oBAAoB;AACvB,yBAAqB;AAErB,QAAI;AAEF,YAAM,OAAO,OAAO;AACpB,YAAM,EAAE,cAAAH,cAAAA,IAAiB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,KAAA;AAC/B,wBAAkB,IAAIA,cAAa,EAAE;AAAA,IACvC,QAAQ;AAEN,YAAM,EAAE,aAAAC,aAAAA,IAAgB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,IAAA;AAC9B,wBAAkB,IAAIA,aAAY,EAAE,MAAM,QAAQ;AAAA,IACpD;AAAA,EACF;AAEA,MAAI,CAAC,iBAAiB;AACpB,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,SAAO;AACT;AAKO,SAAS,iBAAuB;AACrC,oBAAkB;AAClB,uBAAqB;AACvB;AA2BA,eAAsB,cACpB,OACA,gBAC0B;AAC1B,QAAM,YAAY,MAAM,oBAAoB,cAAc;AAC1D,SAAO,UAAU,cAAc,KAAK;AACtC;AA+BA,eAAsB,kBACpB,OACA,QACA,SACA,gBACe;AACf,QAAM,YAAY,MAAM,oBAAoB,cAAc;AAC1D,SAAO,UAAU,UAAU,OAAO,QAAQ,OAAO;AACnD;AA6BA,eAAsB,cACpB,OACA,QACA,SACA,gBACe;AACf,QAAM,YAAY,MAAM,oBAAoB,cAAc;AAC1D,SAAO,UAAU,QAAQ,OAAO,QAAQ,OAAO;AACjD;AA0BA,eAAsB,iBACpB,OACA,gBACwB;AACxB,QAAM,YAAY,MAAM,oBAAoB,cAAc;AAC1D,SAAO,UAAU,YAAY,KAAK;AACpC;AA0BA,eAAsB,aACpB,OACA,YAA2B,cAC3B,gBACiB;AACjB,QAAM,YAAY,MAAM,oBAAoB,cAAc;AAC1D,SAAO,UAAU,KAAK,OAAO,SAAS;AACxC;AAgCA,eAAsB,YACpB,OACA,QACA,SACA,gBACe;AACf,QAAM,YAAY,MAAM,oBAAoB,cAAc;AAC1D,SAAO,UAAU,OAAO,OAAO,QAAQ,OAAO;AAChD;"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Error classes for the images library
3
+ */
4
+ /**
5
+ * Base error class for image processing operations
6
+ */
7
+ export declare class ImageError extends Error {
8
+ code: string;
9
+ adapter?: string | undefined;
10
+ constructor(message: string, code: string, adapter?: string | undefined);
11
+ }
12
+ /**
13
+ * Error thrown when an image file is not found
14
+ */
15
+ export declare class ImageNotFoundError extends ImageError {
16
+ constructor(path: string, adapter?: string);
17
+ }
18
+ /**
19
+ * Error thrown when image format is not supported
20
+ */
21
+ export declare class UnsupportedFormatError extends ImageError {
22
+ constructor(format: string, adapter?: string);
23
+ }
24
+ /**
25
+ * Error thrown when an operation is not supported by the adapter
26
+ */
27
+ export declare class OperationNotSupportedError extends ImageError {
28
+ constructor(operation: string, adapter?: string);
29
+ }
30
+ /**
31
+ * Error thrown when image processing fails
32
+ */
33
+ export declare class ProcessingError extends ImageError {
34
+ cause?: Error | undefined;
35
+ constructor(message: string, adapter?: string, cause?: Error | undefined);
36
+ }
37
+ /**
38
+ * Error thrown when adapter type is invalid
39
+ */
40
+ export declare class InvalidAdapterError extends ImageError {
41
+ constructor(type: string);
42
+ }
43
+ /**
44
+ * Error thrown when remote service (e.g., imgproxy) fails
45
+ */
46
+ export declare class RemoteServiceError extends ImageError {
47
+ statusCode?: number | undefined;
48
+ constructor(message: string, adapter?: string, statusCode?: number | undefined);
49
+ }
50
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAG1B,IAAI,EAAE,MAAM;IACZ,OAAO,CAAC,EAAE,MAAM;gBAFvB,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,YAAA;CAK1B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;gBACpC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI3C;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,UAAU;gBACxC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI7C;AAED;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,UAAU;gBAC5C,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAQhD;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAIpC,KAAK,CAAC,EAAE,KAAK;gBAFpB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EACT,KAAK,CAAC,EAAE,KAAK,YAAA;CAKvB;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,UAAU;gBACrC,IAAI,EAAE,MAAM;CAIzB;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAIvC,UAAU,CAAC,EAAE,MAAM;gBAF1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EACT,UAAU,CAAC,EAAE,MAAM,YAAA;CAK7B"}
@@ -0,0 +1,42 @@
1
+ import { GetImageProcessorOptions, ImageProcessorInterface } from './types.js';
2
+ /**
3
+ * Creates an image processor instance based on the provided options.
4
+ *
5
+ * Supports environment variable configuration using the pattern:
6
+ * - HAVE_IMAGES_TYPE → adapter type ('sharp', 'jimp', 'imgproxy')
7
+ * - HAVE_IMAGES_BASE_URL → imgproxy base URL
8
+ * - HAVE_IMAGES_KEY → imgproxy signing key (hex)
9
+ * - HAVE_IMAGES_SALT → imgproxy signing salt (hex)
10
+ *
11
+ * User-provided options always take precedence over environment variables.
12
+ *
13
+ * @param options - Configuration options for the image processor
14
+ * @returns Promise resolving to an image processor instance
15
+ * @throws {InvalidAdapterError} When the adapter type is unsupported
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // Create Sharp processor (default)
20
+ * const processor = await getImageProcessor();
21
+ *
22
+ * // Create Jimp processor
23
+ * const jimpProcessor = await getImageProcessor({ type: 'jimp' });
24
+ *
25
+ * // Create imgproxy processor
26
+ * const imgproxyProcessor = await getImageProcessor({
27
+ * type: 'imgproxy',
28
+ * baseUrl: 'https://imgproxy.example.com',
29
+ * key: 'abcd1234...',
30
+ * salt: 'efgh5678...'
31
+ * });
32
+ *
33
+ * // Use environment variables (HAVE_IMAGES_TYPE=sharp)
34
+ * const autoProcessor = await getImageProcessor({});
35
+ * ```
36
+ */
37
+ export declare function getImageProcessor(options?: GetImageProcessorOptions): Promise<ImageProcessorInterface>;
38
+ /**
39
+ * Get a list of available adapter types
40
+ */
41
+ export declare function getAvailableAdapters(): string[];
42
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/shared/factory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EAIxB,MAAM,YAAY,CAAC;AAuEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CAsBlC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAE/C"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Core types and interfaces for the images library
3
+ */
4
+ /**
5
+ * Input type for image processing
6
+ * Can be a file path string or a Buffer containing image data
7
+ */
8
+ export type ImageInput = string | Buffer;
9
+ /**
10
+ * Image dimensions
11
+ */
12
+ export interface ImageDimensions {
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * Supported image formats
18
+ */
19
+ export type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif' | 'tiff';
20
+ /**
21
+ * Hash algorithm options
22
+ */
23
+ export type HashAlgorithm = 'perceptual' | 'md5' | 'sha256';
24
+ /**
25
+ * Fit modes for resize operations
26
+ */
27
+ export type FitMode = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
28
+ /**
29
+ * Options for thumbnail generation
30
+ */
31
+ export interface ThumbnailOptions {
32
+ /** Maximum width of the thumbnail */
33
+ maxWidth?: number;
34
+ /** Maximum height of the thumbnail */
35
+ maxHeight?: number;
36
+ /** Quality for lossy formats (1-100) */
37
+ quality?: number;
38
+ /** Output format */
39
+ format?: ImageFormat;
40
+ /** How to fit the image within dimensions */
41
+ fit?: FitMode;
42
+ }
43
+ /**
44
+ * Options for format conversion
45
+ */
46
+ export interface ConvertOptions {
47
+ /** Target format */
48
+ format?: ImageFormat;
49
+ /** Quality for lossy formats (1-100) */
50
+ quality?: number;
51
+ }
52
+ /**
53
+ * Options for resize operations
54
+ */
55
+ export interface ResizeOptions {
56
+ /** Target width */
57
+ width?: number;
58
+ /** Target height */
59
+ height?: number;
60
+ /** How to fit the image within dimensions */
61
+ fit?: FitMode;
62
+ /** Quality for lossy formats (1-100) */
63
+ quality?: number;
64
+ /** Output format */
65
+ format?: ImageFormat;
66
+ }
67
+ /**
68
+ * Image metadata structure
69
+ */
70
+ export interface ImageMetadata {
71
+ /** Image width in pixels */
72
+ width: number;
73
+ /** Image height in pixels */
74
+ height: number;
75
+ /** Image format (jpeg, png, webp, etc.) */
76
+ format: string;
77
+ /** Color space (srgb, rgb, etc.) */
78
+ space?: string;
79
+ /** Number of channels (3 for RGB, 4 for RGBA) */
80
+ channels?: number;
81
+ /** Bit depth per channel */
82
+ depth?: string;
83
+ /** Image density (DPI) */
84
+ density?: number;
85
+ /** Whether the image has an alpha channel */
86
+ hasAlpha?: boolean;
87
+ /** EXIF orientation value (1-8) */
88
+ orientation?: number;
89
+ /** EXIF metadata */
90
+ exif?: Record<string, unknown>;
91
+ /** IPTC metadata */
92
+ iptc?: Record<string, unknown>;
93
+ /** XMP metadata */
94
+ xmp?: Record<string, unknown>;
95
+ }
96
+ /**
97
+ * Core image processor interface
98
+ * All adapters must implement this interface
99
+ */
100
+ export interface ImageProcessorInterface {
101
+ /**
102
+ * Get image dimensions
103
+ * @param input - Image path or Buffer
104
+ * @returns Promise resolving to image dimensions
105
+ */
106
+ getDimensions(input: ImageInput): Promise<ImageDimensions>;
107
+ /**
108
+ * Generate a thumbnail
109
+ * @param input - Source image path or Buffer
110
+ * @param output - Output file path
111
+ * @param options - Thumbnail generation options
112
+ */
113
+ thumbnail(input: ImageInput, output: string, options?: ThumbnailOptions): Promise<void>;
114
+ /**
115
+ * Convert image format
116
+ * @param input - Source image path or Buffer
117
+ * @param output - Output file path
118
+ * @param options - Conversion options
119
+ */
120
+ convert(input: ImageInput, output: string, options?: ConvertOptions): Promise<void>;
121
+ /**
122
+ * Get image metadata
123
+ * @param input - Image path or Buffer
124
+ * @returns Promise resolving to metadata
125
+ */
126
+ getMetadata(input: ImageInput): Promise<ImageMetadata>;
127
+ /**
128
+ * Compute image hash
129
+ * @param input - Image path or Buffer
130
+ * @param algorithm - Hash algorithm to use
131
+ * @returns Promise resolving to hash string
132
+ */
133
+ hash(input: ImageInput, algorithm?: HashAlgorithm): Promise<string>;
134
+ /**
135
+ * Resize image with full control
136
+ * @param input - Source image path or Buffer
137
+ * @param output - Output file path
138
+ * @param options - Resize options
139
+ */
140
+ resize(input: ImageInput, output: string, options: ResizeOptions): Promise<void>;
141
+ }
142
+ /**
143
+ * Sharp adapter options
144
+ */
145
+ export interface SharpOptions {
146
+ type?: 'sharp';
147
+ }
148
+ /**
149
+ * Jimp adapter options
150
+ */
151
+ export interface JimpOptions {
152
+ type: 'jimp';
153
+ }
154
+ /**
155
+ * imgproxy adapter options
156
+ */
157
+ export interface ImgproxyOptions {
158
+ type: 'imgproxy';
159
+ /** Base URL of the imgproxy server */
160
+ baseUrl: string;
161
+ /** Signing key (hex encoded) */
162
+ key?: string;
163
+ /** Signing salt (hex encoded) */
164
+ salt?: string;
165
+ }
166
+ /**
167
+ * Union type for all image processor options
168
+ */
169
+ export type GetImageProcessorOptions = SharpOptions | JimpOptions | ImgproxyOptions;
170
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"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"}
package/metadata.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@happyvertical/images",
3
+ "path": "packages/images",
4
+ "position": {
5
+ "index": 15,
6
+ "count": 30
7
+ },
8
+ "description": "Image processing utilities with adapter pattern for scaling from static to enterprise",
9
+ "provides": [
10
+ "Image processing utilities with adapter pattern for scaling from static to enterprise"
11
+ ],
12
+ "implements": [],
13
+ "requires": {
14
+ "workspace": [],
15
+ "externalHappyVertical": [],
16
+ "external": [
17
+ "@resvg/resvg-js",
18
+ "jimp",
19
+ "satori",
20
+ "sharp"
21
+ ]
22
+ },
23
+ "dependents": [
24
+ "@happyvertical/video"
25
+ ],
26
+ "stability": {
27
+ "level": "stable",
28
+ "reason": "Primary package surface is described as implemented and production-oriented."
29
+ },
30
+ "keywords": [
31
+ "images"
32
+ ]
33
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@happyvertical/images",
3
+ "version": "0.74.8",
4
+ "description": "Image processing utilities with adapter pattern for scaling from static to enterprise",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@resvg/resvg-js": "^2.6.2",
16
+ "satori": "^0.26.0"
17
+ },
18
+ "peerDependencies": {
19
+ "jimp": ">=1.6.1",
20
+ "sharp": ">=0.34.5"
21
+ },
22
+ "peerDependenciesMeta": {
23
+ "sharp": {
24
+ "optional": true
25
+ },
26
+ "jimp": {
27
+ "optional": true
28
+ }
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "25.0.10",
32
+ "jimp": "^1.6.1",
33
+ "sharp": "^0.34.5",
34
+ "typescript": "^5.9.3",
35
+ "vite": "7.3.2",
36
+ "vite-plugin-dts": "4.5.4",
37
+ "vitest": "^4.1.5"
38
+ },
39
+ "bin": {
40
+ "have-images-context": "./dist/cli/claude-context.js"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE",
46
+ "AGENT.md",
47
+ "metadata.json"
48
+ ],
49
+ "publishConfig": {
50
+ "registry": "https://registry.npmjs.org",
51
+ "access": "public"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/happyvertical/sdk.git",
56
+ "directory": "packages/images"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/happyvertical/sdk/issues"
60
+ },
61
+ "homepage": "https://github.com/happyvertical/sdk/tree/main/packages/images#readme",
62
+ "license": "MIT",
63
+ "scripts": {
64
+ "test": "npx vitest run",
65
+ "test:watch": "npx vitest",
66
+ "build": "vite build",
67
+ "build:watch": "vite build --watch",
68
+ "docs": "typedoc --plugin typedoc-plugin-markdown --out docs --entryPoints ./src/index.ts --tsconfig ./tsconfig.json --excludePrivate --excludeInternal --hideGenerator --fileExtension .md --readme none --categorizeByGroup false --includeVersion false --hidePageHeader --hidePageTitle false --outputFileStrategy modules",
69
+ "docs:watch": "typedoc --plugin typedoc-plugin-markdown --out docs --entryPoints ./src/index.ts --tsconfig ./tsconfig.json --excludePrivate --excludeInternal --hideGenerator --fileExtension .md --readme none --categorizeByGroup false --includeVersion false --hidePageHeader --hidePageTitle false --outputFileStrategy modules --watch",
70
+ "clean": "rm -rf dist docs",
71
+ "dev": "npm run build:watch & npm run test:watch"
72
+ }
73
+ }