@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/imgix.ts"],"names":["config"],"mappings":"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","file":"imgix.js","sourcesContent":["/**\r\n * Imgix Adapter for @flightdev/image\r\n * \r\n * CDN-based image optimization using Imgix's transformation API.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createImage } from '@flightdev/image';\r\n * import { imgix } from '@flightdev/image/imgix';\r\n * \r\n * const image = createImage(imgix({\r\n * domain: 'my-source.imgix.net',\r\n * secureUrlToken: process.env.IMGIX_TOKEN, // optional\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 ImgixConfig {\r\n /** Imgix source domain (e.g., 'my-source.imgix.net') */\r\n domain: string;\r\n /** Secure URL token for signed URLs (optional) */\r\n secureUrlToken?: string;\r\n /** Use HTTPS */\r\n useHttps?: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Imgix URL Builder\r\n// ============================================================================\r\n\r\nfunction buildImgixUrl(\r\n domain: string,\r\n path: string,\r\n params: Record<string, string | number>,\r\n useHttps = true\r\n): string {\r\n const protocol = useHttps ? 'https' : 'http';\r\n const query = new URLSearchParams();\r\n\r\n for (const [key, value] of Object.entries(params)) {\r\n query.set(key, String(value));\r\n }\r\n\r\n const queryString = query.toString();\r\n return `${protocol}://${domain}${path}${queryString ? '?' + queryString : ''}`;\r\n}\r\n\r\nfunction formatToImgix(format: ImageFormat): string {\r\n return format === 'auto' ? 'auto' : format;\r\n}\r\n\r\nfunction fitToImgix(fit: string | undefined): string {\r\n switch (fit) {\r\n case 'cover': return 'crop';\r\n case 'contain': return 'fit';\r\n case 'fill': return 'scale';\r\n case 'inside': return 'max';\r\n case 'outside': return 'min';\r\n default: return 'crop';\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Imgix Adapter Implementation\r\n// ============================================================================\r\n\r\n/**\r\n * Create an Imgix-based image adapter\r\n * \r\n * Imgix is a CDN-based image optimization service.\r\n * \r\n * @param config - Imgix configuration\r\n * @returns ImageAdapter instance\r\n */\r\nexport const imgix: ImageAdapterFactory<ImgixConfig> = (config) => {\r\n if (!config?.domain) {\r\n throw new Error('@flightdev/image: Imgix requires a domain configuration');\r\n }\r\n\r\n const { domain, useHttps = true } = config;\r\n\r\n const adapter: ImageAdapter = {\r\n name: 'imgix',\r\n\r\n supportsFormat(format: ImageFormat): boolean {\r\n return ['jpeg', 'png', 'webp', 'avif', 'gif', 'auto'].includes(format);\r\n },\r\n\r\n async getMetadata(input: Buffer | string): Promise<ImageMetadata> {\r\n if (typeof input !== 'string') {\r\n throw new Error('Imgix getMetadata requires a path');\r\n }\r\n\r\n // Imgix can return metadata via fm=json\r\n const url = buildImgixUrl(domain, input, { fm: 'json' }, useHttps);\r\n\r\n try {\r\n const response = await fetch(url);\r\n if (!response.ok) {\r\n throw new Error(`Imgix metadata request failed: ${response.status}`);\r\n }\r\n const data = await response.json() as {\r\n 'Content-Length': string;\r\n PixelWidth: number;\r\n PixelHeight: number;\r\n 'Content-Type': string;\r\n };\r\n\r\n return {\r\n width: data.PixelWidth ?? 0,\r\n height: data.PixelHeight ?? 0,\r\n format: data['Content-Type']?.replace('image/', '') ?? 'unknown',\r\n size: parseInt(data['Content-Length'] ?? '0', 10),\r\n };\r\n } catch {\r\n return { width: 0, height: 0, 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('Imgix adapter works with paths/URLs only');\r\n }\r\n\r\n const params: Record<string, string | number> = {\r\n auto: 'format,compress',\r\n };\r\n\r\n if (options.width) params.w = options.width;\r\n if (options.height) params.h = options.height;\r\n if (options.quality) params.q = options.quality;\r\n if (options.format && options.format !== 'auto') params.fm = formatToImgix(options.format);\r\n if (options.fit) params.fit = fitToImgix(options.fit);\r\n\r\n const url = buildImgixUrl(domain, input, params, useHttps);\r\n\r\n const response = await fetch(url);\r\n if (!response.ok) {\r\n throw new Error(`Imgix 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 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('Imgix generateBlurPlaceholder requires a path');\r\n }\r\n\r\n const params = {\r\n w: size,\r\n h: size,\r\n blur: 200,\r\n q: 20,\r\n fm: 'webp',\r\n };\r\n\r\n const url = buildImgixUrl(domain, input, params, useHttps);\r\n\r\n const response = await fetch(url);\r\n if (!response.ok) {\r\n throw new Error(`Imgix 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('Imgix generateResponsive requires a path');\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 params: Record<string, string | number> = {\r\n w: width,\r\n auto: 'compress',\r\n };\r\n if (format !== 'auto') params.fm = format;\r\n\r\n const url = buildImgixUrl(domain, input, params, useHttps);\r\n\r\n variants.push({\r\n width,\r\n format,\r\n url,\r\n buffer: Buffer.alloc(0),\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 imgix;\r\n"]}
@@ -0,0 +1,37 @@
1
+ import { ImageAdapterFactory } from '../index.js';
2
+ import 'node:buffer';
3
+
4
+ /**
5
+ * Sharp Adapter for @flightdev/image
6
+ *
7
+ * High-performance image processing using Sharp (libvips).
8
+ * Best for Node.js environments.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createImage } from '@flightdev/image';
13
+ * import { sharp } from '@flightdev/image/sharp';
14
+ *
15
+ * const image = createImage(sharp({ cache: true }));
16
+ * ```
17
+ */
18
+
19
+ interface SharpConfig {
20
+ /** Enable Sharp's internal cache (default: true) */
21
+ cache?: boolean;
22
+ /** Concurrency limit (default: auto) */
23
+ concurrency?: number;
24
+ /** SIMD optimization (default: true) */
25
+ simd?: boolean;
26
+ }
27
+ /**
28
+ * Create a Sharp-based image adapter
29
+ *
30
+ * Sharp provides high-performance image processing using libvips.
31
+ *
32
+ * @param config - Sharp configuration options
33
+ * @returns ImageAdapter instance
34
+ */
35
+ declare const sharp: ImageAdapterFactory<SharpConfig>;
36
+
37
+ export { type SharpConfig, sharp as default, sharp };
@@ -0,0 +1,165 @@
1
+ // src/adapters/sharp.ts
2
+ var sharpModule = null;
3
+ async function getSharp() {
4
+ if (!sharpModule) {
5
+ try {
6
+ sharpModule = (await import('sharp')).default;
7
+ } catch (error) {
8
+ throw new Error(
9
+ '@flightdev/image: Sharp is not installed. Run: npm install sharp\nIf you want to use a different adapter, import it instead:\n import { squoosh } from "@flightdev/image/squoosh"\n import { cloudinary } from "@flightdev/image/cloudinary"'
10
+ );
11
+ }
12
+ }
13
+ return sharpModule;
14
+ }
15
+ var FORMAT_MAP = {
16
+ jpeg: "jpeg",
17
+ png: "png",
18
+ webp: "webp",
19
+ avif: "avif",
20
+ gif: "gif",
21
+ tiff: "tiff",
22
+ auto: "webp"
23
+ // Default for auto
24
+ };
25
+ var sharp = (config = {}) => {
26
+ const { cache = true, concurrency, simd = true } = config;
27
+ let configured = false;
28
+ async function ensureConfigured() {
29
+ if (configured) return;
30
+ const sharpLib = await getSharp();
31
+ sharpLib.cache(cache);
32
+ if (concurrency !== void 0) {
33
+ sharpLib.concurrency(concurrency);
34
+ }
35
+ sharpLib.simd(simd);
36
+ configured = true;
37
+ }
38
+ const adapter = {
39
+ name: "sharp",
40
+ supportsFormat(format) {
41
+ return format in FORMAT_MAP;
42
+ },
43
+ async getMetadata(input) {
44
+ await ensureConfigured();
45
+ const sharpLib = await getSharp();
46
+ const image = sharpLib(input);
47
+ const meta = await image.metadata();
48
+ return {
49
+ width: meta.width ?? 0,
50
+ height: meta.height ?? 0,
51
+ format: meta.format ?? "unknown",
52
+ size: meta.size,
53
+ space: meta.space,
54
+ hasAlpha: meta.hasAlpha,
55
+ exif: meta.exif ? { raw: meta.exif } : void 0
56
+ };
57
+ },
58
+ async optimize(input, options) {
59
+ await ensureConfigured();
60
+ const sharpLib = await getSharp();
61
+ let pipeline = sharpLib(input);
62
+ if (options.width || options.height) {
63
+ pipeline = pipeline.resize({
64
+ width: options.width,
65
+ height: options.height,
66
+ fit: options.fit ?? "cover",
67
+ position: options.position ?? "center",
68
+ withoutEnlargement: options.withoutEnlargement ?? false
69
+ });
70
+ }
71
+ const targetFormat = options.format ?? "webp";
72
+ const quality = options.quality ?? 80;
73
+ switch (targetFormat) {
74
+ case "jpeg":
75
+ pipeline = pipeline.jpeg({ quality, progressive: true, mozjpeg: true });
76
+ break;
77
+ case "png":
78
+ pipeline = pipeline.png({ compressionLevel: 9, palette: true });
79
+ break;
80
+ case "webp":
81
+ pipeline = pipeline.webp({ quality, effort: 4 });
82
+ break;
83
+ case "avif":
84
+ pipeline = pipeline.avif({ quality, effort: 4 });
85
+ break;
86
+ case "gif":
87
+ pipeline = pipeline.gif();
88
+ break;
89
+ case "tiff":
90
+ pipeline = pipeline.tiff({ quality, compression: "jpeg" });
91
+ break;
92
+ case "auto":
93
+ pipeline = pipeline.webp({ quality, effort: 4 });
94
+ break;
95
+ }
96
+ if (options.preserveMetadata) {
97
+ pipeline = pipeline.withMetadata();
98
+ }
99
+ const { data, info } = await pipeline.toBuffer({ resolveWithObject: true });
100
+ let blurDataUrl;
101
+ if (options.blur) {
102
+ const blurSize = typeof options.blur === "number" ? options.blur : 10;
103
+ blurDataUrl = await adapter.generateBlurPlaceholder(input, blurSize);
104
+ }
105
+ return {
106
+ buffer: data,
107
+ format: info.format ?? targetFormat,
108
+ width: info.width,
109
+ height: info.height,
110
+ size: info.size,
111
+ blurDataUrl
112
+ };
113
+ },
114
+ async generateBlurPlaceholder(input, size = 10) {
115
+ await ensureConfigured();
116
+ const sharpLib = await getSharp();
117
+ const { data, info } = await sharpLib(input).resize(size, size, { fit: "inside" }).blur(1).webp({ quality: 20 }).toBuffer({ resolveWithObject: true });
118
+ const base64 = data.toString("base64");
119
+ return `data:image/webp;base64,${base64}`;
120
+ },
121
+ async generateResponsive(input, config2) {
122
+ await ensureConfigured();
123
+ await getSharp();
124
+ const formats = config2.formats ?? [config2.format ?? "webp"];
125
+ const variants = [];
126
+ for (const width of config2.widths) {
127
+ for (const format of formats) {
128
+ const result = await adapter.optimize(input, {
129
+ width,
130
+ format,
131
+ quality: 80
132
+ });
133
+ variants.push({
134
+ width,
135
+ format,
136
+ url: `${width}w.${format}`,
137
+ buffer: result.buffer
138
+ });
139
+ }
140
+ }
141
+ const srcsetsByFormat = /* @__PURE__ */ new Map();
142
+ for (const variant of variants) {
143
+ const list = srcsetsByFormat.get(variant.format) ?? [];
144
+ list.push(`${variant.url} ${variant.width}w`);
145
+ srcsetsByFormat.set(variant.format, list);
146
+ }
147
+ const mainFormat = formats[0] ?? "webp";
148
+ const srcset = srcsetsByFormat.get(mainFormat)?.join(", ") ?? "";
149
+ const sizes = config2.sizes ?? config2.widths.slice(0, -1).map((w, i) => `(max-width: ${w}px) ${w}px`).concat(["100vw"]).join(", ");
150
+ const blurDataUrl = await adapter.generateBlurPlaceholder(input);
151
+ return {
152
+ srcset,
153
+ sizes,
154
+ variants,
155
+ blurDataUrl
156
+ };
157
+ }
158
+ };
159
+ return adapter;
160
+ };
161
+ var sharp_default = sharp;
162
+
163
+ export { sharp_default as default, sharp };
164
+ //# sourceMappingURL=sharp.js.map
165
+ //# sourceMappingURL=sharp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/sharp.ts"],"names":["config"],"mappings":"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","file":"sharp.js","sourcesContent":["/**\r\n * Sharp Adapter for @flightdev/image\r\n * \r\n * High-performance image processing using Sharp (libvips).\r\n * Best for Node.js environments.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createImage } from '@flightdev/image';\r\n * import { sharp } from '@flightdev/image/sharp';\r\n * \r\n * const image = createImage(sharp({ cache: true }));\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 SharpConfig {\r\n /** Enable Sharp's internal cache (default: true) */\r\n cache?: boolean;\r\n /** Concurrency limit (default: auto) */\r\n concurrency?: number;\r\n /** SIMD optimization (default: true) */\r\n simd?: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Sharp Import Helper\r\n// ============================================================================\r\n\r\ntype SharpInstance = typeof import('sharp');\r\ntype Sharp = ReturnType<SharpInstance>;\r\n\r\nlet sharpModule: SharpInstance | null = null;\r\n\r\nasync function getSharp(): Promise<SharpInstance> {\r\n if (!sharpModule) {\r\n try {\r\n sharpModule = (await import('sharp')).default;\r\n } catch (error) {\r\n throw new Error(\r\n '@flightdev/image: Sharp is not installed. Run: npm install sharp\\n' +\r\n 'If you want to use a different adapter, import it instead:\\n' +\r\n ' import { squoosh } from \"@flightdev/image/squoosh\"\\n' +\r\n ' import { cloudinary } from \"@flightdev/image/cloudinary\"'\r\n );\r\n }\r\n }\r\n return sharpModule;\r\n}\r\n\r\n// ============================================================================\r\n// Format Helpers\r\n// ============================================================================\r\n\r\nconst FORMAT_MAP: Record<ImageFormat, string> = {\r\n jpeg: 'jpeg',\r\n png: 'png',\r\n webp: 'webp',\r\n avif: 'avif',\r\n gif: 'gif',\r\n tiff: 'tiff',\r\n auto: 'webp', // Default for auto\r\n};\r\n\r\nfunction toSharpFormat(format: ImageFormat): keyof import('sharp').FormatEnum {\r\n return FORMAT_MAP[format] as keyof import('sharp').FormatEnum;\r\n}\r\n\r\n// ============================================================================\r\n// Sharp Adapter Implementation\r\n// ============================================================================\r\n\r\n/**\r\n * Create a Sharp-based image adapter\r\n * \r\n * Sharp provides high-performance image processing using libvips.\r\n * \r\n * @param config - Sharp configuration options\r\n * @returns ImageAdapter instance\r\n */\r\nexport const sharp: ImageAdapterFactory<SharpConfig> = (config = {}) => {\r\n const { cache = true, concurrency, simd = true } = config;\r\n\r\n // Configure Sharp on first use\r\n let configured = false;\r\n async function ensureConfigured(): Promise<void> {\r\n if (configured) return;\r\n const sharpLib = await getSharp();\r\n sharpLib.cache(cache);\r\n if (concurrency !== undefined) {\r\n sharpLib.concurrency(concurrency);\r\n }\r\n sharpLib.simd(simd);\r\n configured = true;\r\n }\r\n\r\n const adapter: ImageAdapter = {\r\n name: 'sharp',\r\n\r\n supportsFormat(format: ImageFormat): boolean {\r\n return format in FORMAT_MAP;\r\n },\r\n\r\n async getMetadata(input: Buffer | string): Promise<ImageMetadata> {\r\n await ensureConfigured();\r\n const sharpLib = await getSharp();\r\n\r\n const image = sharpLib(input);\r\n const meta = await image.metadata();\r\n\r\n return {\r\n width: meta.width ?? 0,\r\n height: meta.height ?? 0,\r\n format: meta.format ?? 'unknown',\r\n size: meta.size,\r\n space: meta.space,\r\n hasAlpha: meta.hasAlpha,\r\n exif: meta.exif ? { raw: meta.exif } : undefined,\r\n };\r\n },\r\n\r\n async optimize(input: Buffer | string, options: ImageOptimizeOptions): Promise<ImageOptimizeResult> {\r\n await ensureConfigured();\r\n const sharpLib = await getSharp();\r\n\r\n let pipeline = sharpLib(input);\r\n\r\n // Resize\r\n if (options.width || options.height) {\r\n pipeline = pipeline.resize({\r\n width: options.width,\r\n height: options.height,\r\n fit: options.fit ?? 'cover',\r\n position: options.position ?? 'center',\r\n withoutEnlargement: options.withoutEnlargement ?? false,\r\n });\r\n }\r\n\r\n // Format conversion\r\n const targetFormat = options.format ?? 'webp';\r\n const quality = options.quality ?? 80;\r\n\r\n switch (targetFormat) {\r\n case 'jpeg':\r\n pipeline = pipeline.jpeg({ quality, progressive: true, mozjpeg: true });\r\n break;\r\n case 'png':\r\n pipeline = pipeline.png({ compressionLevel: 9, palette: true });\r\n break;\r\n case 'webp':\r\n pipeline = pipeline.webp({ quality, effort: 4 });\r\n break;\r\n case 'avif':\r\n pipeline = pipeline.avif({ quality, effort: 4 });\r\n break;\r\n case 'gif':\r\n pipeline = pipeline.gif();\r\n break;\r\n case 'tiff':\r\n pipeline = pipeline.tiff({ quality, compression: 'jpeg' });\r\n break;\r\n case 'auto':\r\n // Default to WebP for auto\r\n pipeline = pipeline.webp({ quality, effort: 4 });\r\n break;\r\n }\r\n\r\n // Metadata handling\r\n if (options.preserveMetadata) {\r\n pipeline = pipeline.withMetadata();\r\n }\r\n\r\n // Process\r\n const { data, info } = await pipeline.toBuffer({ resolveWithObject: true });\r\n\r\n // Generate blur placeholder if requested\r\n let blurDataUrl: string | undefined;\r\n if (options.blur) {\r\n const blurSize = typeof options.blur === 'number' ? options.blur : 10;\r\n blurDataUrl = await adapter.generateBlurPlaceholder(input, blurSize);\r\n }\r\n\r\n return {\r\n buffer: data,\r\n format: (info.format as ImageFormat) ?? targetFormat,\r\n width: info.width,\r\n height: info.height,\r\n size: info.size,\r\n blurDataUrl,\r\n };\r\n },\r\n\r\n async generateBlurPlaceholder(input: Buffer | string, size = 10): Promise<string> {\r\n await ensureConfigured();\r\n const sharpLib = await getSharp();\r\n\r\n const { data, info } = await sharpLib(input)\r\n .resize(size, size, { fit: 'inside' })\r\n .blur(1)\r\n .webp({ quality: 20 })\r\n .toBuffer({ resolveWithObject: true });\r\n\r\n const base64 = data.toString('base64');\r\n return `data:image/webp;base64,${base64}`;\r\n },\r\n\r\n async generateResponsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult> {\r\n await ensureConfigured();\r\n const sharpLib = await getSharp();\r\n\r\n const formats = config.formats ?? [config.format ?? 'webp'];\r\n const variants: ResponsiveResult['variants'] = [];\r\n\r\n // Generate each variant\r\n for (const width of config.widths) {\r\n for (const format of formats) {\r\n const result = await adapter.optimize(input, {\r\n width,\r\n format,\r\n quality: 80,\r\n });\r\n\r\n variants.push({\r\n width,\r\n format,\r\n url: `${width}w.${format}`,\r\n buffer: result.buffer,\r\n });\r\n }\r\n }\r\n\r\n // Generate srcset for each format\r\n const srcsetsByFormat = new Map<ImageFormat, string[]>();\r\n for (const variant of variants) {\r\n const list = srcsetsByFormat.get(variant.format) ?? [];\r\n list.push(`${variant.url} ${variant.width}w`);\r\n srcsetsByFormat.set(variant.format, list);\r\n }\r\n\r\n // Use first format for main srcset\r\n const mainFormat = formats[0] ?? 'webp';\r\n const srcset = srcsetsByFormat.get(mainFormat)?.join(', ') ?? '';\r\n\r\n // Generate sizes attribute\r\n const sizes = config.sizes ?? config.widths\r\n .slice(0, -1)\r\n .map((w, i) => `(max-width: ${w}px) ${w}px`)\r\n .concat(['100vw'])\r\n .join(', ');\r\n\r\n // Generate blur placeholder\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 sharp;\r\n"]}
@@ -0,0 +1,36 @@
1
+ import { ImageAdapterFactory } from '../index.js';
2
+ import 'node:buffer';
3
+
4
+ /**
5
+ * Squoosh Adapter for @flightdev/image
6
+ *
7
+ * WASM-based image processing that works on Edge runtimes.
8
+ * Uses @aspect-build/rules_js compatible libSquoosh.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createImage } from '@flightdev/image';
13
+ * import { squoosh } from '@flightdev/image/squoosh';
14
+ *
15
+ * const image = createImage(squoosh());
16
+ * ```
17
+ */
18
+
19
+ interface SquooshConfig {
20
+ /** Path to WASM binaries (for custom hosting) */
21
+ wasmPath?: string;
22
+ }
23
+ /**
24
+ * Create a Squoosh-based image adapter
25
+ *
26
+ * Squoosh uses WASM for image processing, making it compatible with
27
+ * Edge runtimes like Cloudflare Workers, Vercel Edge, and Deno Deploy.
28
+ *
29
+ * Note: Squoosh is slower than Sharp but works in more environments.
30
+ *
31
+ * @param config - Squoosh configuration options
32
+ * @returns ImageAdapter instance
33
+ */
34
+ declare const squoosh: ImageAdapterFactory<SquooshConfig>;
35
+
36
+ export { type SquooshConfig, squoosh as default, squoosh };
@@ -0,0 +1,141 @@
1
+ // src/adapters/squoosh.ts
2
+ var squoosh = (config = {}) => {
3
+ let imagePool = null;
4
+ async function getImagePool() {
5
+ if (imagePool) return imagePool;
6
+ try {
7
+ const moduleName = "@aspect-dev/squoosh";
8
+ const squooshModule = await import(
9
+ /* @vite-ignore */
10
+ moduleName
11
+ );
12
+ const { ImagePool } = squooshModule;
13
+ imagePool = new ImagePool();
14
+ return imagePool;
15
+ } catch (error) {
16
+ throw new Error(
17
+ '@flightdev/image: Squoosh is not installed. Run: npm install @aspect-dev/squoosh\nFor a lighter alternative on Edge, consider:\n import { cloudinary } from "@flightdev/image/cloudinary"'
18
+ );
19
+ }
20
+ }
21
+ const adapter = {
22
+ name: "squoosh",
23
+ supportsFormat(format) {
24
+ return ["jpeg", "png", "webp", "avif", "auto"].includes(format);
25
+ },
26
+ async getMetadata(input) {
27
+ const pool = await getImagePool();
28
+ const buffer = typeof input === "string" ? await (await import('fs/promises')).readFile(input) : input;
29
+ const image = pool.ingestImage(new Uint8Array(buffer));
30
+ const decoded = await image.decoded;
31
+ return {
32
+ width: decoded.bitmap.width,
33
+ height: decoded.bitmap.height,
34
+ format: "unknown",
35
+ // Squoosh doesn't expose original format easily
36
+ size: buffer.length,
37
+ hasAlpha: true
38
+ // Assume alpha for safety
39
+ };
40
+ },
41
+ async optimize(input, options) {
42
+ const pool = await getImagePool();
43
+ const buffer = typeof input === "string" ? await (await import('fs/promises')).readFile(input) : input;
44
+ const image = pool.ingestImage(new Uint8Array(buffer));
45
+ const decoded = await image.decoded;
46
+ if (options.width || options.height) {
47
+ await image.preprocess({
48
+ resize: {
49
+ enabled: true,
50
+ width: options.width ?? Math.round(
51
+ decoded.bitmap.width * (options.height / decoded.bitmap.height)
52
+ ),
53
+ height: options.height ?? Math.round(
54
+ decoded.bitmap.height * (options.width / decoded.bitmap.width)
55
+ )
56
+ }
57
+ });
58
+ }
59
+ const targetFormat = options.format === "auto" ? "webp" : options.format ?? "webp";
60
+ const quality = options.quality ?? 80;
61
+ const encodeOptions = {};
62
+ switch (targetFormat) {
63
+ case "webp":
64
+ encodeOptions.webp = { quality };
65
+ break;
66
+ case "avif":
67
+ encodeOptions.avif = { quality };
68
+ break;
69
+ case "jpeg":
70
+ encodeOptions.mozjpeg = { quality };
71
+ break;
72
+ case "png":
73
+ encodeOptions.oxipng = { level: 3 };
74
+ break;
75
+ default:
76
+ encodeOptions.webp = { quality };
77
+ }
78
+ const result = await image.encode(encodeOptions);
79
+ const encodedKey = Object.keys(result)[0];
80
+ const encoded = result[encodedKey];
81
+ const outputBuffer = Buffer.from(encoded.binary);
82
+ let blurDataUrl;
83
+ if (options.blur) {
84
+ blurDataUrl = await adapter.generateBlurPlaceholder(input);
85
+ }
86
+ const finalWidth = options.width ?? decoded.bitmap.width;
87
+ const finalHeight = options.height ?? decoded.bitmap.height;
88
+ return {
89
+ buffer: outputBuffer,
90
+ format: targetFormat,
91
+ width: finalWidth,
92
+ height: finalHeight,
93
+ size: outputBuffer.length,
94
+ blurDataUrl
95
+ };
96
+ },
97
+ async generateBlurPlaceholder(input, size = 10) {
98
+ const result = await adapter.optimize(input, {
99
+ width: size,
100
+ height: size,
101
+ format: "webp",
102
+ quality: 20
103
+ });
104
+ return `data:image/webp;base64,${result.buffer.toString("base64")}`;
105
+ },
106
+ async generateResponsive(input, config2) {
107
+ const formats = config2.formats ?? [config2.format ?? "webp"];
108
+ const variants = [];
109
+ for (const width of config2.widths) {
110
+ for (const format of formats) {
111
+ const result = await adapter.optimize(input, {
112
+ width,
113
+ format,
114
+ quality: 80
115
+ });
116
+ variants.push({
117
+ width,
118
+ format,
119
+ url: `${width}w.${format}`,
120
+ buffer: result.buffer
121
+ });
122
+ }
123
+ }
124
+ const srcset = variants.filter((v) => v.format === formats[0]).map((v) => `${v.url} ${v.width}w`).join(", ");
125
+ const sizes = config2.sizes ?? "100vw";
126
+ const blurDataUrl = await adapter.generateBlurPlaceholder(input);
127
+ return {
128
+ srcset,
129
+ sizes,
130
+ variants,
131
+ blurDataUrl
132
+ };
133
+ }
134
+ };
135
+ return adapter;
136
+ };
137
+ var squoosh_default = squoosh;
138
+
139
+ export { squoosh_default as default, squoosh };
140
+ //# sourceMappingURL=squoosh.js.map
141
+ //# sourceMappingURL=squoosh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/squoosh.ts"],"names":["config"],"mappings":"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","file":"squoosh.js","sourcesContent":["/**\r\n * Squoosh Adapter for @flightdev/image\r\n * \r\n * WASM-based image processing that works on Edge runtimes.\r\n * Uses @aspect-build/rules_js compatible libSquoosh.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createImage } from '@flightdev/image';\r\n * import { squoosh } from '@flightdev/image/squoosh';\r\n * \r\n * const image = createImage(squoosh());\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 SquooshConfig {\r\n /** Path to WASM binaries (for custom hosting) */\r\n wasmPath?: string;\r\n}\r\n\r\n// ============================================================================\r\n// Squoosh Adapter Implementation\r\n// ============================================================================\r\n\r\n/**\r\n * Create a Squoosh-based image adapter\r\n * \r\n * Squoosh uses WASM for image processing, making it compatible with\r\n * Edge runtimes like Cloudflare Workers, Vercel Edge, and Deno Deploy.\r\n * \r\n * Note: Squoosh is slower than Sharp but works in more environments.\r\n * \r\n * @param config - Squoosh configuration options\r\n * @returns ImageAdapter instance\r\n */\r\nexport const squoosh: ImageAdapterFactory<SquooshConfig> = (config = {}) => {\r\n // Track initialization\r\n let imagePool: unknown = null;\r\n\r\n async function getImagePool(): Promise<unknown> {\r\n if (imagePool) return imagePool;\r\n\r\n try {\r\n // Dynamic import for squoosh-compatible module\r\n // The module name is constructed at runtime to avoid build-time resolution\r\n const moduleName = '@aspect-dev/squoosh';\r\n const squooshModule = await import(/* @vite-ignore */ moduleName);\r\n const { ImagePool } = squooshModule;\r\n imagePool = new ImagePool();\r\n return imagePool;\r\n } catch (error) {\r\n throw new Error(\r\n '@flightdev/image: Squoosh is not installed. Run: npm install @aspect-dev/squoosh\\n' +\r\n 'For a lighter alternative on Edge, consider:\\n' +\r\n ' import { cloudinary } from \"@flightdev/image/cloudinary\"'\r\n );\r\n }\r\n }\r\n\r\n const adapter: ImageAdapter = {\r\n name: 'squoosh',\r\n\r\n supportsFormat(format: ImageFormat): boolean {\r\n // Squoosh supports most formats\r\n return ['jpeg', 'png', 'webp', 'avif', 'auto'].includes(format);\r\n },\r\n\r\n async getMetadata(input: Buffer | string): Promise<ImageMetadata> {\r\n // For Squoosh, we need to decode the image first\r\n const pool = await getImagePool() as { ingestImage: (data: Uint8Array) => unknown };\r\n\r\n const buffer = typeof input === 'string'\r\n ? await (await import('node:fs/promises')).readFile(input)\r\n : input;\r\n\r\n const image = pool.ingestImage(new Uint8Array(buffer)) as {\r\n decoded: Promise<{ bitmap: { width: number; height: number } }>;\r\n };\r\n const decoded = await image.decoded;\r\n\r\n return {\r\n width: decoded.bitmap.width,\r\n height: decoded.bitmap.height,\r\n format: 'unknown', // Squoosh doesn't expose original format easily\r\n size: buffer.length,\r\n hasAlpha: true, // Assume alpha for safety\r\n };\r\n },\r\n\r\n async optimize(input: Buffer | string, options: ImageOptimizeOptions): Promise<ImageOptimizeResult> {\r\n const pool = await getImagePool() as {\r\n ingestImage: (data: Uint8Array) => {\r\n decoded: Promise<{ bitmap: { width: number; height: number } }>;\r\n preprocess: (opts: unknown) => Promise<void>;\r\n encode: (opts: unknown) => Promise<{\r\n [key: string]: { binary: Uint8Array };\r\n }>;\r\n };\r\n };\r\n\r\n const buffer = typeof input === 'string'\r\n ? await (await import('node:fs/promises')).readFile(input)\r\n : input;\r\n\r\n const image = pool.ingestImage(new Uint8Array(buffer));\r\n const decoded = await image.decoded;\r\n\r\n // Resize if needed\r\n if (options.width || options.height) {\r\n await image.preprocess({\r\n resize: {\r\n enabled: true,\r\n width: options.width ?? Math.round(\r\n decoded.bitmap.width * (options.height! / decoded.bitmap.height)\r\n ),\r\n height: options.height ?? Math.round(\r\n decoded.bitmap.height * (options.width! / decoded.bitmap.width)\r\n ),\r\n },\r\n });\r\n }\r\n\r\n // Determine format and encode\r\n const targetFormat = options.format === 'auto' ? 'webp' : (options.format ?? 'webp');\r\n const quality = options.quality ?? 80;\r\n\r\n const encodeOptions: Record<string, unknown> = {};\r\n\r\n switch (targetFormat) {\r\n case 'webp':\r\n encodeOptions.webp = { quality };\r\n break;\r\n case 'avif':\r\n encodeOptions.avif = { quality };\r\n break;\r\n case 'jpeg':\r\n encodeOptions.mozjpeg = { quality };\r\n break;\r\n case 'png':\r\n encodeOptions.oxipng = { level: 3 };\r\n break;\r\n default:\r\n encodeOptions.webp = { quality };\r\n }\r\n\r\n const result = await image.encode(encodeOptions);\r\n const encodedKey = Object.keys(result)[0];\r\n const encoded = result[encodedKey];\r\n\r\n const outputBuffer = Buffer.from(encoded.binary);\r\n\r\n // Generate blur placeholder 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 // Get final dimensions (simplified - would need proper tracking)\r\n const finalWidth = options.width ?? decoded.bitmap.width;\r\n const finalHeight = options.height ?? decoded.bitmap.height;\r\n\r\n return {\r\n buffer: outputBuffer,\r\n format: targetFormat as ImageFormat,\r\n width: finalWidth,\r\n height: finalHeight,\r\n size: outputBuffer.length,\r\n blurDataUrl,\r\n };\r\n },\r\n\r\n async generateBlurPlaceholder(input: Buffer | string, size = 10): Promise<string> {\r\n const result = await adapter.optimize(input, {\r\n width: size,\r\n height: size,\r\n format: 'webp',\r\n quality: 20,\r\n });\r\n\r\n return `data:image/webp;base64,${result.buffer.toString('base64')}`;\r\n },\r\n\r\n async generateResponsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult> {\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 result = await adapter.optimize(input, {\r\n width,\r\n format,\r\n quality: 80,\r\n });\r\n\r\n variants.push({\r\n width,\r\n format,\r\n url: `${width}w.${format}`,\r\n buffer: result.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 squoosh;\r\n"]}
@@ -0,0 +1,66 @@
1
+ import * as React from 'react';
2
+ import { ImageFormat, ImageFit } from '../index.js';
3
+ import 'node:buffer';
4
+
5
+ /**
6
+ * React Image Component for @flightdev/image
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { Image } from '@flightdev/image/react';
11
+ *
12
+ * <Image
13
+ * src="/hero.jpg"
14
+ * alt="Hero"
15
+ * width={800}
16
+ * height={600}
17
+ * priority
18
+ * placeholder="blur"
19
+ * />
20
+ * ```
21
+ */
22
+
23
+ interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
24
+ /** Image source URL or path */
25
+ src: string;
26
+ /** Alt text (required for accessibility) */
27
+ alt: string;
28
+ /** Target width */
29
+ width?: number;
30
+ /** Target height */
31
+ height?: number;
32
+ /** Output format */
33
+ format?: ImageFormat;
34
+ /** Quality (1-100) */
35
+ quality?: number;
36
+ /** Fit mode */
37
+ fit?: ImageFit;
38
+ /** Priority loading (preload) */
39
+ priority?: boolean;
40
+ /** Placeholder type */
41
+ placeholder?: 'blur' | 'empty' | 'none';
42
+ /** Custom blur data URL */
43
+ blurDataUrl?: string;
44
+ /** Sizes attribute for responsive images */
45
+ sizes?: string;
46
+ /** Image service base URL */
47
+ baseUrl?: string;
48
+ /** Loading behavior */
49
+ loading?: 'lazy' | 'eager';
50
+ /** Callback when image loads */
51
+ onLoad?: React.ReactEventHandler<HTMLImageElement>;
52
+ /** Callback on error */
53
+ onError?: React.ReactEventHandler<HTMLImageElement>;
54
+ }
55
+ /**
56
+ * Optimized Image component for React
57
+ *
58
+ * Automatically handles:
59
+ * - Lazy loading with IntersectionObserver
60
+ * - Blur placeholder during load
61
+ * - Responsive srcset generation
62
+ * - Format optimization
63
+ */
64
+ declare const Image: React.ForwardRefExoticComponent<ImageProps & React.RefAttributes<HTMLImageElement>>;
65
+
66
+ export { Image, type ImageProps, Image as default };