@lens-image/core 0.1.0

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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/dist/cjs/adapters/memory.d.ts +55 -0
  4. package/dist/cjs/adapters/memory.d.ts.map +1 -0
  5. package/dist/cjs/adapters/memory.js +84 -0
  6. package/dist/cjs/adapters/memory.js.map +1 -0
  7. package/dist/cjs/browser.d.ts +46 -0
  8. package/dist/cjs/browser.d.ts.map +1 -0
  9. package/dist/cjs/browser.js +79 -0
  10. package/dist/cjs/browser.js.map +1 -0
  11. package/dist/cjs/bytes.d.ts +18 -0
  12. package/dist/cjs/bytes.d.ts.map +1 -0
  13. package/dist/cjs/bytes.js +31 -0
  14. package/dist/cjs/bytes.js.map +1 -0
  15. package/dist/cjs/cache.d.ts +43 -0
  16. package/dist/cjs/cache.d.ts.map +1 -0
  17. package/dist/cjs/cache.js +77 -0
  18. package/dist/cjs/cache.js.map +1 -0
  19. package/dist/cjs/concurrency.d.ts +35 -0
  20. package/dist/cjs/concurrency.d.ts.map +1 -0
  21. package/dist/cjs/concurrency.js +82 -0
  22. package/dist/cjs/concurrency.js.map +1 -0
  23. package/dist/cjs/engines/index.d.ts +20 -0
  24. package/dist/cjs/engines/index.d.ts.map +1 -0
  25. package/dist/cjs/engines/index.js +38 -0
  26. package/dist/cjs/engines/index.js.map +1 -0
  27. package/dist/cjs/engines/passthrough.d.ts +41 -0
  28. package/dist/cjs/engines/passthrough.d.ts.map +1 -0
  29. package/dist/cjs/engines/passthrough.js +66 -0
  30. package/dist/cjs/engines/passthrough.js.map +1 -0
  31. package/dist/cjs/engines/sharp.d.ts +58 -0
  32. package/dist/cjs/engines/sharp.d.ts.map +1 -0
  33. package/dist/cjs/engines/sharp.js +243 -0
  34. package/dist/cjs/engines/sharp.js.map +1 -0
  35. package/dist/cjs/errors.d.ts +90 -0
  36. package/dist/cjs/errors.d.ts.map +1 -0
  37. package/dist/cjs/errors.js +97 -0
  38. package/dist/cjs/errors.js.map +1 -0
  39. package/dist/cjs/format.d.ts +33 -0
  40. package/dist/cjs/format.d.ts.map +1 -0
  41. package/dist/cjs/format.js +102 -0
  42. package/dist/cjs/format.js.map +1 -0
  43. package/dist/cjs/hash.d.ts +19 -0
  44. package/dist/cjs/hash.d.ts.map +1 -0
  45. package/dist/cjs/hash.js +43 -0
  46. package/dist/cjs/hash.js.map +1 -0
  47. package/dist/cjs/index.d.ts +56 -0
  48. package/dist/cjs/index.d.ts.map +1 -0
  49. package/dist/cjs/index.js +110 -0
  50. package/dist/cjs/index.js.map +1 -0
  51. package/dist/cjs/naming.d.ts +48 -0
  52. package/dist/cjs/naming.d.ts.map +1 -0
  53. package/dist/cjs/naming.js +95 -0
  54. package/dist/cjs/naming.js.map +1 -0
  55. package/dist/cjs/optimizer.d.ts +89 -0
  56. package/dist/cjs/optimizer.d.ts.map +1 -0
  57. package/dist/cjs/optimizer.js +667 -0
  58. package/dist/cjs/optimizer.js.map +1 -0
  59. package/dist/cjs/package.json +3 -0
  60. package/dist/cjs/retry.d.ts +55 -0
  61. package/dist/cjs/retry.d.ts.map +1 -0
  62. package/dist/cjs/retry.js +129 -0
  63. package/dist/cjs/retry.js.map +1 -0
  64. package/dist/cjs/server/handler.d.ts +80 -0
  65. package/dist/cjs/server/handler.d.ts.map +1 -0
  66. package/dist/cjs/server/handler.js +155 -0
  67. package/dist/cjs/server/handler.js.map +1 -0
  68. package/dist/cjs/sniff.d.ts +37 -0
  69. package/dist/cjs/sniff.d.ts.map +1 -0
  70. package/dist/cjs/sniff.js +442 -0
  71. package/dist/cjs/sniff.js.map +1 -0
  72. package/dist/cjs/source.d.ts +45 -0
  73. package/dist/cjs/source.d.ts.map +1 -0
  74. package/dist/cjs/source.js +221 -0
  75. package/dist/cjs/source.js.map +1 -0
  76. package/dist/cjs/srcset.d.ts +66 -0
  77. package/dist/cjs/srcset.d.ts.map +1 -0
  78. package/dist/cjs/srcset.js +81 -0
  79. package/dist/cjs/srcset.js.map +1 -0
  80. package/dist/cjs/types.d.ts +441 -0
  81. package/dist/cjs/types.d.ts.map +1 -0
  82. package/dist/cjs/types.js +12 -0
  83. package/dist/cjs/types.js.map +1 -0
  84. package/dist/cjs/validation.d.ts +38 -0
  85. package/dist/cjs/validation.d.ts.map +1 -0
  86. package/dist/cjs/validation.js +79 -0
  87. package/dist/cjs/validation.js.map +1 -0
  88. package/dist/esm/adapters/memory.d.ts +55 -0
  89. package/dist/esm/adapters/memory.d.ts.map +1 -0
  90. package/dist/esm/adapters/memory.js +80 -0
  91. package/dist/esm/adapters/memory.js.map +1 -0
  92. package/dist/esm/browser.d.ts +46 -0
  93. package/dist/esm/browser.d.ts.map +1 -0
  94. package/dist/esm/browser.js +49 -0
  95. package/dist/esm/browser.js.map +1 -0
  96. package/dist/esm/bytes.d.ts +18 -0
  97. package/dist/esm/bytes.d.ts.map +1 -0
  98. package/dist/esm/bytes.js +28 -0
  99. package/dist/esm/bytes.js.map +1 -0
  100. package/dist/esm/cache.d.ts +43 -0
  101. package/dist/esm/cache.d.ts.map +1 -0
  102. package/dist/esm/cache.js +73 -0
  103. package/dist/esm/cache.js.map +1 -0
  104. package/dist/esm/concurrency.d.ts +35 -0
  105. package/dist/esm/concurrency.d.ts.map +1 -0
  106. package/dist/esm/concurrency.js +78 -0
  107. package/dist/esm/concurrency.js.map +1 -0
  108. package/dist/esm/engines/index.d.ts +20 -0
  109. package/dist/esm/engines/index.d.ts.map +1 -0
  110. package/dist/esm/engines/index.js +30 -0
  111. package/dist/esm/engines/index.js.map +1 -0
  112. package/dist/esm/engines/passthrough.d.ts +41 -0
  113. package/dist/esm/engines/passthrough.d.ts.map +1 -0
  114. package/dist/esm/engines/passthrough.js +63 -0
  115. package/dist/esm/engines/passthrough.js.map +1 -0
  116. package/dist/esm/engines/sharp.d.ts +58 -0
  117. package/dist/esm/engines/sharp.d.ts.map +1 -0
  118. package/dist/esm/engines/sharp.js +206 -0
  119. package/dist/esm/engines/sharp.js.map +1 -0
  120. package/dist/esm/errors.d.ts +90 -0
  121. package/dist/esm/errors.d.ts.map +1 -0
  122. package/dist/esm/errors.js +91 -0
  123. package/dist/esm/errors.js.map +1 -0
  124. package/dist/esm/format.d.ts +33 -0
  125. package/dist/esm/format.d.ts.map +1 -0
  126. package/dist/esm/format.js +93 -0
  127. package/dist/esm/format.js.map +1 -0
  128. package/dist/esm/hash.d.ts +19 -0
  129. package/dist/esm/hash.d.ts.map +1 -0
  130. package/dist/esm/hash.js +38 -0
  131. package/dist/esm/hash.js.map +1 -0
  132. package/dist/esm/index.d.ts +56 -0
  133. package/dist/esm/index.d.ts.map +1 -0
  134. package/dist/esm/index.js +54 -0
  135. package/dist/esm/index.js.map +1 -0
  136. package/dist/esm/naming.d.ts +48 -0
  137. package/dist/esm/naming.d.ts.map +1 -0
  138. package/dist/esm/naming.js +88 -0
  139. package/dist/esm/naming.js.map +1 -0
  140. package/dist/esm/optimizer.d.ts +89 -0
  141. package/dist/esm/optimizer.d.ts.map +1 -0
  142. package/dist/esm/optimizer.js +663 -0
  143. package/dist/esm/optimizer.js.map +1 -0
  144. package/dist/esm/package.json +3 -0
  145. package/dist/esm/retry.d.ts +55 -0
  146. package/dist/esm/retry.d.ts.map +1 -0
  147. package/dist/esm/retry.js +123 -0
  148. package/dist/esm/retry.js.map +1 -0
  149. package/dist/esm/server/handler.d.ts +80 -0
  150. package/dist/esm/server/handler.d.ts.map +1 -0
  151. package/dist/esm/server/handler.js +151 -0
  152. package/dist/esm/server/handler.js.map +1 -0
  153. package/dist/esm/sniff.d.ts +37 -0
  154. package/dist/esm/sniff.d.ts.map +1 -0
  155. package/dist/esm/sniff.js +435 -0
  156. package/dist/esm/sniff.js.map +1 -0
  157. package/dist/esm/source.d.ts +45 -0
  158. package/dist/esm/source.d.ts.map +1 -0
  159. package/dist/esm/source.js +183 -0
  160. package/dist/esm/source.js.map +1 -0
  161. package/dist/esm/srcset.d.ts +66 -0
  162. package/dist/esm/srcset.d.ts.map +1 -0
  163. package/dist/esm/srcset.js +76 -0
  164. package/dist/esm/srcset.js.map +1 -0
  165. package/dist/esm/types.d.ts +441 -0
  166. package/dist/esm/types.d.ts.map +1 -0
  167. package/dist/esm/types.js +11 -0
  168. package/dist/esm/types.js.map +1 -0
  169. package/dist/esm/validation.d.ts +38 -0
  170. package/dist/esm/validation.d.ts.map +1 -0
  171. package/dist/esm/validation.js +74 -0
  172. package/dist/esm/validation.js.map +1 -0
  173. package/package.json +77 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Turning the many shapes of "an image" into bytes plus a filename.
