@cloudwerk/images 0.1.3

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,"sources":["../src/errors.ts","../src/define-image.ts","../src/cloudflare-types.ts","../src/client.ts","../src/transformer.ts","../src/binding-types.ts"],"sourcesContent":["/**\n * @cloudwerk/images - Error Classes\n *\n * Custom error types for Cloudflare Images operations.\n */\n\n// ============================================================================\n// Base Error\n// ============================================================================\n\n/**\n * Base error class for all image-related errors.\n */\nexport class ImageError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ImageError'\n }\n}\n\n// ============================================================================\n// Configuration Errors\n// ============================================================================\n\n/**\n * Error thrown when image configuration is invalid.\n */\nexport class ImageConfigError extends ImageError {\n readonly field: string\n\n constructor(message: string, field: string) {\n super(`Image configuration error in '${field}': ${message}`)\n this.name = 'ImageConfigError'\n this.field = field\n }\n}\n\n/**\n * Error thrown when a required environment variable is missing.\n */\nexport class ImageEnvError extends ImageError {\n readonly envVar: string\n\n constructor(envVar: string) {\n super(\n `Missing required environment variable '${envVar}' for Cloudflare Images`\n )\n this.name = 'ImageEnvError'\n this.envVar = envVar\n }\n}\n\n// ============================================================================\n// Runtime Errors\n// ============================================================================\n\n/**\n * Error thrown when an image is not found.\n */\nexport class ImageNotFoundError extends ImageError {\n readonly imageId: string\n\n constructor(imageId: string) {\n super(`Image '${imageId}' not found`)\n this.name = 'ImageNotFoundError'\n this.imageId = imageId\n }\n}\n\n/**\n * Error thrown when an image upload fails.\n */\nexport class ImageUploadError extends ImageError {\n readonly statusCode?: number\n readonly details?: string\n\n constructor(message: string, statusCode?: number, details?: string) {\n super(message)\n this.name = 'ImageUploadError'\n this.statusCode = statusCode\n this.details = details\n }\n}\n\n/**\n * Error thrown when an image operation fails due to API errors.\n */\nexport class ImageApiError extends ImageError {\n readonly statusCode: number\n readonly errors: Array<{ code: number; message: string }>\n\n constructor(\n statusCode: number,\n errors: Array<{ code: number; message: string }>\n ) {\n const messages = errors.map((e) => e.message).join(', ')\n super(`Cloudflare Images API error (${statusCode}): ${messages}`)\n this.name = 'ImageApiError'\n this.statusCode = statusCode\n this.errors = errors\n }\n}\n\n/**\n * Error thrown when an invalid variant is requested.\n */\nexport class ImageVariantError extends ImageError {\n readonly variant: string\n readonly availableVariants: string[]\n\n constructor(variant: string, availableVariants: string[]) {\n const available =\n availableVariants.length > 0\n ? `Available variants: ${availableVariants.join(', ')}`\n : 'No variants are configured'\n\n super(`Invalid variant '${variant}'. ${available}`)\n this.name = 'ImageVariantError'\n this.variant = variant\n this.availableVariants = availableVariants\n }\n}\n\n/**\n * Error thrown when image binding is accessed outside request context.\n */\nexport class ImageContextError extends ImageError {\n constructor() {\n super(`Image accessed outside of request handler.\n\nThis can happen when:\n1. Accessing images at module-load time (top-level code)\n2. Accessing images in a setTimeout/setInterval callback\n3. The request context was not properly initialized\n\nImages can only be accessed during request handling within a Cloudwerk application.\n\nExample of correct usage:\n import { images } from '@cloudwerk/core/bindings'\n\n export async function POST(request: Request) {\n const formData = await request.formData()\n const file = formData.get('image') as File\n const result = await images.avatars.upload(file)\n return json(result)\n }\n`)\n this.name = 'ImageContextError'\n }\n}\n\n/**\n * Error thrown when an image configuration is not found.\n */\nexport class ImageBindingNotFoundError extends ImageError {\n readonly name: string\n readonly availableImages: string[]\n\n constructor(name: string, availableImages: string[]) {\n const available =\n availableImages.length > 0\n ? `Available images: ${availableImages.join(', ')}`\n : 'No images are configured'\n\n super(`Image '${name}' not found in current environment.\n\n${available}\n\nTo add this image, create a file at app/images/${name}.ts with:\n import { defineImage } from '@cloudwerk/images'\n export default defineImage({\n variants: {\n thumbnail: { width: 100, height: 100, fit: 'cover' },\n },\n })\n`)\n this.name = 'ImageBindingNotFoundError'\n this.availableImages = availableImages\n }\n}\n","/**\n * @cloudwerk/images - defineImage()\n *\n * Factory function for creating image definitions.\n */\n\nimport type { ImageConfig, ImageDefinition, ImageVariant } from './types.js'\nimport { ImageConfigError } from './errors.js'\n\n// ============================================================================\n// Validation\n// ============================================================================\n\n/**\n * Validate a variant configuration.\n *\n * @param variant - Variant to validate\n * @param name - Variant name for error messages\n * @throws ImageConfigError if configuration is invalid\n */\nfunction validateVariant(variant: ImageVariant, name: string): void {\n if (variant.width !== undefined) {\n if (!Number.isInteger(variant.width) || variant.width < 1) {\n throw new ImageConfigError(\n `width must be a positive integer`,\n `variants.${name}.width`\n )\n }\n }\n\n if (variant.height !== undefined) {\n if (!Number.isInteger(variant.height) || variant.height < 1) {\n throw new ImageConfigError(\n `height must be a positive integer`,\n `variants.${name}.height`\n )\n }\n }\n\n if (variant.blur !== undefined) {\n if (variant.blur < 1 || variant.blur > 250) {\n throw new ImageConfigError(\n `blur must be between 1 and 250`,\n `variants.${name}.blur`\n )\n }\n }\n\n if (variant.quality !== undefined) {\n if (variant.quality < 1 || variant.quality > 100) {\n throw new ImageConfigError(\n `quality must be between 1 and 100`,\n `variants.${name}.quality`\n )\n }\n }\n\n if (variant.dpr !== undefined) {\n if (variant.dpr < 1 || variant.dpr > 3) {\n throw new ImageConfigError(\n `dpr must be between 1 and 3`,\n `variants.${name}.dpr`\n )\n }\n }\n\n if (variant.sharpen !== undefined) {\n if (variant.sharpen < 0 || variant.sharpen > 10) {\n throw new ImageConfigError(\n `sharpen must be between 0 and 10`,\n `variants.${name}.sharpen`\n )\n }\n }\n\n if (variant.brightness !== undefined) {\n if (variant.brightness < -1 || variant.brightness > 1) {\n throw new ImageConfigError(\n `brightness must be between -1 and 1`,\n `variants.${name}.brightness`\n )\n }\n }\n\n if (variant.contrast !== undefined) {\n if (variant.contrast < -1 || variant.contrast > 1) {\n throw new ImageConfigError(\n `contrast must be between -1 and 1`,\n `variants.${name}.contrast`\n )\n }\n }\n\n if (variant.rotate !== undefined) {\n if (variant.rotate < 0 || variant.rotate > 360) {\n throw new ImageConfigError(\n `rotate must be between 0 and 360`,\n `variants.${name}.rotate`\n )\n }\n }\n\n const validFits = ['cover', 'contain', 'scale-down', 'crop', 'pad']\n if (variant.fit !== undefined && !validFits.includes(variant.fit)) {\n throw new ImageConfigError(\n `fit must be one of: ${validFits.join(', ')}`,\n `variants.${name}.fit`\n )\n }\n\n const validFormats = ['webp', 'avif', 'json', 'jpeg', 'png']\n if (variant.format !== undefined && !validFormats.includes(variant.format)) {\n throw new ImageConfigError(\n `format must be one of: ${validFormats.join(', ')}`,\n `variants.${name}.format`\n )\n }\n\n const validGravities = ['auto', 'center', 'top', 'bottom', 'left', 'right', 'face']\n if (variant.gravity !== undefined && !validGravities.includes(variant.gravity)) {\n throw new ImageConfigError(\n `gravity must be one of: ${validGravities.join(', ')}`,\n `variants.${name}.gravity`\n )\n }\n\n const validMetadatas = ['keep', 'copyright', 'none']\n if (variant.metadata !== undefined && !validMetadatas.includes(variant.metadata)) {\n throw new ImageConfigError(\n `metadata must be one of: ${validMetadatas.join(', ')}`,\n `variants.${name}.metadata`\n )\n }\n}\n\n/**\n * Validate image configuration.\n *\n * @param config - Image configuration to validate\n * @throws ImageConfigError if configuration is invalid\n */\nfunction validateConfig(config: ImageConfig): void {\n // Validate name if provided\n if (config.name !== undefined) {\n if (typeof config.name !== 'string' || config.name.length === 0) {\n throw new ImageConfigError('name must be a non-empty string', 'name')\n }\n\n // Names should be lowercase alphanumeric with hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(config.name)) {\n throw new ImageConfigError(\n 'name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens',\n 'name'\n )\n }\n }\n\n // Validate deliveryUrl if provided\n if (config.deliveryUrl !== undefined) {\n if (typeof config.deliveryUrl !== 'string') {\n throw new ImageConfigError(\n 'deliveryUrl must be a string',\n 'deliveryUrl'\n )\n }\n\n try {\n new URL(config.deliveryUrl)\n } catch {\n throw new ImageConfigError(\n 'deliveryUrl must be a valid URL',\n 'deliveryUrl'\n )\n }\n }\n\n // Validate variants\n if (config.variants !== undefined) {\n if (typeof config.variants !== 'object' || config.variants === null) {\n throw new ImageConfigError('variants must be an object', 'variants')\n }\n\n for (const [name, variant] of Object.entries(config.variants)) {\n // Variant names should be alphanumeric with hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(name)) {\n throw new ImageConfigError(\n `variant name '${name}' must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens`,\n 'variants'\n )\n }\n\n validateVariant(variant, name)\n }\n }\n\n // Validate defaultVariant references an existing variant\n if (config.defaultVariant !== undefined && config.variants !== undefined) {\n if (!(config.defaultVariant in config.variants)) {\n throw new ImageConfigError(\n `defaultVariant '${config.defaultVariant}' is not defined in variants`,\n 'defaultVariant'\n )\n }\n }\n}\n\n// ============================================================================\n// defineImage()\n// ============================================================================\n\n/**\n * Define a Cloudflare Images configuration.\n *\n * This function creates an image definition that will be automatically\n * discovered and registered by Cloudwerk during build.\n *\n * @param config - Image configuration\n * @returns Image definition\n *\n * @example\n * ```typescript\n * // app/images/avatars.ts\n * import { defineImage } from '@cloudwerk/images'\n *\n * export default defineImage({\n * variants: {\n * thumbnail: { width: 100, height: 100, fit: 'cover' },\n * profile: { width: 400, height: 400, fit: 'cover' },\n * large: { width: 1200, height: 1200, fit: 'contain' },\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // app/images/products.ts - with custom delivery domain\n * import { defineImage } from '@cloudwerk/images'\n *\n * export default defineImage({\n * deliveryUrl: 'https://images.mystore.com',\n * variants: {\n * card: { width: 300, height: 300, fit: 'cover' },\n * detail: { width: 800, height: 600, fit: 'contain' },\n * zoom: { width: 1600, height: 1200, fit: 'contain' },\n * },\n * defaultVariant: 'card',\n * })\n * ```\n *\n * @example\n * ```typescript\n * // app/images/secure.ts - with signed URLs required\n * import { defineImage } from '@cloudwerk/images'\n *\n * export default defineImage({\n * requireSignedURLs: true,\n * variants: {\n * preview: { width: 200, height: 200, fit: 'cover', blur: 20 },\n * full: { width: 1920, height: 1080, fit: 'contain' },\n * },\n * })\n * ```\n */\nexport function defineImage(config: ImageConfig = {}): ImageDefinition {\n // Validate configuration\n validateConfig(config)\n\n // Create the definition object\n const definition: ImageDefinition = {\n __brand: 'cloudwerk-image',\n name: config.name ?? '', // Will be set from filename if empty\n config,\n variants: config.variants ?? {},\n }\n\n return definition\n}\n\n/**\n * Check if a value is an image definition created by defineImage().\n *\n * @param value - Value to check\n * @returns true if value is an ImageDefinition\n */\nexport function isImageDefinition(value: unknown): value is ImageDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__brand' in value &&\n (value as ImageDefinition).__brand === 'cloudwerk-image'\n )\n}\n","/**\n * @cloudwerk/images - Cloudflare API Types\n *\n * Type definitions for Cloudflare Images API responses.\n * Based on Cloudflare API documentation.\n */\n\n// ============================================================================\n// API Response Wrapper\n// ============================================================================\n\n/**\n * Standard Cloudflare API response wrapper.\n */\nexport interface CloudflareApiResponse<T> {\n result: T\n success: boolean\n errors: CloudflareApiError[]\n messages: CloudflareApiMessage[]\n}\n\n/**\n * Cloudflare API error.\n */\nexport interface CloudflareApiError {\n code: number\n message: string\n}\n\n/**\n * Cloudflare API message.\n */\nexport interface CloudflareApiMessage {\n code: number\n message: string\n}\n\n// ============================================================================\n// Image Types\n// ============================================================================\n\n/**\n * Cloudflare Image object from API.\n */\nexport interface CloudflareImage {\n /** Unique image identifier */\n id: string\n\n /** Original filename */\n filename: string\n\n /** Upload timestamp (ISO 8601) */\n uploaded: string\n\n /** Whether signed URLs are required */\n requireSignedURLs: boolean\n\n /** Available variant URLs */\n variants: string[]\n\n /** Custom metadata */\n meta?: Record<string, string>\n}\n\n/**\n * Response from uploading an image.\n */\nexport interface CloudflareUploadResponse {\n id: string\n filename: string\n uploaded: string\n requireSignedURLs: boolean\n variants: string[]\n meta?: Record<string, string>\n}\n\n/**\n * Response from creating a direct upload URL.\n */\nexport interface CloudflareDirectUploadResponse {\n id: string\n uploadURL: string\n}\n\n/**\n * Response from listing images.\n */\nexport interface CloudflareListResponse {\n images: CloudflareImage[]\n continuation_token?: string\n}\n\n/**\n * Response from getting image details.\n */\nexport interface CloudflareImageDetailsResponse {\n id: string\n filename: string\n uploaded: string\n requireSignedURLs: boolean\n variants: string[]\n meta?: Record<string, string>\n}\n\n// ============================================================================\n// Request Types\n// ============================================================================\n\n/**\n * Request body for creating a direct upload URL.\n */\nexport interface DirectUploadRequest {\n /** Whether to require signed URLs */\n requireSignedURLs?: boolean\n\n /** Custom metadata */\n metadata?: Record<string, string>\n\n /** Expiry timestamp (ISO 8601 or Unix timestamp) */\n expiry?: string\n}\n\n// ============================================================================\n// Variant Types\n// ============================================================================\n\n/**\n * Cloudflare Images variant configuration (API format).\n */\nexport interface CloudflareVariant {\n id: string\n options: {\n fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad'\n width?: number\n height?: number\n metadata?: 'keep' | 'copyright' | 'none'\n }\n neverRequireSignedURLs?: boolean\n}\n\n// ============================================================================\n// Stats Types\n// ============================================================================\n\n/**\n * Cloudflare Images usage statistics.\n */\nexport interface CloudflareImagesStats {\n count: {\n current: number\n allowed: number\n }\n}\n\n// ============================================================================\n// API Endpoints\n// ============================================================================\n\n/**\n * Cloudflare Images API endpoints.\n */\nexport const CLOUDFLARE_IMAGES_API = {\n /** Base URL for Cloudflare API v4 */\n BASE: 'https://api.cloudflare.com/client/v4',\n\n /**\n * Get the images endpoint for an account.\n */\n images: (accountId: string) =>\n `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v1`,\n\n /**\n * Get the direct upload endpoint.\n */\n directUpload: (accountId: string) =>\n `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v2/direct_upload`,\n\n /**\n * Get a specific image endpoint.\n */\n image: (accountId: string, imageId: string) =>\n `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v1/${imageId}`,\n\n /**\n * Get the default delivery URL format.\n */\n deliveryUrl: (accountId: string, imageId: string, variant: string) =>\n `https://imagedelivery.net/${accountId}/${imageId}/${variant}`,\n} as const\n","/**\n * @cloudwerk/images - Image Client\n *\n * Client for interacting with Cloudflare Images API.\n */\n\nimport type {\n ImageClientInterface,\n ImageResult,\n DirectUploadResult,\n UploadOptions,\n DirectUploadOptions,\n ListOptions,\n ImageVariant,\n} from './types.js'\nimport type {\n CloudflareApiResponse,\n CloudflareUploadResponse,\n CloudflareDirectUploadResponse,\n CloudflareListResponse,\n CloudflareImage,\n} from './cloudflare-types.js'\nimport { CLOUDFLARE_IMAGES_API } from './cloudflare-types.js'\nimport {\n ImageApiError,\n ImageNotFoundError,\n ImageUploadError,\n ImageVariantError,\n} from './errors.js'\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Parse a duration string into an ISO 8601 expiry timestamp.\n *\n * @param duration - Duration string ('30m', '1h') or seconds\n * @returns ISO 8601 timestamp string\n */\nfunction parseExpiry(duration: string | number): string {\n let seconds: number\n\n if (typeof duration === 'number') {\n seconds = duration\n } else {\n const match = duration.match(/^(\\d+)(s|m|h|d)$/)\n if (!match) {\n // Default to 30 minutes if invalid format\n seconds = 30 * 60\n } else {\n const value = parseInt(match[1], 10)\n const unit = match[2]\n\n switch (unit) {\n case 's':\n seconds = value\n break\n case 'm':\n seconds = value * 60\n break\n case 'h':\n seconds = value * 3600\n break\n case 'd':\n seconds = value * 86400\n break\n default:\n seconds = 30 * 60\n }\n }\n }\n\n const expiryDate = new Date(Date.now() + seconds * 1000)\n return expiryDate.toISOString()\n}\n\n/**\n * Convert Cloudflare API image to ImageResult.\n */\nfunction toImageResult(image: CloudflareImage | CloudflareUploadResponse): ImageResult {\n return {\n id: image.id,\n filename: image.filename || undefined,\n uploaded: new Date(image.uploaded),\n variants: image.variants.map((url) => {\n // Extract variant name from URL\n const parts = url.split('/')\n return parts[parts.length - 1]\n }),\n metadata: image.meta,\n requireSignedURLs: image.requireSignedURLs,\n }\n}\n\n// ============================================================================\n// Image Client\n// ============================================================================\n\n/**\n * Client for interacting with Cloudflare Images.\n *\n * @example\n * ```typescript\n * const client = new ImageClient('account-id', 'api-token', {\n * thumbnail: { width: 100, height: 100, fit: 'cover' },\n * })\n *\n * // Upload an image\n * const result = await client.upload(file)\n *\n * // Get URL with variant\n * const url = client.url(result.id, 'thumbnail')\n * ```\n */\nexport class ImageClient<T extends string = string>\n implements ImageClientInterface<T>\n{\n private readonly accountId: string\n private readonly apiToken: string\n private readonly deliveryUrl?: string\n private readonly variants: Record<string, ImageVariant>\n private readonly variantNames: string[]\n\n constructor(\n accountId: string,\n apiToken: string,\n variants: Record<string, ImageVariant> = {},\n deliveryUrl?: string\n ) {\n this.accountId = accountId\n this.apiToken = apiToken\n this.variants = variants\n this.variantNames = Object.keys(variants)\n this.deliveryUrl = deliveryUrl\n }\n\n /**\n * Make an authenticated API request.\n */\n private async fetch<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<CloudflareApiResponse<T>> {\n const response = await fetch(endpoint, {\n ...options,\n headers: {\n Authorization: `Bearer ${this.apiToken}`,\n ...options.headers,\n },\n })\n\n const data = (await response.json()) as CloudflareApiResponse<T>\n\n if (!response.ok || !data.success) {\n throw new ImageApiError(response.status, data.errors || [])\n }\n\n return data\n }\n\n /**\n * Upload an image file.\n */\n async upload(\n file: File | Blob | ReadableStream,\n options: UploadOptions = {}\n ): Promise<ImageResult> {\n const endpoint = CLOUDFLARE_IMAGES_API.images(this.accountId)\n\n const formData = new FormData()\n\n // Handle different file types\n if (file instanceof ReadableStream) {\n // Convert ReadableStream to Blob\n const response = new Response(file)\n const blob = await response.blob()\n formData.append('file', blob)\n } else {\n formData.append('file', file)\n }\n\n // Add optional parameters\n if (options.id) {\n formData.append('id', options.id)\n }\n\n if (options.requireSignedURLs !== undefined) {\n formData.append('requireSignedURLs', String(options.requireSignedURLs))\n }\n\n if (options.metadata) {\n formData.append('metadata', JSON.stringify(options.metadata))\n }\n\n try {\n const response = await this.fetch<CloudflareUploadResponse>(endpoint, {\n method: 'POST',\n body: formData,\n })\n\n return toImageResult(response.result)\n } catch (error) {\n if (error instanceof ImageApiError) {\n throw new ImageUploadError(\n `Failed to upload image: ${error.message}`,\n error.statusCode,\n error.errors.map((e) => e.message).join(', ')\n )\n }\n throw error\n }\n }\n\n /**\n * Generate a direct upload URL for client-side uploads.\n */\n async getDirectUploadUrl(\n options: DirectUploadOptions = {}\n ): Promise<DirectUploadResult> {\n const endpoint = CLOUDFLARE_IMAGES_API.directUpload(this.accountId)\n\n const body: Record<string, unknown> = {}\n\n if (options.requireSignedURLs !== undefined) {\n body.requireSignedURLs = options.requireSignedURLs\n }\n\n if (options.metadata) {\n body.metadata = options.metadata\n }\n\n if (options.expiry) {\n body.expiry = parseExpiry(options.expiry)\n }\n\n const response = await this.fetch<CloudflareDirectUploadResponse>(\n endpoint,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n }\n )\n\n return {\n uploadUrl: response.result.uploadURL,\n id: response.result.id,\n }\n }\n\n /**\n * Delete an image.\n */\n async delete(imageId: string): Promise<void> {\n const endpoint = CLOUDFLARE_IMAGES_API.image(this.accountId, imageId)\n\n try {\n await this.fetch(endpoint, {\n method: 'DELETE',\n })\n } catch (error) {\n if (error instanceof ImageApiError && error.statusCode === 404) {\n throw new ImageNotFoundError(imageId)\n }\n throw error\n }\n }\n\n /**\n * Get image details.\n */\n async get(imageId: string): Promise<ImageResult | null> {\n const endpoint = CLOUDFLARE_IMAGES_API.image(this.accountId, imageId)\n\n try {\n const response = await this.fetch<CloudflareImage>(endpoint)\n return toImageResult(response.result)\n } catch (error) {\n if (error instanceof ImageApiError && error.statusCode === 404) {\n return null\n }\n throw error\n }\n }\n\n /**\n * List images with pagination.\n */\n async list(options: ListOptions = {}): Promise<ImageResult[]> {\n const endpoint = CLOUDFLARE_IMAGES_API.images(this.accountId)\n const params = new URLSearchParams()\n\n if (options.page !== undefined) {\n params.set('page', String(options.page))\n }\n\n if (options.perPage !== undefined) {\n params.set('per_page', String(Math.min(100, Math.max(1, options.perPage))))\n }\n\n const urlWithParams = params.toString()\n ? `${endpoint}?${params}`\n : endpoint\n\n const response = await this.fetch<CloudflareListResponse>(urlWithParams)\n return response.result.images.map(toImageResult)\n }\n\n /**\n * Generate URL for an image with optional variant.\n */\n url(imageId: string, variant?: T): string {\n // Validate variant if provided\n if (variant !== undefined && !this.variantNames.includes(variant)) {\n throw new ImageVariantError(variant, this.variantNames)\n }\n\n // Use custom delivery URL if provided\n if (this.deliveryUrl) {\n const variantPath = variant ? `/${variant}` : ''\n return `${this.deliveryUrl}/${imageId}${variantPath}`\n }\n\n // Use default Cloudflare Images delivery URL\n const variantName = variant ?? 'public'\n return CLOUDFLARE_IMAGES_API.deliveryUrl(\n this.accountId,\n imageId,\n variantName\n )\n }\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Create an image client from configuration.\n *\n * @param accountId - Cloudflare account ID\n * @param apiToken - Cloudflare Images API token\n * @param variants - Variant definitions\n * @param deliveryUrl - Custom delivery URL (optional)\n * @returns Image client instance\n */\nexport function createImageClient<T extends string = string>(\n accountId: string,\n apiToken: string,\n variants: Record<T, ImageVariant> = {} as Record<T, ImageVariant>,\n deliveryUrl?: string\n): ImageClient<T> {\n return new ImageClient<T>(accountId, apiToken, variants, deliveryUrl)\n}\n","/**\n * @cloudwerk/images - Image Transformer\n *\n * Route handler helper for Cloudflare's Image Resizing service.\n * Creates a handler that transforms images via `fetch(url, { cf: { image: {...} } })`.\n *\n * @example\n * ```typescript\n * // app/cdn/images/[...path]/route.ts\n * import { createImageTransformer } from '@cloudwerk/images'\n *\n * export const GET = createImageTransformer({\n * allowedOrigins: ['https://images.mysite.com'],\n * presets: {\n * thumbnail: { width: 100, height: 100, fit: 'cover' },\n * hero: { width: 1920, height: 1080, fit: 'cover' },\n * },\n * })\n * ```\n */\n\nimport type { ImageVariant } from './types.js'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for the image transformer route handler.\n */\nexport interface TransformerConfig {\n /**\n * Allowed origin URLs for image sources.\n * If not specified, any origin is allowed (not recommended for production).\n *\n * @example ['https://images.mysite.com', 'https://cdn.example.com']\n */\n allowedOrigins?: string[]\n\n /**\n * Named transformation presets.\n * Can be applied via URL parameter (e.g., ?preset=thumbnail).\n *\n * @example\n * ```typescript\n * presets: {\n * thumbnail: { width: 100, height: 100, fit: 'cover' },\n * hero: { width: 1920, height: 1080, fit: 'cover' },\n * }\n * ```\n */\n presets?: Record<string, TransformPreset>\n\n /**\n * Default transformation options applied to all requests.\n */\n defaults?: TransformPreset\n\n /**\n * Maximum allowed width. Requests exceeding this will be clamped.\n * @default 4096\n */\n maxWidth?: number\n\n /**\n * Maximum allowed height. Requests exceeding this will be clamped.\n * @default 4096\n */\n maxHeight?: number\n\n /**\n * Cache control header for transformed images.\n * @default 'public, max-age=31536000' (1 year)\n */\n cacheControl?: string\n\n /**\n * Whether to allow arbitrary width/height from query parameters.\n * If false, only preset transformations are allowed.\n * @default true\n */\n allowArbitrary?: boolean\n\n /**\n * Custom validation function for source URLs.\n * Return true to allow the URL, false to reject.\n */\n validateSource?: (url: URL) => boolean | Promise<boolean>\n}\n\n/**\n * Transformation preset options.\n * Subset of Cloudflare Image Resizing options.\n */\nexport interface TransformPreset {\n /** Width in pixels */\n width?: number\n /** Height in pixels */\n height?: number\n /** Fit mode */\n fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad'\n /** Output format */\n format?: 'webp' | 'avif' | 'json' | 'jpeg' | 'png' | 'auto'\n /** Quality (1-100) */\n quality?: number\n /** Device pixel ratio (1-3) */\n dpr?: number\n /** Gravity for cropping */\n gravity?: 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'face'\n /** Sharpen (0-10) */\n sharpen?: number\n /** Blur (1-250) */\n blur?: number\n /** Brightness (-1 to 1) */\n brightness?: number\n /** Contrast (-1 to 1) */\n contrast?: number\n /** Rotation (0, 90, 180, 270) */\n rotate?: 0 | 90 | 180 | 270\n /** Metadata handling */\n metadata?: 'keep' | 'copyright' | 'none'\n /** Background color for padding */\n background?: string\n}\n\n/**\n * Cloudflare Image Resizing options passed to fetch().\n */\ninterface CfImageOptions {\n width?: number\n height?: number\n fit?: string\n format?: string\n quality?: number\n dpr?: number\n gravity?: string\n sharpen?: number\n blur?: number\n brightness?: number\n contrast?: number\n rotate?: number\n metadata?: string\n background?: string\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error thrown when image transformation fails.\n */\nexport class ImageTransformError extends Error {\n readonly status: number\n readonly code: string\n\n constructor(message: string, status: number, code: string) {\n super(message)\n this.name = 'ImageTransformError'\n this.status = status\n this.code = code\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Parse transformation options from URL search params.\n */\nfunction parseTransformParams(\n searchParams: URLSearchParams,\n config: TransformerConfig\n): TransformPreset {\n const options: TransformPreset = {}\n\n // Check for preset\n const presetName = searchParams.get('preset')\n if (presetName && config.presets?.[presetName]) {\n Object.assign(options, config.presets[presetName])\n }\n\n // Apply defaults\n if (config.defaults) {\n // Defaults are applied first, then overridden by preset and params\n Object.assign(options, config.defaults, options)\n }\n\n // If arbitrary params are allowed, parse them\n if (config.allowArbitrary !== false) {\n // Width\n const width = searchParams.get('w') || searchParams.get('width')\n if (width) {\n const w = parseInt(width, 10)\n if (!isNaN(w) && w > 0) {\n options.width = Math.min(w, config.maxWidth ?? 4096)\n }\n }\n\n // Height\n const height = searchParams.get('h') || searchParams.get('height')\n if (height) {\n const h = parseInt(height, 10)\n if (!isNaN(h) && h > 0) {\n options.height = Math.min(h, config.maxHeight ?? 4096)\n }\n }\n\n // Fit\n const fit = searchParams.get('fit')\n if (fit && ['cover', 'contain', 'scale-down', 'crop', 'pad'].includes(fit)) {\n options.fit = fit as TransformPreset['fit']\n }\n\n // Format\n const format = searchParams.get('format') || searchParams.get('f')\n if (format && ['webp', 'avif', 'jpeg', 'png', 'auto'].includes(format)) {\n options.format = format as TransformPreset['format']\n }\n\n // Quality\n const quality = searchParams.get('q') || searchParams.get('quality')\n if (quality) {\n const q = parseInt(quality, 10)\n if (!isNaN(q) && q >= 1 && q <= 100) {\n options.quality = q\n }\n }\n\n // DPR\n const dpr = searchParams.get('dpr')\n if (dpr) {\n const d = parseFloat(dpr)\n if (!isNaN(d) && d >= 1 && d <= 3) {\n options.dpr = d\n }\n }\n\n // Gravity\n const gravity = searchParams.get('gravity')\n if (gravity && ['auto', 'center', 'top', 'bottom', 'left', 'right', 'face'].includes(gravity)) {\n options.gravity = gravity as TransformPreset['gravity']\n }\n\n // Blur\n const blur = searchParams.get('blur')\n if (blur) {\n const b = parseInt(blur, 10)\n if (!isNaN(b) && b >= 1 && b <= 250) {\n options.blur = b\n }\n }\n\n // Sharpen\n const sharpen = searchParams.get('sharpen')\n if (sharpen) {\n const s = parseFloat(sharpen)\n if (!isNaN(s) && s >= 0 && s <= 10) {\n options.sharpen = s\n }\n }\n\n // Brightness\n const brightness = searchParams.get('brightness')\n if (brightness) {\n const br = parseFloat(brightness)\n if (!isNaN(br) && br >= -1 && br <= 1) {\n options.brightness = br\n }\n }\n\n // Contrast\n const contrast = searchParams.get('contrast')\n if (contrast) {\n const c = parseFloat(contrast)\n if (!isNaN(c) && c >= -1 && c <= 1) {\n options.contrast = c\n }\n }\n\n // Rotate\n const rotate = searchParams.get('rotate')\n if (rotate) {\n const r = parseInt(rotate, 10)\n if ([0, 90, 180, 270].includes(r)) {\n options.rotate = r as TransformPreset['rotate']\n }\n }\n }\n\n return options\n}\n\n/**\n * Convert TransformPreset to Cloudflare Image options.\n */\nfunction presetToCfOptions(preset: TransformPreset): CfImageOptions {\n const options: CfImageOptions = {}\n\n if (preset.width !== undefined) options.width = preset.width\n if (preset.height !== undefined) options.height = preset.height\n if (preset.fit !== undefined) options.fit = preset.fit\n if (preset.format !== undefined) options.format = preset.format\n if (preset.quality !== undefined) options.quality = preset.quality\n if (preset.dpr !== undefined) options.dpr = preset.dpr\n if (preset.gravity !== undefined) options.gravity = preset.gravity\n if (preset.sharpen !== undefined) options.sharpen = preset.sharpen\n if (preset.blur !== undefined) options.blur = preset.blur\n if (preset.brightness !== undefined) options.brightness = preset.brightness\n if (preset.contrast !== undefined) options.contrast = preset.contrast\n if (preset.rotate !== undefined) options.rotate = preset.rotate\n if (preset.metadata !== undefined) options.metadata = preset.metadata\n if (preset.background !== undefined) options.background = preset.background\n\n return options\n}\n\n/**\n * Validate a source URL against allowed origins.\n */\nfunction validateOrigin(sourceUrl: URL, allowedOrigins?: string[]): boolean {\n if (!allowedOrigins || allowedOrigins.length === 0) {\n return true // No restrictions\n }\n\n const sourceOrigin = sourceUrl.origin\n return allowedOrigins.some((origin) => {\n // Exact match\n if (origin === sourceOrigin) return true\n // Wildcard subdomain match (e.g., 'https://*.example.com')\n if (origin.includes('*')) {\n const pattern = origin.replace(/\\*/g, '[^.]+')\n const regex = new RegExp(`^${pattern}$`)\n return regex.test(sourceOrigin)\n }\n return false\n })\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Create an image transformer route handler.\n *\n * This creates a GET handler that:\n * 1. Extracts the source image URL from the request path\n * 2. Parses transformation options from query parameters\n * 3. Fetches the source image with Cloudflare Image Resizing options\n * 4. Returns the transformed image\n *\n * @param config - Transformer configuration\n * @returns Route handler function\n *\n * @example\n * ```typescript\n * // app/cdn/images/[...path]/route.ts\n * import { createImageTransformer } from '@cloudwerk/images'\n *\n * export const GET = createImageTransformer({\n * allowedOrigins: ['https://images.mysite.com'],\n * presets: {\n * thumbnail: { width: 100, height: 100, fit: 'cover' },\n * hero: { width: 1920, height: 1080, fit: 'cover' },\n * },\n * defaults: {\n * format: 'auto',\n * quality: 85,\n * },\n * })\n *\n * // Usage:\n * // /cdn/images/https://images.mysite.com/photo.jpg?preset=thumbnail\n * // /cdn/images/https://images.mysite.com/photo.jpg?w=800&h=600&fit=cover\n * ```\n */\nexport function createImageTransformer(\n config: TransformerConfig = {}\n): (request: Request, context: { params: Record<string, string | string[]> }) => Promise<Response> {\n const {\n allowedOrigins,\n presets = {},\n defaults = {},\n maxWidth = 4096,\n maxHeight = 4096,\n cacheControl = 'public, max-age=31536000',\n allowArbitrary = true,\n validateSource,\n } = config\n\n return async (request: Request, context: { params: Record<string, string | string[]> }) => {\n // Get the source URL from the path parameter\n const pathParam = context.params.path\n if (!pathParam) {\n return new Response(JSON.stringify({ error: 'Missing image path' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Reconstruct the source URL\n const sourcePath = Array.isArray(pathParam) ? pathParam.join('/') : pathParam\n let sourceUrl: URL\n\n try {\n sourceUrl = new URL(sourcePath)\n } catch {\n // Try treating it as a relative path if it doesn't look like a URL\n return new Response(JSON.stringify({ error: 'Invalid image URL' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Validate origin\n if (!validateOrigin(sourceUrl, allowedOrigins)) {\n return new Response(JSON.stringify({ error: 'Origin not allowed' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Custom validation\n if (validateSource) {\n const isValid = await validateSource(sourceUrl)\n if (!isValid) {\n return new Response(JSON.stringify({ error: 'Source URL rejected' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n }\n\n // Parse transformation options\n const requestUrl = new URL(request.url)\n const transformOptions = parseTransformParams(requestUrl.searchParams, {\n presets,\n defaults,\n maxWidth,\n maxHeight,\n allowArbitrary,\n })\n\n // Convert to Cloudflare options\n const cfOptions = presetToCfOptions(transformOptions)\n\n // Check if there are any transformations\n const hasTransforms = Object.keys(cfOptions).length > 0\n\n try {\n // Fetch the image with transformations\n const imageRequest = new Request(sourceUrl.toString(), {\n headers: request.headers,\n })\n\n const fetchOptions: RequestInit & { cf?: { image?: CfImageOptions } } = {}\n if (hasTransforms) {\n fetchOptions.cf = { image: cfOptions }\n }\n\n const response = await fetch(imageRequest, fetchOptions)\n\n if (!response.ok) {\n return new Response(JSON.stringify({ error: 'Failed to fetch source image' }), {\n status: response.status,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Return the transformed image with appropriate headers\n const headers = new Headers(response.headers)\n headers.set('Cache-Control', cacheControl)\n\n // Add Vary header for content negotiation\n headers.set('Vary', 'Accept')\n\n return new Response(response.body, {\n status: 200,\n headers,\n })\n } catch (error) {\n console.error('Image transform error:', error)\n return new Response(JSON.stringify({ error: 'Image transformation failed' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n }\n}\n\n/**\n * Convert an ImageVariant to a TransformPreset.\n * Useful when you want to reuse variants defined for Hosted Images.\n */\nexport function variantToPreset(variant: ImageVariant): TransformPreset {\n const preset: TransformPreset = {}\n\n if (variant.width !== undefined) preset.width = variant.width\n if (variant.height !== undefined) preset.height = variant.height\n if (variant.fit !== undefined) preset.fit = variant.fit\n if (variant.format !== undefined) preset.format = variant.format\n if (variant.quality !== undefined) preset.quality = variant.quality\n if (variant.dpr !== undefined) preset.dpr = variant.dpr\n if (variant.gravity !== undefined) {\n // Map gravity values\n preset.gravity = variant.gravity\n }\n if (variant.sharpen !== undefined) preset.sharpen = variant.sharpen\n if (variant.blur !== undefined) preset.blur = variant.blur\n if (variant.brightness !== undefined) preset.brightness = variant.brightness\n if (variant.contrast !== undefined) preset.contrast = variant.contrast\n if (variant.rotate !== undefined) {\n // Ensure rotation is valid\n if ([0, 90, 180, 270].includes(variant.rotate)) {\n preset.rotate = variant.rotate as 0 | 90 | 180 | 270\n }\n }\n if (variant.metadata !== undefined) preset.metadata = variant.metadata\n\n return preset\n}\n","/**\n * @cloudwerk/images - Cloudflare IMAGES Binding Types\n *\n * Type definitions for the Cloudflare IMAGES binding used in Workers.\n * This binding provides on-the-fly image transformation without uploading\n * to Cloudflare Images.\n *\n * @example\n * ```typescript\n * // wrangler.toml\n * [images]\n * binding = \"MY_IMAGES\"\n * ```\n *\n * @example\n * ```typescript\n * import { getBinding } from '@cloudwerk/core/bindings'\n * import type { CloudflareImagesBinding } from '@cloudwerk/images'\n *\n * const IMAGES = getBinding<CloudflareImagesBinding>('MY_IMAGES')\n *\n * // Transform an image\n * const result = await IMAGES\n * .input(imageStream)\n * .transform({ width: 800 })\n * .output({ format: 'image/webp' })\n * .response()\n * ```\n */\n\n// ============================================================================\n// Cloudflare IMAGES Binding Types\n// ============================================================================\n\n/**\n * Image information returned by the IMAGES binding.\n */\nexport interface CloudflareImageInfo {\n /** Width of the image in pixels */\n width: number\n /** Height of the image in pixels */\n height: number\n /** Format of the image (e.g., 'image/jpeg', 'image/png') */\n format: string\n /** File size in bytes */\n fileSize: number\n}\n\n/**\n * Transform options for the IMAGES binding.\n */\nexport interface CloudflareImageTransformOptions {\n /** Width in pixels */\n width?: number\n /** Height in pixels */\n height?: number\n /** Fit mode for resizing */\n fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad'\n /** Gravity for cropping */\n gravity?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'face'\n /** Quality for lossy formats (1-100) */\n quality?: number\n /** Device pixel ratio (1-3) */\n dpr?: number\n /** Rotation in degrees (0, 90, 180, 270) */\n rotate?: 0 | 90 | 180 | 270\n /** Sharpen amount (0-10) */\n sharpen?: number\n /** Blur radius (1-250) */\n blur?: number\n /** Brightness adjustment (-1 to 1) */\n brightness?: number\n /** Contrast adjustment (-1 to 1) */\n contrast?: number\n /** Background color for padding (hex or rgb) */\n background?: string\n /** Border configuration */\n border?: {\n color: string\n width: number\n }\n /** Trim whitespace from edges */\n trim?: {\n top?: number\n right?: number\n bottom?: number\n left?: number\n }\n}\n\n/**\n * Output options for the IMAGES binding.\n */\nexport interface CloudflareImageOutputOptions {\n /** Output format */\n format?: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif' | 'image/gif'\n /** Quality for lossy formats (1-100) */\n quality?: number\n /** Metadata handling */\n metadata?: 'keep' | 'copyright' | 'none'\n}\n\n/**\n * Result object returned after awaiting the transform pipeline.\n * Use `.response()` to get the transformed image.\n */\nexport interface CloudflareImagesTransformResult {\n /**\n * Get the transformed image as a Response.\n * @returns Response containing the transformed image\n */\n response(): Response\n}\n\n/**\n * Transform builder interface for the IMAGES binding.\n * Provides a fluent API for chaining transformations.\n *\n * @example\n * ```typescript\n * // The entire chain must be awaited, then call .response()\n * const result = await IMAGES\n * .input(stream)\n * .transform({ rotate: 90 })\n * .output({ format: 'image/webp' })\n *\n * return result.response()\n * ```\n */\nexport interface CloudflareImagesTransformBuilder {\n /**\n * Apply transformation options to the image.\n * Can be chained multiple times.\n * @param options - Transformation options\n * @returns The builder for chaining\n */\n transform(options: CloudflareImageTransformOptions): CloudflareImagesTransformBuilder\n\n /**\n * Set output format and options.\n * @param options - Output options\n * @returns Promise that resolves to a result object with .response() method\n */\n output(options: CloudflareImageOutputOptions): Promise<CloudflareImagesTransformResult>\n\n /**\n * Draw another image over this one (watermarking/compositing).\n * @param image - Another transform builder or ReadableStream\n * @param position - Position options (top, left, bottom, right, opacity, repeat)\n * @returns The builder for chaining\n */\n draw(\n image: CloudflareImagesTransformBuilder | ReadableStream<Uint8Array>,\n position?: {\n top?: number\n left?: number\n bottom?: number\n right?: number\n opacity?: number\n repeat?: boolean | 'x' | 'y'\n }\n ): CloudflareImagesTransformBuilder\n}\n\n/**\n * Cloudflare IMAGES binding interface.\n *\n * This binding provides access to Cloudflare's image transformation pipeline\n * for on-the-fly image processing without uploading to Cloudflare Images.\n *\n * @example\n * ```typescript\n * import { getBinding } from '@cloudwerk/core/bindings'\n * import type { CloudflareImagesBinding } from '@cloudwerk/images'\n *\n * export async function GET(request: Request) {\n * const IMAGES = getBinding<CloudflareImagesBinding>('MY_IMAGES')\n *\n * // Transform an image from a stream\n * // Note: await the chain, then call .response()\n * const result = await IMAGES\n * .input(imageStream)\n * .transform({ width: 800, rotate: 90 })\n * .output({ format: 'image/webp' })\n *\n * return result.response()\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Get image information\n * const info = await IMAGES.info(imageStream)\n * console.log(`Image size: ${info.width}x${info.height}`)\n * ```\n */\nexport interface CloudflareImagesBinding {\n /**\n * Start a transformation pipeline with an input image.\n * @param source - Image source as Blob, ArrayBuffer, or ReadableStream\n * @returns Transform builder for chaining operations\n */\n input(source: Blob | ArrayBuffer | ReadableStream<Uint8Array>): CloudflareImagesTransformBuilder\n\n /**\n * Get information about an image without transforming it.\n * @param source - Image source as Blob, ArrayBuffer, or ReadableStream\n * @returns Image information including dimensions and format\n */\n info(source: Blob | ArrayBuffer | ReadableStream<Uint8Array>): Promise<CloudflareImageInfo>\n}\n\n// ============================================================================\n// Additional Transform Types\n// ============================================================================\n\n/**\n * Fit modes for image resizing.\n *\n * - `scale-down`: Shrink to fit within dimensions, never enlarge\n * - `contain`: Fit within dimensions, may add letterboxing\n * - `cover`: Fill dimensions, cropping if necessary\n * - `crop`: Crop to exact dimensions from center\n * - `pad`: Fit within dimensions, padding if necessary\n */\nexport type ImageFitMode = 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad'\n\n/**\n * Gravity options for cropping.\n *\n * - `auto`: Automatic based on image content\n * - `left`, `right`, `top`, `bottom`: Align to edge\n * - `center`: Center crop\n * - `face`: Focus on detected faces\n */\nexport type ImageGravity = 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'face'\n\n/**\n * Supported output formats.\n */\nexport type ImageOutputFormat = 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif' | 'image/gif'\n\n/**\n * Metadata handling options.\n *\n * - `keep`: Preserve all metadata\n * - `copyright`: Keep only copyright information\n * - `none`: Strip all metadata\n */\nexport type ImageMetadataHandling = 'keep' | 'copyright' | 'none'\n\n// ============================================================================\n// Helper Types for Common Use Cases\n// ============================================================================\n\n/**\n * Preset transform configurations for common use cases.\n */\nexport interface ImageTransformPreset {\n /** Width in pixels */\n width?: number\n /** Height in pixels */\n height?: number\n /** Fit mode */\n fit?: ImageFitMode\n /** Quality (1-100) */\n quality?: number\n /** Output format */\n format?: ImageOutputFormat\n}\n\n/**\n * Common presets for image transformations.\n */\nexport const IMAGE_PRESETS = {\n /** Small thumbnail (100x100 cover) */\n thumbnail: {\n width: 100,\n height: 100,\n fit: 'cover' as const,\n quality: 80,\n },\n /** Medium preview (400x400 contain) */\n preview: {\n width: 400,\n height: 400,\n fit: 'contain' as const,\n quality: 85,\n },\n /** Large display (1200px wide) */\n large: {\n width: 1200,\n fit: 'scale-down' as const,\n quality: 90,\n },\n /** Hero banner (1920x1080 cover) */\n hero: {\n width: 1920,\n height: 1080,\n fit: 'cover' as const,\n quality: 85,\n },\n /** Social share (1200x630 for Open Graph) */\n social: {\n width: 1200,\n height: 630,\n fit: 'cover' as const,\n quality: 85,\n },\n /** Mobile optimized (640px wide, WebP) */\n mobile: {\n width: 640,\n fit: 'scale-down' as const,\n quality: 75,\n format: 'image/webp' as const,\n },\n} as const\n\n/**\n * Type for preset names.\n */\nexport type ImagePresetName = keyof typeof IMAGE_PRESETS\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@cloudwerk/images",
3
+ "version": "0.1.3",
4
+ "description": "Cloudflare Images integration for Cloudwerk",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/squirrelsoft-dev/cloudwerk.git",
8
+ "directory": "packages/images"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "test": "vitest --run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "dependencies": {
26
+ "@cloudwerk/core": "workspace:*"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.4.0",
30
+ "vitest": "^1.0.0",
31
+ "tsup": "^8.0.0"
32
+ },
33
+ "peerDependencies": {
34
+ "typescript": "^5.0.0"
35
+ }
36
+ }