@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,1209 @@
1
+ /**
2
+ * @cloudwerk/images - Type Definitions
3
+ *
4
+ * Core types for Cloudflare Images integration.
5
+ */
6
+ /**
7
+ * Configuration for an image variant (transformation preset).
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const thumbnailVariant: ImageVariant = {
12
+ * width: 100,
13
+ * height: 100,
14
+ * fit: 'cover',
15
+ * quality: 80,
16
+ * }
17
+ * ```
18
+ */
19
+ interface ImageVariant {
20
+ /** Width in pixels */
21
+ width?: number;
22
+ /** Height in pixels */
23
+ height?: number;
24
+ /**
25
+ * How the image should be resized to fit the dimensions.
26
+ *
27
+ * - 'cover': Fill the dimensions, cropping if necessary
28
+ * - 'contain': Fit within dimensions, may have letterboxing
29
+ * - 'scale-down': Only shrink, never enlarge
30
+ * - 'crop': Crop to exact dimensions from center
31
+ * - 'pad': Fit within dimensions, padding if necessary
32
+ */
33
+ fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad';
34
+ /**
35
+ * Blur radius (1-250).
36
+ * Higher values = more blur.
37
+ */
38
+ blur?: number;
39
+ /**
40
+ * Quality for lossy formats (1-100).
41
+ * @default 85
42
+ */
43
+ quality?: number;
44
+ /**
45
+ * Output format override.
46
+ * If not specified, format is negotiated based on Accept header.
47
+ */
48
+ format?: 'webp' | 'avif' | 'json' | 'jpeg' | 'png';
49
+ /**
50
+ * Device pixel ratio (1-3).
51
+ * Multiplies width/height for retina displays.
52
+ */
53
+ dpr?: number;
54
+ /**
55
+ * Gravity for cropping (where to focus when cropping).
56
+ */
57
+ gravity?: 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'face';
58
+ /**
59
+ * Sharpen the image (0-10).
60
+ */
61
+ sharpen?: number;
62
+ /**
63
+ * Brightness adjustment (-1 to 1).
64
+ */
65
+ brightness?: number;
66
+ /**
67
+ * Contrast adjustment (-1 to 1).
68
+ */
69
+ contrast?: number;
70
+ /**
71
+ * Rotate the image in degrees (0-360).
72
+ */
73
+ rotate?: number;
74
+ /**
75
+ * Metadata handling.
76
+ *
77
+ * - 'keep': Preserve all metadata
78
+ * - 'copyright': Keep only copyright info
79
+ * - 'none': Strip all metadata
80
+ */
81
+ metadata?: 'keep' | 'copyright' | 'none';
82
+ }
83
+ /**
84
+ * Environment variable reference for sensitive configuration.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const config: ImageConfig = {
89
+ * accountId: { env: 'CF_ACCOUNT_ID' },
90
+ * apiToken: { env: 'CF_IMAGES_API_TOKEN' },
91
+ * }
92
+ * ```
93
+ */
94
+ interface EnvRef {
95
+ /** Name of the environment variable */
96
+ env: string;
97
+ }
98
+ /**
99
+ * Configuration for defining a Cloudflare Images integration.
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * // app/images/avatars.ts
104
+ * export default defineImage({
105
+ * name: 'avatars',
106
+ * variants: {
107
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
108
+ * profile: { width: 400, height: 400, fit: 'cover' },
109
+ * },
110
+ * })
111
+ * ```
112
+ */
113
+ interface ImageConfig {
114
+ /**
115
+ * Unique name for this image configuration.
116
+ * Defaults to the filename (without extension).
117
+ */
118
+ name?: string;
119
+ /**
120
+ * Cloudflare account ID.
121
+ * Can be a string value or environment variable reference.
122
+ */
123
+ accountId?: string | EnvRef;
124
+ /**
125
+ * Cloudflare Images API token.
126
+ * Can be a string value or environment variable reference.
127
+ */
128
+ apiToken?: string | EnvRef;
129
+ /**
130
+ * Predefined image variants (transformation presets).
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * variants: {
135
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
136
+ * large: { width: 1200, height: 800, fit: 'contain' },
137
+ * blur: { blur: 20, width: 20 },
138
+ * }
139
+ * ```
140
+ */
141
+ variants?: Record<string, ImageVariant>;
142
+ /**
143
+ * Custom domain for image delivery.
144
+ * If not specified, uses the default Cloudflare Images URL.
145
+ *
146
+ * @example 'https://images.example.com'
147
+ */
148
+ deliveryUrl?: string;
149
+ /**
150
+ * Default variant to use when none is specified.
151
+ */
152
+ defaultVariant?: string;
153
+ /**
154
+ * Whether to require signed URLs for this image configuration.
155
+ * @default false
156
+ */
157
+ requireSignedURLs?: boolean;
158
+ }
159
+ /**
160
+ * An image definition created by `defineImage()`.
161
+ *
162
+ * This is the return type of the factory function and contains
163
+ * the processed configuration with a brand marker for type safety.
164
+ */
165
+ interface ImageDefinition {
166
+ /** Internal marker identifying this as an image definition */
167
+ readonly __brand: 'cloudwerk-image';
168
+ /** Image configuration name */
169
+ readonly name: string;
170
+ /** Processed configuration with defaults applied */
171
+ readonly config: ImageConfig;
172
+ /** Variant definitions */
173
+ readonly variants: Record<string, ImageVariant>;
174
+ }
175
+ /**
176
+ * Options for uploading an image.
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const result = await images.avatars.upload(file, {
181
+ * id: 'user-123-avatar',
182
+ * metadata: { userId: '123', uploadedAt: new Date().toISOString() },
183
+ * })
184
+ * ```
185
+ */
186
+ interface UploadOptions {
187
+ /**
188
+ * Custom metadata to attach to the image.
189
+ * Can be used for organization and filtering.
190
+ */
191
+ metadata?: Record<string, string>;
192
+ /**
193
+ * Whether to require signed URLs to access this image.
194
+ * Overrides the configuration-level setting.
195
+ */
196
+ requireSignedURLs?: boolean;
197
+ /**
198
+ * Custom image ID.
199
+ * If not specified, Cloudflare generates a UUID.
200
+ */
201
+ id?: string;
202
+ }
203
+ /**
204
+ * Options for generating a direct upload URL.
205
+ *
206
+ * Direct upload URLs allow clients to upload images directly to Cloudflare
207
+ * without going through your server, reducing bandwidth costs.
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const { uploadUrl, id } = await images.avatars.getDirectUploadUrl({
212
+ * expiry: '1h',
213
+ * metadata: { userId: '123' },
214
+ * })
215
+ *
216
+ * // Return uploadUrl to client for direct upload
217
+ * ```
218
+ */
219
+ interface DirectUploadOptions {
220
+ /**
221
+ * Custom metadata to attach to the uploaded image.
222
+ */
223
+ metadata?: Record<string, string>;
224
+ /**
225
+ * Whether to require signed URLs to access the uploaded image.
226
+ */
227
+ requireSignedURLs?: boolean;
228
+ /**
229
+ * How long the upload URL is valid.
230
+ * Can be a duration string ('1h', '30m') or seconds.
231
+ * @default '30m'
232
+ */
233
+ expiry?: string | number;
234
+ }
235
+ /**
236
+ * Options for listing images.
237
+ */
238
+ interface ListOptions {
239
+ /** Page number (1-based) */
240
+ page?: number;
241
+ /** Number of images per page (1-100) */
242
+ perPage?: number;
243
+ }
244
+ /**
245
+ * Result of an image upload operation.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * const result = await images.avatars.upload(file)
250
+ * console.log(result.id) // 'abc123'
251
+ * console.log(result.variants) // ['thumbnail', 'profile']
252
+ * console.log(result.uploaded) // Date object
253
+ * ```
254
+ */
255
+ interface ImageResult {
256
+ /** Unique image ID */
257
+ id: string;
258
+ /** Original filename (if provided during upload) */
259
+ filename?: string;
260
+ /** When the image was uploaded */
261
+ uploaded: Date;
262
+ /** Available variant names for this image */
263
+ variants: string[];
264
+ /** Custom metadata attached to the image */
265
+ metadata?: Record<string, string>;
266
+ /** Whether the image requires signed URLs */
267
+ requireSignedURLs?: boolean;
268
+ }
269
+ /**
270
+ * Result of generating a direct upload URL.
271
+ */
272
+ interface DirectUploadResult {
273
+ /** One-time upload URL for direct client upload */
274
+ uploadUrl: string;
275
+ /** Image ID that will be assigned to the uploaded image */
276
+ id: string;
277
+ }
278
+ /**
279
+ * Image client interface for interacting with Cloudflare Images.
280
+ *
281
+ * @typeParam T - Variant names type for this image configuration
282
+ */
283
+ interface ImageClientInterface<T extends string = string> {
284
+ /**
285
+ * Upload an image file.
286
+ *
287
+ * @param file - File, Blob, or ReadableStream to upload
288
+ * @param options - Upload options
289
+ * @returns Upload result with image ID and metadata
290
+ */
291
+ upload(file: File | Blob | ReadableStream, options?: UploadOptions): Promise<ImageResult>;
292
+ /**
293
+ * Generate a direct upload URL for client-side uploads.
294
+ *
295
+ * @param options - Direct upload options
296
+ * @returns Upload URL and image ID
297
+ */
298
+ getDirectUploadUrl(options?: DirectUploadOptions): Promise<DirectUploadResult>;
299
+ /**
300
+ * Delete an image.
301
+ *
302
+ * @param imageId - ID of the image to delete
303
+ */
304
+ delete(imageId: string): Promise<void>;
305
+ /**
306
+ * Get image details.
307
+ *
308
+ * @param imageId - ID of the image
309
+ * @returns Image result or null if not found
310
+ */
311
+ get(imageId: string): Promise<ImageResult | null>;
312
+ /**
313
+ * List images with pagination.
314
+ *
315
+ * @param options - Pagination options
316
+ * @returns Array of image results
317
+ */
318
+ list(options?: ListOptions): Promise<ImageResult[]>;
319
+ /**
320
+ * Generate URL for an image with optional variant.
321
+ *
322
+ * @param imageId - ID of the image
323
+ * @param variant - Variant name (optional)
324
+ * @returns Full URL to the image
325
+ */
326
+ url(imageId: string, variant?: T): string;
327
+ }
328
+ /**
329
+ * A scanned image file from the app/images/ directory.
330
+ */
331
+ interface ScannedImage {
332
+ /** Relative path from app/images/ (e.g., 'avatars.ts') */
333
+ relativePath: string;
334
+ /** Absolute filesystem path */
335
+ absolutePath: string;
336
+ /** File name without extension (e.g., 'avatars') */
337
+ name: string;
338
+ /** File extension (e.g., '.ts') */
339
+ extension: string;
340
+ }
341
+ /**
342
+ * Result of scanning the app/images/ directory.
343
+ */
344
+ interface ImageScanResult {
345
+ /** All discovered image files */
346
+ images: ScannedImage[];
347
+ }
348
+ /**
349
+ * A compiled image entry in the manifest.
350
+ */
351
+ interface ImageEntry {
352
+ /** Image name derived from filename (e.g., 'avatars') */
353
+ name: string;
354
+ /** Binding name for wrangler.toml (e.g., 'AVATARS_IMAGES') */
355
+ bindingName: string;
356
+ /** Relative path to the image definition file */
357
+ filePath: string;
358
+ /** Absolute path to the image definition file */
359
+ absolutePath: string;
360
+ /** Variant definitions from the image config */
361
+ variants: Record<string, ImageVariant>;
362
+ /** Full image configuration */
363
+ config: ImageConfig;
364
+ }
365
+ /**
366
+ * Validation error for an image definition.
367
+ */
368
+ interface ImageValidationError {
369
+ /** Image file path */
370
+ file: string;
371
+ /** Error message */
372
+ message: string;
373
+ /** Error code for programmatic handling */
374
+ code: 'INVALID_CONFIG' | 'DUPLICATE_NAME' | 'INVALID_NAME' | 'MISSING_REQUIRED';
375
+ }
376
+ /**
377
+ * Validation warning for an image definition.
378
+ */
379
+ interface ImageValidationWarning {
380
+ /** Image file path */
381
+ file: string;
382
+ /** Warning message */
383
+ message: string;
384
+ /** Warning code */
385
+ code: 'NO_VARIANTS' | 'MISSING_ACCOUNT_ID' | 'MISSING_API_TOKEN';
386
+ }
387
+ /**
388
+ * Complete image manifest generated during build.
389
+ */
390
+ interface ImageManifest {
391
+ /** All compiled image entries */
392
+ images: ImageEntry[];
393
+ /** Validation errors (image won't be registered) */
394
+ errors: ImageValidationError[];
395
+ /** Validation warnings (image will be registered with warning) */
396
+ warnings: ImageValidationWarning[];
397
+ /** When the manifest was generated */
398
+ generatedAt: Date;
399
+ /** Root directory of the app */
400
+ rootDir: string;
401
+ }
402
+
403
+ /**
404
+ * @cloudwerk/images - Error Classes
405
+ *
406
+ * Custom error types for Cloudflare Images operations.
407
+ */
408
+ /**
409
+ * Base error class for all image-related errors.
410
+ */
411
+ declare class ImageError extends Error {
412
+ constructor(message: string);
413
+ }
414
+ /**
415
+ * Error thrown when image configuration is invalid.
416
+ */
417
+ declare class ImageConfigError extends ImageError {
418
+ readonly field: string;
419
+ constructor(message: string, field: string);
420
+ }
421
+ /**
422
+ * Error thrown when a required environment variable is missing.
423
+ */
424
+ declare class ImageEnvError extends ImageError {
425
+ readonly envVar: string;
426
+ constructor(envVar: string);
427
+ }
428
+ /**
429
+ * Error thrown when an image is not found.
430
+ */
431
+ declare class ImageNotFoundError extends ImageError {
432
+ readonly imageId: string;
433
+ constructor(imageId: string);
434
+ }
435
+ /**
436
+ * Error thrown when an image upload fails.
437
+ */
438
+ declare class ImageUploadError extends ImageError {
439
+ readonly statusCode?: number;
440
+ readonly details?: string;
441
+ constructor(message: string, statusCode?: number, details?: string);
442
+ }
443
+ /**
444
+ * Error thrown when an image operation fails due to API errors.
445
+ */
446
+ declare class ImageApiError extends ImageError {
447
+ readonly statusCode: number;
448
+ readonly errors: Array<{
449
+ code: number;
450
+ message: string;
451
+ }>;
452
+ constructor(statusCode: number, errors: Array<{
453
+ code: number;
454
+ message: string;
455
+ }>);
456
+ }
457
+ /**
458
+ * Error thrown when an invalid variant is requested.
459
+ */
460
+ declare class ImageVariantError extends ImageError {
461
+ readonly variant: string;
462
+ readonly availableVariants: string[];
463
+ constructor(variant: string, availableVariants: string[]);
464
+ }
465
+ /**
466
+ * Error thrown when image binding is accessed outside request context.
467
+ */
468
+ declare class ImageContextError extends ImageError {
469
+ constructor();
470
+ }
471
+ /**
472
+ * Error thrown when an image configuration is not found.
473
+ */
474
+ declare class ImageBindingNotFoundError extends ImageError {
475
+ readonly name: string;
476
+ readonly availableImages: string[];
477
+ constructor(name: string, availableImages: string[]);
478
+ }
479
+
480
+ /**
481
+ * @cloudwerk/images - defineImage()
482
+ *
483
+ * Factory function for creating image definitions.
484
+ */
485
+
486
+ /**
487
+ * Define a Cloudflare Images configuration.
488
+ *
489
+ * This function creates an image definition that will be automatically
490
+ * discovered and registered by Cloudwerk during build.
491
+ *
492
+ * @param config - Image configuration
493
+ * @returns Image definition
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * // app/images/avatars.ts
498
+ * import { defineImage } from '@cloudwerk/images'
499
+ *
500
+ * export default defineImage({
501
+ * variants: {
502
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
503
+ * profile: { width: 400, height: 400, fit: 'cover' },
504
+ * large: { width: 1200, height: 1200, fit: 'contain' },
505
+ * },
506
+ * })
507
+ * ```
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * // app/images/products.ts - with custom delivery domain
512
+ * import { defineImage } from '@cloudwerk/images'
513
+ *
514
+ * export default defineImage({
515
+ * deliveryUrl: 'https://images.mystore.com',
516
+ * variants: {
517
+ * card: { width: 300, height: 300, fit: 'cover' },
518
+ * detail: { width: 800, height: 600, fit: 'contain' },
519
+ * zoom: { width: 1600, height: 1200, fit: 'contain' },
520
+ * },
521
+ * defaultVariant: 'card',
522
+ * })
523
+ * ```
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * // app/images/secure.ts - with signed URLs required
528
+ * import { defineImage } from '@cloudwerk/images'
529
+ *
530
+ * export default defineImage({
531
+ * requireSignedURLs: true,
532
+ * variants: {
533
+ * preview: { width: 200, height: 200, fit: 'cover', blur: 20 },
534
+ * full: { width: 1920, height: 1080, fit: 'contain' },
535
+ * },
536
+ * })
537
+ * ```
538
+ */
539
+ declare function defineImage(config?: ImageConfig): ImageDefinition;
540
+ /**
541
+ * Check if a value is an image definition created by defineImage().
542
+ *
543
+ * @param value - Value to check
544
+ * @returns true if value is an ImageDefinition
545
+ */
546
+ declare function isImageDefinition(value: unknown): value is ImageDefinition;
547
+
548
+ /**
549
+ * @cloudwerk/images - Image Client
550
+ *
551
+ * Client for interacting with Cloudflare Images API.
552
+ */
553
+
554
+ /**
555
+ * Client for interacting with Cloudflare Images.
556
+ *
557
+ * @example
558
+ * ```typescript
559
+ * const client = new ImageClient('account-id', 'api-token', {
560
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
561
+ * })
562
+ *
563
+ * // Upload an image
564
+ * const result = await client.upload(file)
565
+ *
566
+ * // Get URL with variant
567
+ * const url = client.url(result.id, 'thumbnail')
568
+ * ```
569
+ */
570
+ declare class ImageClient<T extends string = string> implements ImageClientInterface<T> {
571
+ private readonly accountId;
572
+ private readonly apiToken;
573
+ private readonly deliveryUrl?;
574
+ private readonly variants;
575
+ private readonly variantNames;
576
+ constructor(accountId: string, apiToken: string, variants?: Record<string, ImageVariant>, deliveryUrl?: string);
577
+ /**
578
+ * Make an authenticated API request.
579
+ */
580
+ private fetch;
581
+ /**
582
+ * Upload an image file.
583
+ */
584
+ upload(file: File | Blob | ReadableStream, options?: UploadOptions): Promise<ImageResult>;
585
+ /**
586
+ * Generate a direct upload URL for client-side uploads.
587
+ */
588
+ getDirectUploadUrl(options?: DirectUploadOptions): Promise<DirectUploadResult>;
589
+ /**
590
+ * Delete an image.
591
+ */
592
+ delete(imageId: string): Promise<void>;
593
+ /**
594
+ * Get image details.
595
+ */
596
+ get(imageId: string): Promise<ImageResult | null>;
597
+ /**
598
+ * List images with pagination.
599
+ */
600
+ list(options?: ListOptions): Promise<ImageResult[]>;
601
+ /**
602
+ * Generate URL for an image with optional variant.
603
+ */
604
+ url(imageId: string, variant?: T): string;
605
+ }
606
+ /**
607
+ * Create an image client from configuration.
608
+ *
609
+ * @param accountId - Cloudflare account ID
610
+ * @param apiToken - Cloudflare Images API token
611
+ * @param variants - Variant definitions
612
+ * @param deliveryUrl - Custom delivery URL (optional)
613
+ * @returns Image client instance
614
+ */
615
+ declare function createImageClient<T extends string = string>(accountId: string, apiToken: string, variants?: Record<T, ImageVariant>, deliveryUrl?: string): ImageClient<T>;
616
+
617
+ /**
618
+ * @cloudwerk/images - Cloudflare API Types
619
+ *
620
+ * Type definitions for Cloudflare Images API responses.
621
+ * Based on Cloudflare API documentation.
622
+ */
623
+ /**
624
+ * Standard Cloudflare API response wrapper.
625
+ */
626
+ interface CloudflareApiResponse<T> {
627
+ result: T;
628
+ success: boolean;
629
+ errors: CloudflareApiError[];
630
+ messages: CloudflareApiMessage[];
631
+ }
632
+ /**
633
+ * Cloudflare API error.
634
+ */
635
+ interface CloudflareApiError {
636
+ code: number;
637
+ message: string;
638
+ }
639
+ /**
640
+ * Cloudflare API message.
641
+ */
642
+ interface CloudflareApiMessage {
643
+ code: number;
644
+ message: string;
645
+ }
646
+ /**
647
+ * Cloudflare Image object from API.
648
+ */
649
+ interface CloudflareImage {
650
+ /** Unique image identifier */
651
+ id: string;
652
+ /** Original filename */
653
+ filename: string;
654
+ /** Upload timestamp (ISO 8601) */
655
+ uploaded: string;
656
+ /** Whether signed URLs are required */
657
+ requireSignedURLs: boolean;
658
+ /** Available variant URLs */
659
+ variants: string[];
660
+ /** Custom metadata */
661
+ meta?: Record<string, string>;
662
+ }
663
+ /**
664
+ * Response from uploading an image.
665
+ */
666
+ interface CloudflareUploadResponse {
667
+ id: string;
668
+ filename: string;
669
+ uploaded: string;
670
+ requireSignedURLs: boolean;
671
+ variants: string[];
672
+ meta?: Record<string, string>;
673
+ }
674
+ /**
675
+ * Response from creating a direct upload URL.
676
+ */
677
+ interface CloudflareDirectUploadResponse {
678
+ id: string;
679
+ uploadURL: string;
680
+ }
681
+ /**
682
+ * Response from listing images.
683
+ */
684
+ interface CloudflareListResponse {
685
+ images: CloudflareImage[];
686
+ continuation_token?: string;
687
+ }
688
+ /**
689
+ * Response from getting image details.
690
+ */
691
+ interface CloudflareImageDetailsResponse {
692
+ id: string;
693
+ filename: string;
694
+ uploaded: string;
695
+ requireSignedURLs: boolean;
696
+ variants: string[];
697
+ meta?: Record<string, string>;
698
+ }
699
+ /**
700
+ * Request body for creating a direct upload URL.
701
+ */
702
+ interface DirectUploadRequest {
703
+ /** Whether to require signed URLs */
704
+ requireSignedURLs?: boolean;
705
+ /** Custom metadata */
706
+ metadata?: Record<string, string>;
707
+ /** Expiry timestamp (ISO 8601 or Unix timestamp) */
708
+ expiry?: string;
709
+ }
710
+ /**
711
+ * Cloudflare Images variant configuration (API format).
712
+ */
713
+ interface CloudflareVariant {
714
+ id: string;
715
+ options: {
716
+ fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad';
717
+ width?: number;
718
+ height?: number;
719
+ metadata?: 'keep' | 'copyright' | 'none';
720
+ };
721
+ neverRequireSignedURLs?: boolean;
722
+ }
723
+ /**
724
+ * Cloudflare Images usage statistics.
725
+ */
726
+ interface CloudflareImagesStats {
727
+ count: {
728
+ current: number;
729
+ allowed: number;
730
+ };
731
+ }
732
+ /**
733
+ * Cloudflare Images API endpoints.
734
+ */
735
+ declare const CLOUDFLARE_IMAGES_API: {
736
+ /** Base URL for Cloudflare API v4 */
737
+ readonly BASE: "https://api.cloudflare.com/client/v4";
738
+ /**
739
+ * Get the images endpoint for an account.
740
+ */
741
+ readonly images: (accountId: string) => string;
742
+ /**
743
+ * Get the direct upload endpoint.
744
+ */
745
+ readonly directUpload: (accountId: string) => string;
746
+ /**
747
+ * Get a specific image endpoint.
748
+ */
749
+ readonly image: (accountId: string, imageId: string) => string;
750
+ /**
751
+ * Get the default delivery URL format.
752
+ */
753
+ readonly deliveryUrl: (accountId: string, imageId: string, variant: string) => string;
754
+ };
755
+
756
+ /**
757
+ * @cloudwerk/images - Image Transformer
758
+ *
759
+ * Route handler helper for Cloudflare's Image Resizing service.
760
+ * Creates a handler that transforms images via `fetch(url, { cf: { image: {...} } })`.
761
+ *
762
+ * @example
763
+ * ```typescript
764
+ * // app/cdn/images/[...path]/route.ts
765
+ * import { createImageTransformer } from '@cloudwerk/images'
766
+ *
767
+ * export const GET = createImageTransformer({
768
+ * allowedOrigins: ['https://images.mysite.com'],
769
+ * presets: {
770
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
771
+ * hero: { width: 1920, height: 1080, fit: 'cover' },
772
+ * },
773
+ * })
774
+ * ```
775
+ */
776
+
777
+ /**
778
+ * Configuration for the image transformer route handler.
779
+ */
780
+ interface TransformerConfig {
781
+ /**
782
+ * Allowed origin URLs for image sources.
783
+ * If not specified, any origin is allowed (not recommended for production).
784
+ *
785
+ * @example ['https://images.mysite.com', 'https://cdn.example.com']
786
+ */
787
+ allowedOrigins?: string[];
788
+ /**
789
+ * Named transformation presets.
790
+ * Can be applied via URL parameter (e.g., ?preset=thumbnail).
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * presets: {
795
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
796
+ * hero: { width: 1920, height: 1080, fit: 'cover' },
797
+ * }
798
+ * ```
799
+ */
800
+ presets?: Record<string, TransformPreset>;
801
+ /**
802
+ * Default transformation options applied to all requests.
803
+ */
804
+ defaults?: TransformPreset;
805
+ /**
806
+ * Maximum allowed width. Requests exceeding this will be clamped.
807
+ * @default 4096
808
+ */
809
+ maxWidth?: number;
810
+ /**
811
+ * Maximum allowed height. Requests exceeding this will be clamped.
812
+ * @default 4096
813
+ */
814
+ maxHeight?: number;
815
+ /**
816
+ * Cache control header for transformed images.
817
+ * @default 'public, max-age=31536000' (1 year)
818
+ */
819
+ cacheControl?: string;
820
+ /**
821
+ * Whether to allow arbitrary width/height from query parameters.
822
+ * If false, only preset transformations are allowed.
823
+ * @default true
824
+ */
825
+ allowArbitrary?: boolean;
826
+ /**
827
+ * Custom validation function for source URLs.
828
+ * Return true to allow the URL, false to reject.
829
+ */
830
+ validateSource?: (url: URL) => boolean | Promise<boolean>;
831
+ }
832
+ /**
833
+ * Transformation preset options.
834
+ * Subset of Cloudflare Image Resizing options.
835
+ */
836
+ interface TransformPreset {
837
+ /** Width in pixels */
838
+ width?: number;
839
+ /** Height in pixels */
840
+ height?: number;
841
+ /** Fit mode */
842
+ fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad';
843
+ /** Output format */
844
+ format?: 'webp' | 'avif' | 'json' | 'jpeg' | 'png' | 'auto';
845
+ /** Quality (1-100) */
846
+ quality?: number;
847
+ /** Device pixel ratio (1-3) */
848
+ dpr?: number;
849
+ /** Gravity for cropping */
850
+ gravity?: 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'face';
851
+ /** Sharpen (0-10) */
852
+ sharpen?: number;
853
+ /** Blur (1-250) */
854
+ blur?: number;
855
+ /** Brightness (-1 to 1) */
856
+ brightness?: number;
857
+ /** Contrast (-1 to 1) */
858
+ contrast?: number;
859
+ /** Rotation (0, 90, 180, 270) */
860
+ rotate?: 0 | 90 | 180 | 270;
861
+ /** Metadata handling */
862
+ metadata?: 'keep' | 'copyright' | 'none';
863
+ /** Background color for padding */
864
+ background?: string;
865
+ }
866
+ /**
867
+ * Error thrown when image transformation fails.
868
+ */
869
+ declare class ImageTransformError extends Error {
870
+ readonly status: number;
871
+ readonly code: string;
872
+ constructor(message: string, status: number, code: string);
873
+ }
874
+ /**
875
+ * Create an image transformer route handler.
876
+ *
877
+ * This creates a GET handler that:
878
+ * 1. Extracts the source image URL from the request path
879
+ * 2. Parses transformation options from query parameters
880
+ * 3. Fetches the source image with Cloudflare Image Resizing options
881
+ * 4. Returns the transformed image
882
+ *
883
+ * @param config - Transformer configuration
884
+ * @returns Route handler function
885
+ *
886
+ * @example
887
+ * ```typescript
888
+ * // app/cdn/images/[...path]/route.ts
889
+ * import { createImageTransformer } from '@cloudwerk/images'
890
+ *
891
+ * export const GET = createImageTransformer({
892
+ * allowedOrigins: ['https://images.mysite.com'],
893
+ * presets: {
894
+ * thumbnail: { width: 100, height: 100, fit: 'cover' },
895
+ * hero: { width: 1920, height: 1080, fit: 'cover' },
896
+ * },
897
+ * defaults: {
898
+ * format: 'auto',
899
+ * quality: 85,
900
+ * },
901
+ * })
902
+ *
903
+ * // Usage:
904
+ * // /cdn/images/https://images.mysite.com/photo.jpg?preset=thumbnail
905
+ * // /cdn/images/https://images.mysite.com/photo.jpg?w=800&h=600&fit=cover
906
+ * ```
907
+ */
908
+ declare function createImageTransformer(config?: TransformerConfig): (request: Request, context: {
909
+ params: Record<string, string | string[]>;
910
+ }) => Promise<Response>;
911
+ /**
912
+ * Convert an ImageVariant to a TransformPreset.
913
+ * Useful when you want to reuse variants defined for Hosted Images.
914
+ */
915
+ declare function variantToPreset(variant: ImageVariant): TransformPreset;
916
+
917
+ /**
918
+ * @cloudwerk/images - Cloudflare IMAGES Binding Types
919
+ *
920
+ * Type definitions for the Cloudflare IMAGES binding used in Workers.
921
+ * This binding provides on-the-fly image transformation without uploading
922
+ * to Cloudflare Images.
923
+ *
924
+ * @example
925
+ * ```typescript
926
+ * // wrangler.toml
927
+ * [images]
928
+ * binding = "MY_IMAGES"
929
+ * ```
930
+ *
931
+ * @example
932
+ * ```typescript
933
+ * import { getBinding } from '@cloudwerk/core/bindings'
934
+ * import type { CloudflareImagesBinding } from '@cloudwerk/images'
935
+ *
936
+ * const IMAGES = getBinding<CloudflareImagesBinding>('MY_IMAGES')
937
+ *
938
+ * // Transform an image
939
+ * const result = await IMAGES
940
+ * .input(imageStream)
941
+ * .transform({ width: 800 })
942
+ * .output({ format: 'image/webp' })
943
+ * .response()
944
+ * ```
945
+ */
946
+ /**
947
+ * Image information returned by the IMAGES binding.
948
+ */
949
+ interface CloudflareImageInfo {
950
+ /** Width of the image in pixels */
951
+ width: number;
952
+ /** Height of the image in pixels */
953
+ height: number;
954
+ /** Format of the image (e.g., 'image/jpeg', 'image/png') */
955
+ format: string;
956
+ /** File size in bytes */
957
+ fileSize: number;
958
+ }
959
+ /**
960
+ * Transform options for the IMAGES binding.
961
+ */
962
+ interface CloudflareImageTransformOptions {
963
+ /** Width in pixels */
964
+ width?: number;
965
+ /** Height in pixels */
966
+ height?: number;
967
+ /** Fit mode for resizing */
968
+ fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
969
+ /** Gravity for cropping */
970
+ gravity?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'face';
971
+ /** Quality for lossy formats (1-100) */
972
+ quality?: number;
973
+ /** Device pixel ratio (1-3) */
974
+ dpr?: number;
975
+ /** Rotation in degrees (0, 90, 180, 270) */
976
+ rotate?: 0 | 90 | 180 | 270;
977
+ /** Sharpen amount (0-10) */
978
+ sharpen?: number;
979
+ /** Blur radius (1-250) */
980
+ blur?: number;
981
+ /** Brightness adjustment (-1 to 1) */
982
+ brightness?: number;
983
+ /** Contrast adjustment (-1 to 1) */
984
+ contrast?: number;
985
+ /** Background color for padding (hex or rgb) */
986
+ background?: string;
987
+ /** Border configuration */
988
+ border?: {
989
+ color: string;
990
+ width: number;
991
+ };
992
+ /** Trim whitespace from edges */
993
+ trim?: {
994
+ top?: number;
995
+ right?: number;
996
+ bottom?: number;
997
+ left?: number;
998
+ };
999
+ }
1000
+ /**
1001
+ * Output options for the IMAGES binding.
1002
+ */
1003
+ interface CloudflareImageOutputOptions {
1004
+ /** Output format */
1005
+ format?: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif' | 'image/gif';
1006
+ /** Quality for lossy formats (1-100) */
1007
+ quality?: number;
1008
+ /** Metadata handling */
1009
+ metadata?: 'keep' | 'copyright' | 'none';
1010
+ }
1011
+ /**
1012
+ * Result object returned after awaiting the transform pipeline.
1013
+ * Use `.response()` to get the transformed image.
1014
+ */
1015
+ interface CloudflareImagesTransformResult {
1016
+ /**
1017
+ * Get the transformed image as a Response.
1018
+ * @returns Response containing the transformed image
1019
+ */
1020
+ response(): Response;
1021
+ }
1022
+ /**
1023
+ * Transform builder interface for the IMAGES binding.
1024
+ * Provides a fluent API for chaining transformations.
1025
+ *
1026
+ * @example
1027
+ * ```typescript
1028
+ * // The entire chain must be awaited, then call .response()
1029
+ * const result = await IMAGES
1030
+ * .input(stream)
1031
+ * .transform({ rotate: 90 })
1032
+ * .output({ format: 'image/webp' })
1033
+ *
1034
+ * return result.response()
1035
+ * ```
1036
+ */
1037
+ interface CloudflareImagesTransformBuilder {
1038
+ /**
1039
+ * Apply transformation options to the image.
1040
+ * Can be chained multiple times.
1041
+ * @param options - Transformation options
1042
+ * @returns The builder for chaining
1043
+ */
1044
+ transform(options: CloudflareImageTransformOptions): CloudflareImagesTransformBuilder;
1045
+ /**
1046
+ * Set output format and options.
1047
+ * @param options - Output options
1048
+ * @returns Promise that resolves to a result object with .response() method
1049
+ */
1050
+ output(options: CloudflareImageOutputOptions): Promise<CloudflareImagesTransformResult>;
1051
+ /**
1052
+ * Draw another image over this one (watermarking/compositing).
1053
+ * @param image - Another transform builder or ReadableStream
1054
+ * @param position - Position options (top, left, bottom, right, opacity, repeat)
1055
+ * @returns The builder for chaining
1056
+ */
1057
+ draw(image: CloudflareImagesTransformBuilder | ReadableStream<Uint8Array>, position?: {
1058
+ top?: number;
1059
+ left?: number;
1060
+ bottom?: number;
1061
+ right?: number;
1062
+ opacity?: number;
1063
+ repeat?: boolean | 'x' | 'y';
1064
+ }): CloudflareImagesTransformBuilder;
1065
+ }
1066
+ /**
1067
+ * Cloudflare IMAGES binding interface.
1068
+ *
1069
+ * This binding provides access to Cloudflare's image transformation pipeline
1070
+ * for on-the-fly image processing without uploading to Cloudflare Images.
1071
+ *
1072
+ * @example
1073
+ * ```typescript
1074
+ * import { getBinding } from '@cloudwerk/core/bindings'
1075
+ * import type { CloudflareImagesBinding } from '@cloudwerk/images'
1076
+ *
1077
+ * export async function GET(request: Request) {
1078
+ * const IMAGES = getBinding<CloudflareImagesBinding>('MY_IMAGES')
1079
+ *
1080
+ * // Transform an image from a stream
1081
+ * // Note: await the chain, then call .response()
1082
+ * const result = await IMAGES
1083
+ * .input(imageStream)
1084
+ * .transform({ width: 800, rotate: 90 })
1085
+ * .output({ format: 'image/webp' })
1086
+ *
1087
+ * return result.response()
1088
+ * }
1089
+ * ```
1090
+ *
1091
+ * @example
1092
+ * ```typescript
1093
+ * // Get image information
1094
+ * const info = await IMAGES.info(imageStream)
1095
+ * console.log(`Image size: ${info.width}x${info.height}`)
1096
+ * ```
1097
+ */
1098
+ interface CloudflareImagesBinding {
1099
+ /**
1100
+ * Start a transformation pipeline with an input image.
1101
+ * @param source - Image source as Blob, ArrayBuffer, or ReadableStream
1102
+ * @returns Transform builder for chaining operations
1103
+ */
1104
+ input(source: Blob | ArrayBuffer | ReadableStream<Uint8Array>): CloudflareImagesTransformBuilder;
1105
+ /**
1106
+ * Get information about an image without transforming it.
1107
+ * @param source - Image source as Blob, ArrayBuffer, or ReadableStream
1108
+ * @returns Image information including dimensions and format
1109
+ */
1110
+ info(source: Blob | ArrayBuffer | ReadableStream<Uint8Array>): Promise<CloudflareImageInfo>;
1111
+ }
1112
+ /**
1113
+ * Fit modes for image resizing.
1114
+ *
1115
+ * - `scale-down`: Shrink to fit within dimensions, never enlarge
1116
+ * - `contain`: Fit within dimensions, may add letterboxing
1117
+ * - `cover`: Fill dimensions, cropping if necessary
1118
+ * - `crop`: Crop to exact dimensions from center
1119
+ * - `pad`: Fit within dimensions, padding if necessary
1120
+ */
1121
+ type ImageFitMode = 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
1122
+ /**
1123
+ * Gravity options for cropping.
1124
+ *
1125
+ * - `auto`: Automatic based on image content
1126
+ * - `left`, `right`, `top`, `bottom`: Align to edge
1127
+ * - `center`: Center crop
1128
+ * - `face`: Focus on detected faces
1129
+ */
1130
+ type ImageGravity = 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'face';
1131
+ /**
1132
+ * Supported output formats.
1133
+ */
1134
+ type ImageOutputFormat = 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif' | 'image/gif';
1135
+ /**
1136
+ * Metadata handling options.
1137
+ *
1138
+ * - `keep`: Preserve all metadata
1139
+ * - `copyright`: Keep only copyright information
1140
+ * - `none`: Strip all metadata
1141
+ */
1142
+ type ImageMetadataHandling = 'keep' | 'copyright' | 'none';
1143
+ /**
1144
+ * Preset transform configurations for common use cases.
1145
+ */
1146
+ interface ImageTransformPreset {
1147
+ /** Width in pixels */
1148
+ width?: number;
1149
+ /** Height in pixels */
1150
+ height?: number;
1151
+ /** Fit mode */
1152
+ fit?: ImageFitMode;
1153
+ /** Quality (1-100) */
1154
+ quality?: number;
1155
+ /** Output format */
1156
+ format?: ImageOutputFormat;
1157
+ }
1158
+ /**
1159
+ * Common presets for image transformations.
1160
+ */
1161
+ declare const IMAGE_PRESETS: {
1162
+ /** Small thumbnail (100x100 cover) */
1163
+ readonly thumbnail: {
1164
+ readonly width: 100;
1165
+ readonly height: 100;
1166
+ readonly fit: "cover";
1167
+ readonly quality: 80;
1168
+ };
1169
+ /** Medium preview (400x400 contain) */
1170
+ readonly preview: {
1171
+ readonly width: 400;
1172
+ readonly height: 400;
1173
+ readonly fit: "contain";
1174
+ readonly quality: 85;
1175
+ };
1176
+ /** Large display (1200px wide) */
1177
+ readonly large: {
1178
+ readonly width: 1200;
1179
+ readonly fit: "scale-down";
1180
+ readonly quality: 90;
1181
+ };
1182
+ /** Hero banner (1920x1080 cover) */
1183
+ readonly hero: {
1184
+ readonly width: 1920;
1185
+ readonly height: 1080;
1186
+ readonly fit: "cover";
1187
+ readonly quality: 85;
1188
+ };
1189
+ /** Social share (1200x630 for Open Graph) */
1190
+ readonly social: {
1191
+ readonly width: 1200;
1192
+ readonly height: 630;
1193
+ readonly fit: "cover";
1194
+ readonly quality: 85;
1195
+ };
1196
+ /** Mobile optimized (640px wide, WebP) */
1197
+ readonly mobile: {
1198
+ readonly width: 640;
1199
+ readonly fit: "scale-down";
1200
+ readonly quality: 75;
1201
+ readonly format: "image/webp";
1202
+ };
1203
+ };
1204
+ /**
1205
+ * Type for preset names.
1206
+ */
1207
+ type ImagePresetName = keyof typeof IMAGE_PRESETS;
1208
+
1209
+ export { CLOUDFLARE_IMAGES_API, type CloudflareApiError, type CloudflareApiMessage, type CloudflareApiResponse, type CloudflareDirectUploadResponse, type CloudflareImage, type CloudflareImageDetailsResponse, type CloudflareImageInfo, type CloudflareImageOutputOptions, type CloudflareImageTransformOptions, type CloudflareImagesBinding, type CloudflareImagesStats, type CloudflareImagesTransformBuilder, type CloudflareImagesTransformResult, type CloudflareListResponse, type CloudflareUploadResponse, type CloudflareVariant, type DirectUploadOptions, type DirectUploadRequest, type DirectUploadResult, type EnvRef, IMAGE_PRESETS, ImageApiError, ImageBindingNotFoundError, ImageClient, type ImageClientInterface, type ImageConfig, ImageConfigError, ImageContextError, type ImageDefinition, type ImageEntry, ImageEnvError, ImageError, type ImageFitMode, type ImageGravity, type ImageManifest, type ImageMetadataHandling, ImageNotFoundError, type ImageOutputFormat, type ImagePresetName, type ImageResult, type ImageScanResult, ImageTransformError, type ImageTransformPreset, ImageUploadError, type ImageValidationError, type ImageValidationWarning, type ImageVariant, ImageVariantError, type ListOptions, type ScannedImage, type TransformPreset, type TransformerConfig, type UploadOptions, createImageClient, createImageTransformer, defineImage, isImageDefinition, variantToPreset };