3
+ *
4
+ * Node builtins only - `node:fs/promises` for paths, global `fetch` for remote
5
+ * URLs (Node 18+), and manual concatenation for async iterables.
6
+ */
7
+ import { basename, extname } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { formatBytes } from './bytes.js';
10
+ import { LensError, wrapError } from './errors.js';
11
+ const isAsyncIterable = (value) => typeof value === 'object' && value !== null && Symbol.asyncIterator in value;
12
+ /**
13
+ * Normalises any {@link ImageSource} into a buffer.
14
+ *
15
+ * @throws {LensError} `INVALID_SOURCE` for an unrecognised shape,
16
+ * `SOURCE_UNREADABLE` when the read itself fails,
17
+ * `VALIDATION_FAILED` when `maxBytes` is exceeded mid-read.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * await resolveSource('./photo.jpg');
22
+ * await resolveSource(request.body, { filename: 'upload.png', maxBytes: 5_000_000 });
23
+ * ```
24
+ */
25
+ export async function resolveSource(source, options = {}) {
26
+ const { filename: override } = options;
27
+ // { data, filename } wrapper - unwrap and recurse, keeping the inner filename
28
+ // unless the caller supplied an explicit override.
29
+ if (typeof source === 'object' &&
30
+ source !== null &&
31
+ !(source instanceof URL) &&
32
+ !ArrayBuffer.isView(source) &&
33
+ !isAsyncIterable(source) &&
34
+ 'data' in source) {
35
+ const wrapper = source;
36
+ const inner = await resolveSource(wrapper.data, {
37
+ ...options,
38
+ filename: override ?? wrapper.filename,
39
+ });
40
+ return wrapper.contentType ? { ...inner, contentType: wrapper.contentType } : inner;
41
+ }
42
+ if (typeof source === 'string') {
43
+ return readPath(source, override, options);
44
+ }
45
+ if (source instanceof URL) {
46
+ if (source.protocol === 'file:') {
47
+ return readPath(fileURLToPath(source), override, options);
48
+ }
49
+ if (source.protocol === 'http:' || source.protocol === 'https:') {
50
+ return readRemote(source, override, options);
51
+ }
52
+ throw new LensError('INVALID_SOURCE', `Unsupported URL protocol "${source.protocol}". Use a file:, http: or https: URL.`);
53
+ }
54
+ if (ArrayBuffer.isView(source)) {
55
+ const data = toUint8(source);
56
+ assertUnderLimit(data.byteLength, options.maxBytes);
57
+ return { data, filename: normalizeFilename(override ?? 'image'), origin: 'buffer' };
58
+ }
59
+ if (isAsyncIterable(source)) {
60
+ const data = await collect(source, options.maxBytes, options.signal);
61
+ return { data, filename: normalizeFilename(override ?? 'image'), origin: 'stream' };
62
+ }
63
+ throw new LensError('INVALID_SOURCE', 'source must be a file path, URL, Uint8Array, async iterable of chunks, or { data, filename }. ' +
64
+ `Received ${describe(source)}.`);
65
+ }
66
+ async function readPath(path, override, options) {
67
+ const { readFile, stat } = await import('node:fs/promises');
68
+ try {
69
+ if (options.maxBytes !== undefined) {
70
+ // Check the size before reading so an oversized file never lands in memory.
71
+ const info = await stat(path);
72
+ assertUnderLimit(info.size, options.maxBytes, path);
73
+ }
74
+ const buffer = await readFile(path, options.signal ? { signal: options.signal } : undefined);
75
+ return {
76
+ data: toUint8(buffer),
77
+ filename: normalizeFilename(override ?? basename(path)),
78
+ origin: 'path',
79
+ };
80
+ }
81
+ catch (error) {
82
+ if (LensError.is(error))
83
+ throw error;
84
+ if (error?.name === 'AbortError') {
85
+ throw new LensError('ABORTED', `Reading "${path}" was aborted.`, { cause: error });
86
+ }
87
+ throw wrapError('SOURCE_UNREADABLE', `Could not read image at "${path}"`, error, { path });
88
+ }
89
+ }
90
+ async function readRemote(url, override, options) {
91
+ if (!options.allowRemote) {
92
+ throw new LensError('INVALID_SOURCE', `Refusing to fetch "${url.href}". Remote sources are disabled by default because they are ` +
93
+ 'an SSRF surface; pass `allowRemote: true` to the optimizer once you have validated the host.', { details: { url: url.href } });
94
+ }
95
+ let response;
96
+ try {
97
+ response = await fetch(url, options.signal ? { signal: options.signal } : {});
98
+ }
99
+ catch (error) {
100
+ throw wrapError('SOURCE_UNREADABLE', `Fetching "${url.href}" failed`, error, { url: url.href });
101
+ }
102
+ if (!response.ok) {
103
+ throw new LensError('SOURCE_UNREADABLE', `Fetching "${url.href}" returned HTTP ${response.status} ${response.statusText}.`, { details: { url: url.href, status: response.status } });
104
+ }
105
+ const declared = Number(response.headers.get('content-length'));
106
+ if (Number.isFinite(declared) && declared > 0) {
107
+ assertUnderLimit(declared, options.maxBytes, url.href);
108
+ }
109
+ const data = toUint8(new Uint8Array(await response.arrayBuffer()));
110
+ assertUnderLimit(data.byteLength, options.maxBytes, url.href);
111
+ const contentType = response.headers.get('content-type') ?? undefined;
112
+ const derived = basename(url.pathname) || 'image';
113
+ return {
114
+ data,
115
+ filename: normalizeFilename(override ?? derived),
116
+ ...(contentType ? { contentType } : {}),
117
+ origin: 'url',
118
+ };
119
+ }
120
+ /** Drains an async iterable into one buffer, enforcing `maxBytes` as it goes. */
121
+ async function collect(stream, maxBytes, signal) {
122
+ const chunks = [];
123
+ let total = 0;
124
+ try {
125
+ for await (const chunk of stream) {
126
+ if (signal?.aborted) {
127
+ throw new LensError('ABORTED', 'Reading the source stream was aborted.');
128
+ }
129
+ const bytes = toUint8(chunk);
130
+ total += bytes.byteLength;
131
+ assertUnderLimit(total, maxBytes);
132
+ chunks.push(bytes);
133
+ }
134
+ }
135
+ catch (error) {
136
+ throw wrapError('SOURCE_UNREADABLE', 'Reading the source stream failed', error);
137
+ }
138
+ const out = new Uint8Array(total);
139
+ let offset = 0;
140
+ for (const chunk of chunks) {
141
+ out.set(chunk, offset);
142
+ offset += chunk.byteLength;
143
+ }
144
+ return out;
145
+ }
146
+ function assertUnderLimit(size, maxBytes, what) {
147
+ if (maxBytes !== undefined && size > maxBytes) {
148
+ throw new LensError('VALIDATION_FAILED', `Image is ${formatBytes(size)}${what ? ` ("${what}")` : ''}, over the ${formatBytes(maxBytes)} limit.`, { details: { limit: maxBytes, actual: size } });
149
+ }
150
+ }
151
+ /**
152
+ * Views any ArrayBufferView as a Uint8Array without copying.
153
+ *
154
+ * The byteOffset/byteLength dance matters: Node often hands you a Buffer that
155
+ * is a window onto a larger pooled allocation, and ignoring that ships the
156
+ * neighbouring bytes too.
157
+ */
158
+ function toUint8(view) {
159
+ return view instanceof Uint8Array && view.byteOffset === 0 && view.byteLength === view.buffer.byteLength
160
+ ? view
161
+ : new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
162
+ }
163
+ /** Strips directory separators and control characters from a filename. */
164
+ function normalizeFilename(name) {
165
+ const cleaned = name.split(/[\\/]/).pop() ?? 'image';
166
+ // eslint-disable-next-line no-control-regex
167
+ const safe = cleaned.replace(/[\x00-\x1f\x7f]/g, '').trim();
168
+ return safe.length > 0 ? safe : 'image';
169
+ }
170
+ /** Filename without its extension. */
171
+ export function stem(filename) {
172
+ const ext = extname(filename);
173
+ return ext ? filename.slice(0, -ext.length) : filename;
174
+ }
175
+ function describe(value) {
176
+ if (value === null)
177
+ return 'null';
178
+ if (Array.isArray(value))
179
+ return 'an array';
180
+ return typeof value;
181
+ }
182
+ export { formatBytes };
183
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Helpers for getting an {@link OptimizeResult} into HTML.
3
+ *
4
+ * These are pure functions over the result object, deliberately framework-free
5
+ * so they work in Express templates, Next.js, Astro or a string concatenation
6
+ * in a cron job.
7
+ */
8
+ import type { ImageFormat, OptimizeResult, Variant } from './types.js';
9
+ /**
10
+ * Builds a `srcset` attribute value from variants.
11
+ *
12
+ * Variants without a URL (process-only mode) and zero-width variants are
13
+ * skipped, and the list is sorted ascending so browsers pick sensibly.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * buildSrcset(result.formats.webp!.variants);
18
+ * // 'https://cdn/…-300w.webp 300w, https://cdn/…-600w.webp 600w'
19
+ * ```
20
+ */
21
+ export declare function buildSrcset(variants: readonly Variant[]): string;
22
+ /** One `<source>` element's worth of data. */
23
+ export interface PictureSource {
24
+ readonly type: string;
25
+ readonly srcset: string;
26
+ readonly sizes?: string;
27
+ }
28
+ /** Everything needed to render a `<picture>`. */
29
+ export interface PictureData {
30
+ /** In the order given to `optimize`, which is the order they should be tried. */
31
+ readonly sources: readonly PictureSource[];
32
+ /** `src` for the `<img>` fallback - the widest variant of the last format. */
33
+ readonly src: string;
34
+ readonly width: number;
35
+ readonly height: number;
36
+ readonly alt: string;
37
+ }
38
+ /**
39
+ * Reshapes a result into `<picture>`-ready data.
40
+ *
41
+ * Source order is the order formats were requested, which matters: the browser
42
+ * takes the first `<source>` it supports. Request `['avif', 'webp', 'jpeg']`
43
+ * and you get exactly the progressive-enhancement ladder you wanted.
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * const pic = toPicture(result, { alt: 'Cabin at dusk', sizes: '(max-width: 768px) 100vw, 50vw' });
48
+ *
49
+ * <picture>
50
+ * {pic.sources.map((s) => <source key={s.type} type={s.type} srcSet={s.srcset} sizes={s.sizes} />)}
51
+ * <img src={pic.src} width={pic.width} height={pic.height} alt={pic.alt} loading="lazy" />
52
+ * </picture>
53
+ * ```
54
+ */
55
+ export declare function toPicture(result: OptimizeResult, options?: {
56
+ alt?: string;
57
+ sizes?: string;
58
+ }): PictureData;
59
+ /**
60
+ * Picks the variant closest to a target width without going under it, so the
61
+ * rendered image is never upscaled.
62
+ *
63
+ * @example `pickVariant(result, 800, 'webp')`
64
+ */
65
+ export declare function pickVariant(result: OptimizeResult, targetWidth: number, format?: ImageFormat): Variant | undefined;
66
+ //# sourceMappingURL=srcset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"srcset.d.ts","sourceRoot":"","sources":["../../src/srcset.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAgB,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAMhE;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,WAAW,CAsBb;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,GAAG,SAAS,CAIrB"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Helpers for getting an {@link OptimizeResult} into HTML.
3
+ *
4
+ * These are pure functions over the result object, deliberately framework-free
5
+ * so they work in Express templates, Next.js, Astro or a string concatenation
6
+ * in a cron job.
7
+ */
8
+ import { mimeTypeFor } from './sniff.js';
9
+ /**
10
+ * Builds a `srcset` attribute value from variants.
11
+ *
12
+ * Variants without a URL (process-only mode) and zero-width variants are
13
+ * skipped, and the list is sorted ascending so browsers pick sensibly.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * buildSrcset(result.formats.webp!.variants);
18
+ * // 'https://cdn/…-300w.webp 300w, https://cdn/…-600w.webp 600w'
19
+ * ```
20
+ */
21
+ export function buildSrcset(variants) {
22
+ return [...variants]
23
+ .filter((v) => v.url && v.width > 0)
24
+ .sort((a, b) => a.width - b.width)
25
+ .map((v) => `${v.url} ${v.width}w`)
26
+ .join(', ');
27
+ }
28
+ /**
29
+ * Reshapes a result into `<picture>`-ready data.
30
+ *
31
+ * Source order is the order formats were requested, which matters: the browser
32
+ * takes the first `<source>` it supports. Request `['avif', 'webp', 'jpeg']`
33
+ * and you get exactly the progressive-enhancement ladder you wanted.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const pic = toPicture(result, { alt: 'Cabin at dusk', sizes: '(max-width: 768px) 100vw, 50vw' });
38
+ *
39
+ * <picture>
40
+ * {pic.sources.map((s) => <source key={s.type} type={s.type} srcSet={s.srcset} sizes={s.sizes} />)}
41
+ * <img src={pic.src} width={pic.width} height={pic.height} alt={pic.alt} loading="lazy" />
42
+ * </picture>
43
+ * ```
44
+ */
45
+ export function toPicture(result, options = {}) {
46
+ const groups = Object.values(result.formats).filter(Boolean);
47
+ const sources = groups
48
+ .filter((group) => group.srcset.length > 0)
49
+ .map((group) => ({
50
+ type: mimeTypeFor(group.format),
51
+ srcset: group.srcset,
52
+ ...(options.sizes ? { sizes: options.sizes } : {}),
53
+ }));
54
+ // The <img> fallback should be the most compatible format available, which is
55
+ // the last one requested in a properly ordered format list.
56
+ const fallback = groups[groups.length - 1]?.largest;
57
+ return {
58
+ sources,
59
+ src: fallback?.url ?? '',
60
+ width: fallback?.width ?? result.source.width,
61
+ height: fallback?.height ?? result.source.height,
62
+ alt: options.alt ?? '',
63
+ };
64
+ }
65
+ /**
66
+ * Picks the variant closest to a target width without going under it, so the
67
+ * rendered image is never upscaled.
68
+ *
69
+ * @example `pickVariant(result, 800, 'webp')`
70
+ */
71
+ export function pickVariant(result, targetWidth, format) {
72
+ const pool = format ? (result.formats[format]?.variants ?? []) : result.variants;
73
+ const sorted = [...pool].sort((a, b) => a.width - b.width);
74
+ return sorted.find((v) => v.width >= targetWidth) ?? sorted[sorted.length - 1];
75
+ }
76
+ //# sourceMappingURL=srcset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"srcset.js","sourceRoot":"","sources":["../../src/srcset.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,QAA4B;IACtD,OAAO,CAAC,GAAG,QAAQ,CAAC;SACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;SACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC;SAClC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAoBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CACvB,MAAsB,EACtB,UAA4C,EAAE;IAE9C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAmB,CAAC;IAE/E,MAAM,OAAO,GAAG,MAAM;SACnB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;SAC1C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC;QAC/B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnD,CAAC,CAAC,CAAC;IAEN,8EAA8E;IAC9E,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC;IAEpD,OAAO;QACL,OAAO;QACP,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE;QACxB,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK;QAC7C,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM;QAChD,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE;KACvB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,MAAsB,EACtB,WAAmB,EACnB,MAAoB;IAEpB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IACjF,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjF,CAAC"}