@flightdev/image 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/adapters/cloudinary.d.ts +46 -0
- package/dist/adapters/cloudinary.js +160 -0
- package/dist/adapters/cloudinary.js.map +1 -0
- package/dist/adapters/imgix.d.ts +39 -0
- package/dist/adapters/imgix.js +152 -0
- package/dist/adapters/imgix.js.map +1 -0
- package/dist/adapters/sharp.d.ts +37 -0
- package/dist/adapters/sharp.js +165 -0
- package/dist/adapters/sharp.js.map +1 -0
- package/dist/adapters/squoosh.d.ts +36 -0
- package/dist/adapters/squoosh.js +141 -0
- package/dist/adapters/squoosh.js.map +1 -0
- package/dist/components/react.d.ts +66 -0
- package/dist/components/react.js +129 -0
- package/dist/components/react.js.map +1 -0
- package/dist/components/solid.d.ts +44 -0
- package/dist/components/solid.js +101 -0
- package/dist/components/solid.js.map +1 -0
- package/dist/components/svelte.d.ts +70 -0
- package/dist/components/svelte.js +89 -0
- package/dist/components/svelte.js.map +1 -0
- package/dist/components/vue.d.ts +133 -0
- package/dist/components/vue.js +109 -0
- package/dist/components/vue.js.map +1 -0
- package/dist/index.d.ts +210 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/package.json +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Flight Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @flight-framework/image
|
|
2
|
+
|
|
3
|
+
Image optimization package for Flight Framework with Sharp, Squoosh, and CDN adapters.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @flight-framework/image
|
|
9
|
+
|
|
10
|
+
# Install your preferred processor:
|
|
11
|
+
npm install sharp # For Node.js (fastest)
|
|
12
|
+
# OR use CDN adapters (no additional install needed)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createImage } from '@flight-framework/image';
|
|
19
|
+
import { sharp } from '@flight-framework/image/sharp';
|
|
20
|
+
|
|
21
|
+
const image = createImage(sharp());
|
|
22
|
+
|
|
23
|
+
// Optimize an image
|
|
24
|
+
const result = await image.optimize('./hero.jpg', {
|
|
25
|
+
width: 800,
|
|
26
|
+
format: 'webp',
|
|
27
|
+
quality: 80,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Generate blur placeholder
|
|
31
|
+
const blur = await image.blur('./hero.jpg');
|
|
32
|
+
|
|
33
|
+
// Generate responsive variants
|
|
34
|
+
const responsive = await image.responsive('./hero.jpg', {
|
|
35
|
+
widths: [640, 768, 1024, 1280],
|
|
36
|
+
formats: ['webp', 'avif'],
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Adapters
|
|
41
|
+
|
|
42
|
+
### Sharp (Node.js)
|
|
43
|
+
|
|
44
|
+
Fastest option for Node.js environments.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { sharp } from '@flight-framework/image/sharp';
|
|
48
|
+
|
|
49
|
+
const adapter = sharp({
|
|
50
|
+
cache: true, // Enable internal cache
|
|
51
|
+
concurrency: 4, // Parallel processing
|
|
52
|
+
simd: true, // SIMD optimization
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Squoosh (Edge/WASM)
|
|
57
|
+
|
|
58
|
+
Works on Edge runtimes (Cloudflare Workers, Vercel Edge).
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { squoosh } from '@flight-framework/image/squoosh';
|
|
62
|
+
|
|
63
|
+
const adapter = squoosh();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Cloudinary (CDN)
|
|
67
|
+
|
|
68
|
+
CDN-based optimization, no local processing.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { cloudinary } from '@flight-framework/image/cloudinary';
|
|
72
|
+
|
|
73
|
+
const adapter = cloudinary({
|
|
74
|
+
cloudName: 'my-cloud',
|
|
75
|
+
apiKey: process.env.CLOUDINARY_API_KEY,
|
|
76
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET,
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Imgix (CDN)
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { imgix } from '@flight-framework/image/imgix';
|
|
84
|
+
|
|
85
|
+
const adapter = imgix({
|
|
86
|
+
domain: 'my-source.imgix.net',
|
|
87
|
+
secureUrlToken: process.env.IMGIX_TOKEN,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## UI Components
|
|
92
|
+
|
|
93
|
+
### React
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { Image } from '@flight-framework/image/react';
|
|
97
|
+
|
|
98
|
+
<Image
|
|
99
|
+
src="/hero.jpg"
|
|
100
|
+
alt="Hero image"
|
|
101
|
+
width={800}
|
|
102
|
+
height={600}
|
|
103
|
+
priority // Preload for LCP
|
|
104
|
+
placeholder="blur" // Show blur while loading
|
|
105
|
+
blurDataUrl={blur} // From image.blur()
|
|
106
|
+
/>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Vue
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<script setup>
|
|
113
|
+
import { FlightImage } from '@flight-framework/image/vue';
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<FlightImage
|
|
118
|
+
src="/hero.jpg"
|
|
119
|
+
alt="Hero"
|
|
120
|
+
:width="800"
|
|
121
|
+
:height="600"
|
|
122
|
+
priority
|
|
123
|
+
placeholder="blur"
|
|
124
|
+
/>
|
|
125
|
+
</template>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Svelte
|
|
129
|
+
|
|
130
|
+
```svelte
|
|
131
|
+
<script>
|
|
132
|
+
import { createImageProps, lazyImage } from '@flight-framework/image/svelte';
|
|
133
|
+
|
|
134
|
+
const props = createImageProps({
|
|
135
|
+
src: '/hero.jpg',
|
|
136
|
+
alt: 'Hero',
|
|
137
|
+
width: 800,
|
|
138
|
+
height: 600,
|
|
139
|
+
});
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<img {...props} use:lazyImage={blurDataUrl} />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Solid
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { Image } from '@flight-framework/image/solid';
|
|
149
|
+
|
|
150
|
+
<Image
|
|
151
|
+
src="/hero.jpg"
|
|
152
|
+
alt="Hero"
|
|
153
|
+
width={800}
|
|
154
|
+
height={600}
|
|
155
|
+
priority
|
|
156
|
+
placeholder="blur"
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## API Reference
|
|
161
|
+
|
|
162
|
+
### ImageOptimizeOptions
|
|
163
|
+
|
|
164
|
+
| Option | Type | Default | Description |
|
|
165
|
+
|--------|------|---------|-------------|
|
|
166
|
+
| `width` | number | - | Target width |
|
|
167
|
+
| `height` | number | - | Target height |
|
|
168
|
+
| `format` | ImageFormat | 'auto' | Output format |
|
|
169
|
+
| `quality` | number | 80 | Quality (1-100) |
|
|
170
|
+
| `fit` | ImageFit | 'cover' | Resize fit mode |
|
|
171
|
+
| `blur` | boolean | false | Generate blur placeholder |
|
|
172
|
+
| `withoutEnlargement` | boolean | false | Prevent upscaling |
|
|
173
|
+
| `preserveMetadata` | boolean | false | Keep EXIF data |
|
|
174
|
+
|
|
175
|
+
### ImageFormat
|
|
176
|
+
|
|
177
|
+
`'jpeg' | 'png' | 'webp' | 'avif' | 'gif' | 'tiff' | 'auto'`
|
|
178
|
+
|
|
179
|
+
### ImageFit
|
|
180
|
+
|
|
181
|
+
`'cover' | 'contain' | 'fill' | 'inside' | 'outside'`
|
|
182
|
+
|
|
183
|
+
## Creating Custom Adapters
|
|
184
|
+
|
|
185
|
+
Implement the `ImageAdapter` interface:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import type { ImageAdapter } from '@flight-framework/image';
|
|
189
|
+
|
|
190
|
+
export function myAdapter(config: MyConfig): ImageAdapter {
|
|
191
|
+
return {
|
|
192
|
+
name: 'my-adapter',
|
|
193
|
+
|
|
194
|
+
supportsFormat(format) {
|
|
195
|
+
return ['webp', 'jpeg', 'png'].includes(format);
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
async getMetadata(input) {
|
|
199
|
+
// Return image dimensions, format, etc.
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
async optimize(input, options) {
|
|
203
|
+
// Process and return optimized image
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async generateBlurPlaceholder(input, size) {
|
|
207
|
+
// Return base64 blur placeholder
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
async generateResponsive(input, config) {
|
|
211
|
+
// Generate multiple sizes
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ImageAdapterFactory } from '../index.js';
|
|
2
|
+
import 'node:buffer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cloudinary Adapter for @flightdev/image
|
|
6
|
+
*
|
|
7
|
+
* CDN-based image optimization using Cloudinary's transformation API.
|
|
8
|
+
* No local processing required - images are transformed at the CDN edge.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createImage } from '@flightdev/image';
|
|
13
|
+
* import { cloudinary } from '@flightdev/image/cloudinary';
|
|
14
|
+
*
|
|
15
|
+
* const image = createImage(cloudinary({
|
|
16
|
+
* cloudName: 'my-cloud',
|
|
17
|
+
* apiKey: process.env.CLOUDINARY_API_KEY,
|
|
18
|
+
* apiSecret: process.env.CLOUDINARY_API_SECRET,
|
|
19
|
+
* }));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface CloudinaryConfig {
|
|
24
|
+
/** Cloudinary cloud name */
|
|
25
|
+
cloudName: string;
|
|
26
|
+
/** API key (optional for URL generation, required for uploads) */
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
/** API secret (optional for URL generation, required for uploads) */
|
|
29
|
+
apiSecret?: string;
|
|
30
|
+
/** Use secure URLs (https) */
|
|
31
|
+
secure?: boolean;
|
|
32
|
+
/** Default folder for assets */
|
|
33
|
+
folder?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a Cloudinary-based image adapter
|
|
37
|
+
*
|
|
38
|
+
* Cloudinary is a CDN-based image optimization service.
|
|
39
|
+
* Images are transformed at the edge, requiring no local processing.
|
|
40
|
+
*
|
|
41
|
+
* @param config - Cloudinary configuration
|
|
42
|
+
* @returns ImageAdapter instance
|
|
43
|
+
*/
|
|
44
|
+
declare const cloudinary: ImageAdapterFactory<CloudinaryConfig>;
|
|
45
|
+
|
|
46
|
+
export { type CloudinaryConfig, cloudinary, cloudinary as default };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/adapters/cloudinary.ts
|
|
2
|
+
function buildCloudinaryUrl(cloudName, publicId, transformations, secure = true) {
|
|
3
|
+
const protocol = secure ? "https" : "http";
|
|
4
|
+
const transform = transformations.length > 0 ? transformations.join(",") + "/" : "";
|
|
5
|
+
return `${protocol}://res.cloudinary.com/${cloudName}/image/upload/${transform}${publicId}`;
|
|
6
|
+
}
|
|
7
|
+
function formatToCloudinary(format) {
|
|
8
|
+
if (format === "auto") return "f_auto";
|
|
9
|
+
return `f_${format}`;
|
|
10
|
+
}
|
|
11
|
+
function fitToCloudinary(fit) {
|
|
12
|
+
switch (fit) {
|
|
13
|
+
case "cover":
|
|
14
|
+
return "c_fill";
|
|
15
|
+
case "contain":
|
|
16
|
+
return "c_fit";
|
|
17
|
+
case "fill":
|
|
18
|
+
return "c_scale";
|
|
19
|
+
case "inside":
|
|
20
|
+
return "c_limit";
|
|
21
|
+
case "outside":
|
|
22
|
+
return "c_mfit";
|
|
23
|
+
default:
|
|
24
|
+
return "c_fill";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var cloudinary = (config) => {
|
|
28
|
+
if (!config?.cloudName) {
|
|
29
|
+
throw new Error("@flightdev/image: Cloudinary requires a cloudName configuration");
|
|
30
|
+
}
|
|
31
|
+
const { cloudName, secure = true, folder = "" } = config;
|
|
32
|
+
const adapter = {
|
|
33
|
+
name: "cloudinary",
|
|
34
|
+
supportsFormat(format) {
|
|
35
|
+
return ["jpeg", "png", "webp", "avif", "gif", "tiff", "auto"].includes(format);
|
|
36
|
+
},
|
|
37
|
+
async getMetadata(input) {
|
|
38
|
+
if (typeof input !== "string") {
|
|
39
|
+
throw new Error("Cloudinary getMetadata requires a URL or public ID");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
width: 0,
|
|
43
|
+
height: 0,
|
|
44
|
+
format: "unknown"
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
async optimize(input, options) {
|
|
48
|
+
if (typeof input !== "string") {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Cloudinary adapter works with URLs/public IDs.\nFor buffer processing, use Sharp or Squoosh adapter."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const transformations = [];
|
|
54
|
+
if (options.quality) {
|
|
55
|
+
transformations.push(`q_${options.quality}`);
|
|
56
|
+
} else {
|
|
57
|
+
transformations.push("q_auto");
|
|
58
|
+
}
|
|
59
|
+
if (options.format) {
|
|
60
|
+
transformations.push(formatToCloudinary(options.format));
|
|
61
|
+
} else {
|
|
62
|
+
transformations.push("f_auto");
|
|
63
|
+
}
|
|
64
|
+
if (options.width) {
|
|
65
|
+
transformations.push(`w_${options.width}`);
|
|
66
|
+
}
|
|
67
|
+
if (options.height) {
|
|
68
|
+
transformations.push(`h_${options.height}`);
|
|
69
|
+
}
|
|
70
|
+
if (options.fit) {
|
|
71
|
+
transformations.push(fitToCloudinary(options.fit));
|
|
72
|
+
}
|
|
73
|
+
if (options.withoutEnlargement) {
|
|
74
|
+
transformations.push("c_limit");
|
|
75
|
+
}
|
|
76
|
+
const publicId = folder ? `${folder}/${input}` : input;
|
|
77
|
+
const url = buildCloudinaryUrl(cloudName, publicId, transformations, secure);
|
|
78
|
+
const response = await fetch(url);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`Cloudinary request failed: ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
83
|
+
const contentType = response.headers.get("content-type") ?? "image/webp";
|
|
84
|
+
const format = contentType.replace("image/", "");
|
|
85
|
+
let blurDataUrl;
|
|
86
|
+
if (options.blur) {
|
|
87
|
+
blurDataUrl = await adapter.generateBlurPlaceholder(input);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
buffer,
|
|
91
|
+
format,
|
|
92
|
+
width: options.width ?? 0,
|
|
93
|
+
height: options.height ?? 0,
|
|
94
|
+
size: buffer.length,
|
|
95
|
+
blurDataUrl
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
async generateBlurPlaceholder(input, size = 10) {
|
|
99
|
+
if (typeof input !== "string") {
|
|
100
|
+
throw new Error("Cloudinary generateBlurPlaceholder requires a URL/public ID");
|
|
101
|
+
}
|
|
102
|
+
const transformations = [
|
|
103
|
+
`w_${size}`,
|
|
104
|
+
`h_${size}`,
|
|
105
|
+
"c_fill",
|
|
106
|
+
"e_blur:1000",
|
|
107
|
+
"q_20",
|
|
108
|
+
"f_webp"
|
|
109
|
+
];
|
|
110
|
+
const publicId = folder ? `${folder}/${input}` : input;
|
|
111
|
+
const url = buildCloudinaryUrl(cloudName, publicId, transformations, secure);
|
|
112
|
+
const response = await fetch(url);
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
throw new Error(`Cloudinary blur request failed: ${response.status}`);
|
|
115
|
+
}
|
|
116
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
117
|
+
return `data:image/webp;base64,${buffer.toString("base64")}`;
|
|
118
|
+
},
|
|
119
|
+
async generateResponsive(input, config2) {
|
|
120
|
+
if (typeof input !== "string") {
|
|
121
|
+
throw new Error("Cloudinary generateResponsive requires a URL/public ID");
|
|
122
|
+
}
|
|
123
|
+
const formats = config2.formats ?? [config2.format ?? "webp"];
|
|
124
|
+
const variants = [];
|
|
125
|
+
for (const width of config2.widths) {
|
|
126
|
+
for (const format of formats) {
|
|
127
|
+
const transformations = [
|
|
128
|
+
`w_${width}`,
|
|
129
|
+
formatToCloudinary(format),
|
|
130
|
+
"q_auto"
|
|
131
|
+
];
|
|
132
|
+
const publicId = folder ? `${folder}/${input}` : input;
|
|
133
|
+
const url = buildCloudinaryUrl(cloudName, publicId, transformations, secure);
|
|
134
|
+
variants.push({
|
|
135
|
+
width,
|
|
136
|
+
format,
|
|
137
|
+
url,
|
|
138
|
+
buffer: Buffer.alloc(0)
|
|
139
|
+
// CDN - no local buffer
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const srcset = variants.filter((v) => v.format === formats[0]).map((v) => `${v.url} ${v.width}w`).join(", ");
|
|
144
|
+
const sizes = config2.sizes ?? "100vw";
|
|
145
|
+
const blurDataUrl = await adapter.generateBlurPlaceholder(input);
|
|
146
|
+
return {
|
|
147
|
+
srcset,
|
|
148
|
+
sizes,
|
|
149
|
+
variants,
|
|
150
|
+
blurDataUrl
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return adapter;
|
|
155
|
+
};
|
|
156
|
+
var cloudinary_default = cloudinary;
|
|
157
|
+
|
|
158
|
+
export { cloudinary, cloudinary_default as default };
|
|
159
|
+
//# sourceMappingURL=cloudinary.js.map
|
|
160
|
+
//# sourceMappingURL=cloudinary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/cloudinary.ts"],"names":["config"],"mappings":"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","file":"cloudinary.js","sourcesContent":["/**\r\n * Cloudinary Adapter for @flightdev/image\r\n * \r\n * CDN-based image optimization using Cloudinary's transformation API.\r\n * No local processing required - images are transformed at the CDN edge.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createImage } from '@flightdev/image';\r\n * import { cloudinary } from '@flightdev/image/cloudinary';\r\n * \r\n * const image = createImage(cloudinary({\r\n * cloudName: 'my-cloud',\r\n * apiKey: process.env.CLOUDINARY_API_KEY,\r\n * apiSecret: process.env.CLOUDINARY_API_SECRET,\r\n * }));\r\n * ```\r\n */\r\n\r\nimport type {\r\n ImageAdapter,\r\n ImageAdapterFactory,\r\n ImageFormat,\r\n ImageMetadata,\r\n ImageOptimizeOptions,\r\n ImageOptimizeResult,\r\n ResponsiveConfig,\r\n ResponsiveResult,\r\n} from '../index.js';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface CloudinaryConfig {\r\n /** Cloudinary cloud name */\r\n cloudName: string;\r\n /** API key (optional for URL generation, required for uploads) */\r\n apiKey?: string;\r\n /** API secret (optional for URL generation, required for uploads) */\r\n apiSecret?: string;\r\n /** Use secure URLs (https) */\r\n secure?: boolean;\r\n /** Default folder for assets */\r\n folder?: string;\r\n}\r\n\r\n// ============================================================================\r\n// Cloudinary URL Builder\r\n// ============================================================================\r\n\r\nfunction buildCloudinaryUrl(\r\n cloudName: string,\r\n publicId: string,\r\n transformations: string[],\r\n secure = true\r\n): string {\r\n const protocol = secure ? 'https' : 'http';\r\n const transform = transformations.length > 0 ? transformations.join(',') + '/' : '';\r\n return `${protocol}://res.cloudinary.com/${cloudName}/image/upload/${transform}${publicId}`;\r\n}\r\n\r\nfunction formatToCloudinary(format: ImageFormat): string {\r\n if (format === 'auto') return 'f_auto';\r\n return `f_${format}`;\r\n}\r\n\r\nfunction fitToCloudinary(fit: string | undefined): string {\r\n switch (fit) {\r\n case 'cover': return 'c_fill';\r\n case 'contain': return 'c_fit';\r\n case 'fill': return 'c_scale';\r\n case 'inside': return 'c_limit';\r\n case 'outside': return 'c_mfit';\r\n default: return 'c_fill';\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Cloudinary Adapter Implementation\r\n// ============================================================================\r\n\r\n/**\r\n * Create a Cloudinary-based image adapter\r\n * \r\n * Cloudinary is a CDN-based image optimization service.\r\n * Images are transformed at the edge, requiring no local processing.\r\n * \r\n * @param config - Cloudinary configuration\r\n * @returns ImageAdapter instance\r\n */\r\nexport const cloudinary: ImageAdapterFactory<CloudinaryConfig> = (config) => {\r\n if (!config?.cloudName) {\r\n throw new Error('@flightdev/image: Cloudinary requires a cloudName configuration');\r\n }\r\n\r\n const { cloudName, secure = true, folder = '' } = config;\r\n\r\n const adapter: ImageAdapter = {\r\n name: 'cloudinary',\r\n\r\n supportsFormat(format: ImageFormat): boolean {\r\n // Cloudinary supports all common formats\r\n return ['jpeg', 'png', 'webp', 'avif', 'gif', 'tiff', 'auto'].includes(format);\r\n },\r\n\r\n async getMetadata(input: Buffer | string): Promise<ImageMetadata> {\r\n // For CDN adapters, metadata requires an API call\r\n // For now, return placeholder - in production you'd call Cloudinary API\r\n if (typeof input !== 'string') {\r\n throw new Error('Cloudinary getMetadata requires a URL or public ID');\r\n }\r\n\r\n // Extract from fl_getinfo would require API call\r\n return {\r\n width: 0,\r\n height: 0,\r\n format: 'unknown',\r\n };\r\n },\r\n\r\n async optimize(input: Buffer | string, options: ImageOptimizeOptions): Promise<ImageOptimizeResult> {\r\n if (typeof input !== 'string') {\r\n throw new Error(\r\n 'Cloudinary adapter works with URLs/public IDs.\\n' +\r\n 'For buffer processing, use Sharp or Squoosh adapter.'\r\n );\r\n }\r\n\r\n const transformations: string[] = [];\r\n\r\n // Quality\r\n if (options.quality) {\r\n transformations.push(`q_${options.quality}`);\r\n } else {\r\n transformations.push('q_auto');\r\n }\r\n\r\n // Format\r\n if (options.format) {\r\n transformations.push(formatToCloudinary(options.format));\r\n } else {\r\n transformations.push('f_auto');\r\n }\r\n\r\n // Resize\r\n if (options.width) {\r\n transformations.push(`w_${options.width}`);\r\n }\r\n if (options.height) {\r\n transformations.push(`h_${options.height}`);\r\n }\r\n\r\n // Fit\r\n if (options.fit) {\r\n transformations.push(fitToCloudinary(options.fit));\r\n }\r\n\r\n // Prevent enlargement\r\n if (options.withoutEnlargement) {\r\n transformations.push('c_limit');\r\n }\r\n\r\n const publicId = folder ? `${folder}/${input}` : input;\r\n const url = buildCloudinaryUrl(cloudName, publicId, transformations, secure);\r\n\r\n // Fetch the optimized image\r\n const response = await fetch(url);\r\n if (!response.ok) {\r\n throw new Error(`Cloudinary request failed: ${response.status}`);\r\n }\r\n\r\n const buffer = Buffer.from(await response.arrayBuffer());\r\n const contentType = response.headers.get('content-type') ?? 'image/webp';\r\n const format = contentType.replace('image/', '') as ImageFormat;\r\n\r\n // Generate blur if requested\r\n let blurDataUrl: string | undefined;\r\n if (options.blur) {\r\n blurDataUrl = await adapter.generateBlurPlaceholder(input);\r\n }\r\n\r\n return {\r\n buffer,\r\n format,\r\n width: options.width ?? 0,\r\n height: options.height ?? 0,\r\n size: buffer.length,\r\n blurDataUrl,\r\n };\r\n },\r\n\r\n async generateBlurPlaceholder(input: Buffer | string, size = 10): Promise<string> {\r\n if (typeof input !== 'string') {\r\n throw new Error('Cloudinary generateBlurPlaceholder requires a URL/public ID');\r\n }\r\n\r\n const transformations = [\r\n `w_${size}`,\r\n `h_${size}`,\r\n 'c_fill',\r\n 'e_blur:1000',\r\n 'q_20',\r\n 'f_webp',\r\n ];\r\n\r\n const publicId = folder ? `${folder}/${input}` : input;\r\n const url = buildCloudinaryUrl(cloudName, publicId, transformations, secure);\r\n\r\n const response = await fetch(url);\r\n if (!response.ok) {\r\n throw new Error(`Cloudinary blur request failed: ${response.status}`);\r\n }\r\n\r\n const buffer = Buffer.from(await response.arrayBuffer());\r\n return `data:image/webp;base64,${buffer.toString('base64')}`;\r\n },\r\n\r\n async generateResponsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult> {\r\n if (typeof input !== 'string') {\r\n throw new Error('Cloudinary generateResponsive requires a URL/public ID');\r\n }\r\n\r\n const formats = config.formats ?? [config.format ?? 'webp'];\r\n const variants: ResponsiveResult['variants'] = [];\r\n\r\n for (const width of config.widths) {\r\n for (const format of formats) {\r\n const transformations = [\r\n `w_${width}`,\r\n formatToCloudinary(format),\r\n 'q_auto',\r\n ];\r\n\r\n const publicId = folder ? `${folder}/${input}` : input;\r\n const url = buildCloudinaryUrl(cloudName, publicId, transformations, secure);\r\n\r\n variants.push({\r\n width,\r\n format,\r\n url,\r\n buffer: Buffer.alloc(0), // CDN - no local buffer\r\n });\r\n }\r\n }\r\n\r\n const srcset = variants\r\n .filter(v => v.format === formats[0])\r\n .map(v => `${v.url} ${v.width}w`)\r\n .join(', ');\r\n\r\n const sizes = config.sizes ?? '100vw';\r\n const blurDataUrl = await adapter.generateBlurPlaceholder(input);\r\n\r\n return {\r\n srcset,\r\n sizes,\r\n variants,\r\n blurDataUrl,\r\n };\r\n },\r\n };\r\n\r\n return adapter;\r\n};\r\n\r\nexport default cloudinary;\r\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ImageAdapterFactory } from '../index.js';
|
|
2
|
+
import 'node:buffer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Imgix Adapter for @flightdev/image
|
|
6
|
+
*
|
|
7
|
+
* CDN-based image optimization using Imgix's transformation API.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createImage } from '@flightdev/image';
|
|
12
|
+
* import { imgix } from '@flightdev/image/imgix';
|
|
13
|
+
*
|
|
14
|
+
* const image = createImage(imgix({
|
|
15
|
+
* domain: 'my-source.imgix.net',
|
|
16
|
+
* secureUrlToken: process.env.IMGIX_TOKEN, // optional
|
|
17
|
+
* }));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
interface ImgixConfig {
|
|
22
|
+
/** Imgix source domain (e.g., 'my-source.imgix.net') */
|
|
23
|
+
domain: string;
|
|
24
|
+
/** Secure URL token for signed URLs (optional) */
|
|
25
|
+
secureUrlToken?: string;
|
|
26
|
+
/** Use HTTPS */
|
|
27
|
+
useHttps?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create an Imgix-based image adapter
|
|
31
|
+
*
|
|
32
|
+
* Imgix is a CDN-based image optimization service.
|
|
33
|
+
*
|
|
34
|
+
* @param config - Imgix configuration
|
|
35
|
+
* @returns ImageAdapter instance
|
|
36
|
+
*/
|
|
37
|
+
declare const imgix: ImageAdapterFactory<ImgixConfig>;
|
|
38
|
+
|
|
39
|
+
export { type ImgixConfig, imgix as default, imgix };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// src/adapters/imgix.ts
|
|
2
|
+
function buildImgixUrl(domain, path, params, useHttps = true) {
|
|
3
|
+
const protocol = useHttps ? "https" : "http";
|
|
4
|
+
const query = new URLSearchParams();
|
|
5
|
+
for (const [key, value] of Object.entries(params)) {
|
|
6
|
+
query.set(key, String(value));
|
|
7
|
+
}
|
|
8
|
+
const queryString = query.toString();
|
|
9
|
+
return `${protocol}://${domain}${path}${queryString ? "?" + queryString : ""}`;
|
|
10
|
+
}
|
|
11
|
+
function formatToImgix(format) {
|
|
12
|
+
return format === "auto" ? "auto" : format;
|
|
13
|
+
}
|
|
14
|
+
function fitToImgix(fit) {
|
|
15
|
+
switch (fit) {
|
|
16
|
+
case "cover":
|
|
17
|
+
return "crop";
|
|
18
|
+
case "contain":
|
|
19
|
+
return "fit";
|
|
20
|
+
case "fill":
|
|
21
|
+
return "scale";
|
|
22
|
+
case "inside":
|
|
23
|
+
return "max";
|
|
24
|
+
case "outside":
|
|
25
|
+
return "min";
|
|
26
|
+
default:
|
|
27
|
+
return "crop";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
var imgix = (config) => {
|
|
31
|
+
if (!config?.domain) {
|
|
32
|
+
throw new Error("@flightdev/image: Imgix requires a domain configuration");
|
|
33
|
+
}
|
|
34
|
+
const { domain, useHttps = true } = config;
|
|
35
|
+
const adapter = {
|
|
36
|
+
name: "imgix",
|
|
37
|
+
supportsFormat(format) {
|
|
38
|
+
return ["jpeg", "png", "webp", "avif", "gif", "auto"].includes(format);
|
|
39
|
+
},
|
|
40
|
+
async getMetadata(input) {
|
|
41
|
+
if (typeof input !== "string") {
|
|
42
|
+
throw new Error("Imgix getMetadata requires a path");
|
|
43
|
+
}
|
|
44
|
+
const url = buildImgixUrl(domain, input, { fm: "json" }, useHttps);
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch(url);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`Imgix metadata request failed: ${response.status}`);
|
|
49
|
+
}
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
return {
|
|
52
|
+
width: data.PixelWidth ?? 0,
|
|
53
|
+
height: data.PixelHeight ?? 0,
|
|
54
|
+
format: data["Content-Type"]?.replace("image/", "") ?? "unknown",
|
|
55
|
+
size: parseInt(data["Content-Length"] ?? "0", 10)
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
return { width: 0, height: 0, format: "unknown" };
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
async optimize(input, options) {
|
|
62
|
+
if (typeof input !== "string") {
|
|
63
|
+
throw new Error("Imgix adapter works with paths/URLs only");
|
|
64
|
+
}
|
|
65
|
+
const params = {
|
|
66
|
+
auto: "format,compress"
|
|
67
|
+
};
|
|
68
|
+
if (options.width) params.w = options.width;
|
|
69
|
+
if (options.height) params.h = options.height;
|
|
70
|
+
if (options.quality) params.q = options.quality;
|
|
71
|
+
if (options.format && options.format !== "auto") params.fm = formatToImgix(options.format);
|
|
72
|
+
if (options.fit) params.fit = fitToImgix(options.fit);
|
|
73
|
+
const url = buildImgixUrl(domain, input, params, useHttps);
|
|
74
|
+
const response = await fetch(url);
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`Imgix request failed: ${response.status}`);
|
|
77
|
+
}
|
|
78
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
79
|
+
const contentType = response.headers.get("content-type") ?? "image/webp";
|
|
80
|
+
const format = contentType.replace("image/", "");
|
|
81
|
+
let blurDataUrl;
|
|
82
|
+
if (options.blur) {
|
|
83
|
+
blurDataUrl = await adapter.generateBlurPlaceholder(input);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
buffer,
|
|
87
|
+
format,
|
|
88
|
+
width: options.width ?? 0,
|
|
89
|
+
height: options.height ?? 0,
|
|
90
|
+
size: buffer.length,
|
|
91
|
+
blurDataUrl
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
async generateBlurPlaceholder(input, size = 10) {
|
|
95
|
+
if (typeof input !== "string") {
|
|
96
|
+
throw new Error("Imgix generateBlurPlaceholder requires a path");
|
|
97
|
+
}
|
|
98
|
+
const params = {
|
|
99
|
+
w: size,
|
|
100
|
+
h: size,
|
|
101
|
+
blur: 200,
|
|
102
|
+
q: 20,
|
|
103
|
+
fm: "webp"
|
|
104
|
+
};
|
|
105
|
+
const url = buildImgixUrl(domain, input, params, useHttps);
|
|
106
|
+
const response = await fetch(url);
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new Error(`Imgix blur request failed: ${response.status}`);
|
|
109
|
+
}
|
|
110
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
111
|
+
return `data:image/webp;base64,${buffer.toString("base64")}`;
|
|
112
|
+
},
|
|
113
|
+
async generateResponsive(input, config2) {
|
|
114
|
+
if (typeof input !== "string") {
|
|
115
|
+
throw new Error("Imgix generateResponsive requires a path");
|
|
116
|
+
}
|
|
117
|
+
const formats = config2.formats ?? [config2.format ?? "webp"];
|
|
118
|
+
const variants = [];
|
|
119
|
+
for (const width of config2.widths) {
|
|
120
|
+
for (const format of formats) {
|
|
121
|
+
const params = {
|
|
122
|
+
w: width,
|
|
123
|
+
auto: "compress"
|
|
124
|
+
};
|
|
125
|
+
if (format !== "auto") params.fm = format;
|
|
126
|
+
const url = buildImgixUrl(domain, input, params, useHttps);
|
|
127
|
+
variants.push({
|
|
128
|
+
width,
|
|
129
|
+
format,
|
|
130
|
+
url,
|
|
131
|
+
buffer: Buffer.alloc(0)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const srcset = variants.filter((v) => v.format === formats[0]).map((v) => `${v.url} ${v.width}w`).join(", ");
|
|
136
|
+
const sizes = config2.sizes ?? "100vw";
|
|
137
|
+
const blurDataUrl = await adapter.generateBlurPlaceholder(input);
|
|
138
|
+
return {
|
|
139
|
+
srcset,
|
|
140
|
+
sizes,
|
|
141
|
+
variants,
|
|
142
|
+
blurDataUrl
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
return adapter;
|
|
147
|
+
};
|
|
148
|
+
var imgix_default = imgix;
|
|
149
|
+
|
|
150
|
+
export { imgix_default as default, imgix };
|
|
151
|
+
//# sourceMappingURL=imgix.js.map
|
|
152
|
+
//# sourceMappingURL=imgix.js.map
|