@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,63 @@
1
+ /**
2
+ * A codec-free {@link ImageEngine}.
3
+ *
4
+ * It cannot resize or transcode - it has no decoder. What it *can* do is read
5
+ * dimensions from the header and hand the original bytes back unchanged, which
6
+ * covers three real cases:
7
+ *
8
+ * 1. **Tests.** Deterministic, instant, no native binary in CI.
9
+ * 2. **Store-only pipelines.** Validate, content-hash, key and upload an image
10
+ * without re-encoding it.
11
+ * 3. **Graceful degradation.** When sharp is missing, the optimizer can still
12
+ * store the original rather than failing the whole request - the user's
13
+ * upload survives, just unoptimised.
14
+ */
15
+ import { LensError } from './../errors.js';
16
+ import { sniff } from './../sniff.js';
17
+ /**
18
+ * Creates the passthrough engine.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Validate and store originals, no codec required.
23
+ * const optimizer = new ImageOptimizer({
24
+ * engine: createPassthroughEngine(),
25
+ * adapter: new LocalAdapter({ root: './uploads' }),
26
+ * });
27
+ * ```
28
+ */
29
+ export function createPassthroughEngine(options = {}) {
30
+ const { onUnsupported = 'throw' } = options;
31
+ return {
32
+ name: 'passthrough',
33
+ supports() {
34
+ // It can "produce" any format only in the sense of not changing it. The
35
+ // optimizer relies on transform() to reject genuine conversions.
36
+ return true;
37
+ },
38
+ async probe(input) {
39
+ return sniff(input);
40
+ },
41
+ async transform(input, op) {
42
+ const meta = sniff(input);
43
+ const sameFormat = meta.format === op.format;
44
+ const needsResize = op.resize !== undefined &&
45
+ ((op.resize.width !== undefined && op.resize.width !== meta.width) ||
46
+ (op.resize.height !== undefined && op.resize.height !== meta.height));
47
+ if ((!sameFormat || needsResize) && onUnsupported === 'throw') {
48
+ throw new LensError('ENCODE_FAILED', `The passthrough engine cannot ${!sameFormat ? `convert ${meta.format} to ${op.format}` : 'resize images'}. ` +
49
+ 'Install sharp (`npm install sharp`) to enable real image processing.', { details: { engine: 'passthrough', format: op.format, detected: meta.format } });
50
+ }
51
+ return {
52
+ // Copy so a caller mutating the result cannot corrupt the source buffer
53
+ // that other variants in the same run are still reading from.
54
+ data: input.slice(),
55
+ format: (sameFormat ? op.format : meta.format) ?? op.format,
56
+ width: meta.width,
57
+ height: meta.height,
58
+ size: input.byteLength,
59
+ };
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=passthrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passthrough.js","sourceRoot":"","sources":["../../../src/engines/passthrough.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAgBtC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAAoC,EAAE;IAC5E,MAAM,EAAE,aAAa,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC;IAE5C,OAAO;QACL,IAAI,EAAE,aAAa;QAEnB,QAAQ;YACN,wEAAwE;YACxE,iEAAiE;YACjE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,KAAiB;YAC3B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,KAAiB,EAAE,EAAe;YAChD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,KAAM,EAAE,CAAC,MAAiB,CAAC;YACzD,MAAM,WAAW,GACf,EAAE,CAAC,MAAM,KAAK,SAAS;gBACvB,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC;oBAChE,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAE1E,IAAI,CAAC,CAAC,UAAU,IAAI,WAAW,CAAC,IAAI,aAAa,KAAK,OAAO,EAAE,CAAC;gBAC9D,MAAM,IAAI,SAAS,CACjB,eAAe,EACf,iCAAiC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI;oBAC3G,sEAAsE,EACxE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CACjF,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,wEAAwE;gBACxE,8DAA8D;gBAC9D,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE;gBACnB,MAAM,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAE,IAAI,CAAC,MAAsB,CAAC,IAAI,EAAE,CAAC,MAAM;gBAC5E,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,IAAI,EAAE,KAAK,CAAC,UAAU;aACvB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The sharp-backed {@link ImageEngine}.
3
+ *
4
+ * ### Why this is not a dependency
5
+ *
6
+ * `@lens-image/core` declares sharp as an *optional peer*. Nothing in this file is
7
+ * evaluated until you actually call the engine, and the import is dynamic, so
8
+ * `npm install @lens-image/core` pulls in zero packages and installs zero native
9
+ * binaries. If you never resize an image - you only validate and re-key, say -
10
+ * you never need sharp at all.
11
+ *
12
+ * The moment you do need it, you run `npm install sharp` yourself, which means
13
+ * the native binary, its platform matrix and its release cadence are all
14
+ * visible in *your* lockfile rather than smuggled in under ours.
15
+ */
16
+ import type { ImageEngine } from './../types.js';
17
+ /** Options for {@link createSharpEngine}. */
18
+ export interface SharpEngineOptions {
19
+ /**
20
+ * libvips operation cache. Defaults to `false`.
21
+ *
22
+ * sharp caches decoded operations globally, which is a memory leak shaped
23
+ * like a performance feature in a long-lived server that processes unbounded
24
+ * user uploads. Turn it on only if you re-process the same images repeatedly.
25
+ */
26
+ readonly cache?: false | {
27
+ memory?: number;
28
+ files?: number;
29
+ items?: number;
30
+ };
31
+ /**
32
+ * libvips worker threads per operation. Defaults to sharp's own choice.
33
+ *
34
+ * Set this to 1 when you are already running several optimize jobs in
35
+ * parallel: `concurrency: 4` in Lens plus 4 libvips threads each is 16
36
+ * threads fighting over the same cores.
37
+ */
38
+ readonly threads?: number;
39
+ /** Pixel limit handed to sharp's own decoder guard. Default 100 megapixels. */
40
+ readonly pixelLimit?: number;
41
+ /** Pull every frame of an animated GIF/WebP through the pipeline. Default `false`. */
42
+ readonly animated?: boolean;
43
+ }
44
+ /**
45
+ * Creates an {@link ImageEngine} backed by sharp.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const optimizer = new ImageOptimizer({
50
+ * engine: createSharpEngine({ threads: 1 }), // we do our own parallelism
51
+ * adapter,
52
+ * });
53
+ * ```
54
+ */
55
+ export declare function createSharpEngine(options?: SharpEngineOptions): ImageEngine;
56
+ /** Test seam: forget the memoised sharp module. */
57
+ export declare function resetSharpCache(): void;
58
+ //# sourceMappingURL=sharp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharp.d.ts","sourceRoot":"","sources":["../../../src/engines/sharp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAgB,WAAW,EAA2C,MAAM,eAAe,CAAC;AAoExG,6CAA6C;AAC7C,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE7E;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAuG/E;AAkDD,mDAAmD;AACnD,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * The sharp-backed {@link ImageEngine}.
3
+ *
4
+ * ### Why this is not a dependency
5
+ *
6
+ * `@lens-image/core` declares sharp as an *optional peer*. Nothing in this file is
7
+ * evaluated until you actually call the engine, and the import is dynamic, so
8
+ * `npm install @lens-image/core` pulls in zero packages and installs zero native
9
+ * binaries. If you never resize an image - you only validate and re-key, say -
10
+ * you never need sharp at all.
11
+ *
12
+ * The moment you do need it, you run `npm install sharp` yourself, which means
13
+ * the native binary, its platform matrix and its release cadence are all
14
+ * visible in *your* lockfile rather than smuggled in under ours.
15
+ */
16
+ import { LensError, wrapError } from './../errors.js';
17
+ import { defaultEffort } from './../format.js';
18
+ /**
19
+ * Formats whose capability is reported under a different key.
20
+ *
21
+ * sharp reports support per *container*, and AVIF is not one of its containers:
22
+ * AVIF is HEIF with AV1 compression, so `sharp.format.avif` is `undefined` even
23
+ * on builds that encode AVIF perfectly well. Checking only the literal name
24
+ * makes every AVIF request degrade to JPEG for no reason - the exact failure
25
+ * the fallback exists to handle, triggered by a bug rather than a real
26
+ * limitation.
27
+ *
28
+ * The first entry that reports an encoder wins.
29
+ */
30
+ const CAPABILITY_KEYS = {
31
+ avif: ['avif', 'heif'],
32
+ };
33
+ let cached;
34
+ /**
35
+ * Resolves sharp, memoising both success and failure.
36
+ *
37
+ * The error message is the one a developer will actually read when their
38
+ * deploy breaks, so it says what to install rather than echoing
39
+ * `ERR_MODULE_NOT_FOUND`.
40
+ */
41
+ async function loadSharp() {
42
+ cached ??= (async () => {
43
+ try {
44
+ // The template literal defeats bundler static analysis, so tools like
45
+ // webpack and esbuild do not try to resolve sharp at build time in
46
+ // projects that never use it.
47
+ const specifier = 'sharp';
48
+ const mod = (await import(/* @vite-ignore */ `${specifier}`));
49
+ return ('default' in mod ? mod.default : mod);
50
+ }
51
+ catch (error) {
52
+ throw new LensError('ENGINE_UNAVAILABLE', 'Image processing needs sharp, which is not installed. Run `npm install sharp`. ' +
53
+ 'It is an optional peer dependency of @lens-image/core so that installing the core ' +
54
+ 'library never pulls in a native binary you might not need.', { cause: error, details: { engine: 'sharp' } });
55
+ }
56
+ })();
57
+ return cached;
58
+ }
59
+ /**
60
+ * Creates an {@link ImageEngine} backed by sharp.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const optimizer = new ImageOptimizer({
65
+ * engine: createSharpEngine({ threads: 1 }), // we do our own parallelism
66
+ * adapter,
67
+ * });
68
+ * ```
69
+ */
70
+ export function createSharpEngine(options = {}) {
71
+ const { cache = false, threads, pixelLimit = 100_000_000, animated = false } = options;
72
+ let configured = false;
73
+ async function sharp() {
74
+ const mod = await loadSharp();
75
+ if (!configured) {
76
+ configured = true;
77
+ mod.cache?.(cache);
78
+ if (threads !== undefined)
79
+ mod.concurrency?.(threads);
80
+ }
81
+ return mod;
82
+ }
83
+ return {
84
+ name: 'sharp',
85
+ async supports(format) {
86
+ try {
87
+ const mod = await sharp();
88
+ // Capability depends on how libvips was built - AVIF in particular is
89
+ // missing from some prebuilt binaries - so this asks sharp rather than
90
+ // assuming. See CAPABILITY_KEYS for why the name is not always literal.
91
+ const keys = CAPABILITY_KEYS[format] ?? [format];
92
+ return keys.some((key) => Boolean(mod.format[key]?.output));
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ },
98
+ async probe(input) {
99
+ const mod = await sharp();
100
+ try {
101
+ const meta = (await mod(input, { limitInputPixels: pixelLimit }).metadata());
102
+ return {
103
+ format: meta.format ?? 'unknown',
104
+ width: meta.width ?? 0,
105
+ height: meta.height ?? 0,
106
+ size: meta.size ?? input.byteLength,
107
+ hasAlpha: meta.hasAlpha ?? false,
108
+ isAnimated: (meta.pages ?? 1) > 1,
109
+ ...(meta.orientation !== undefined ? { orientation: meta.orientation } : {}),
110
+ ...(meta.space !== undefined ? { space: meta.space } : {}),
111
+ };
112
+ }
113
+ catch (error) {
114
+ throw wrapError('UNSUPPORTED_INPUT', 'sharp could not read this image', error, {
115
+ engine: 'sharp',
116
+ });
117
+ }
118
+ },
119
+ async transform(input, op) {
120
+ const mod = await sharp();
121
+ try {
122
+ let pipeline = mod(input, { limitInputPixels: pixelLimit, animated });
123
+ // Auto-orient first: rotating after a resize would resize against the
124
+ // wrong axis for any photo shot in portrait on a phone.
125
+ if (op.autoOrient)
126
+ pipeline = pipeline.rotate();
127
+ if (op.resize) {
128
+ pipeline = pipeline.resize({
129
+ ...(op.resize.width !== undefined ? { width: op.resize.width } : {}),
130
+ ...(op.resize.height !== undefined ? { height: op.resize.height } : {}),
131
+ fit: op.resize.fit ?? 'cover',
132
+ position: op.resize.position ?? 'center',
133
+ withoutEnlargement: op.resize.withoutEnlargement ?? true,
134
+ ...(op.resize.background ? { background: op.resize.background } : {}),
135
+ });
136
+ }
137
+ pipeline = pipeline.toFormat(op.format, encoderOptions(op));
138
+ // sharp strips metadata unless asked; `withMetadata()` keeps EXIF/ICC.
139
+ if (op.preserveMetadata)
140
+ pipeline = pipeline.withMetadata();
141
+ const { data, info } = await pipeline.toBuffer({ resolveWithObject: true });
142
+ return {
143
+ data,
144
+ format: op.format,
145
+ width: info.width,
146
+ height: info.height,
147
+ size: info.size ?? data.byteLength,
148
+ };
149
+ }
150
+ catch (error) {
151
+ throw wrapError('ENCODE_FAILED', `sharp failed to encode ${op.format}`, error, { engine: 'sharp', format: op.format });
152
+ }
153
+ },
154
+ };
155
+ }
156
+ /** Maps a {@link TransformOp} onto sharp's per-format encoder options. */
157
+ function encoderOptions(op) {
158
+ const effort = op.effort ?? defaultEffort(op.format);
159
+ const base = { quality: op.quality };
160
+ switch (op.format) {
161
+ case 'jpeg':
162
+ return {
163
+ ...base,
164
+ progressive: op.progressive ?? true,
165
+ // 4:4:4 avoids the colour smearing that 4:2:0 causes on saturated reds
166
+ // and fine text, which is exactly what high-quality output is for.
167
+ chromaSubsampling: op.chromaSubsampling ?? (op.quality >= 90 ? '4:4:4' : '4:2:0'),
168
+ mozjpeg: true,
169
+ };
170
+ case 'png':
171
+ return {
172
+ progressive: op.progressive ?? false,
173
+ compressionLevel: 9,
174
+ // Palette quantisation is where PNG size actually comes from; without
175
+ // it `quality` is ignored entirely and output is often larger than input.
176
+ palette: true,
177
+ quality: op.quality,
178
+ effort: effort ?? 7,
179
+ };
180
+ case 'webp':
181
+ return {
182
+ ...base,
183
+ ...(op.lossless !== undefined ? { lossless: op.lossless } : {}),
184
+ ...(effort !== undefined ? { effort } : {}),
185
+ smartSubsample: true,
186
+ };
187
+ case 'avif':
188
+ return {
189
+ ...base,
190
+ ...(op.lossless !== undefined ? { lossless: op.lossless } : {}),
191
+ ...(effort !== undefined ? { effort } : {}),
192
+ chromaSubsampling: op.chromaSubsampling ?? '4:4:4',
193
+ };
194
+ case 'gif':
195
+ return { ...(effort !== undefined ? { effort } : {}) };
196
+ case 'tiff':
197
+ return { ...base, compression: 'lzw' };
198
+ default:
199
+ return base;
200
+ }
201
+ }
202
+ /** Test seam: forget the memoised sharp module. */
203
+ export function resetSharpCache() {
204
+ cached = undefined;
205
+ }
206
+ //# sourceMappingURL=sharp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharp.js","sourceRoot":"","sources":["../../../src/engines/sharp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Error taxonomy.
3
+ *
4
+ * Every error Lens throws is a {@link LensError} with a stable `code`, so you
5
+ * can branch on failure without string-matching messages. Messages are written
6
+ * for the developer reading a stack trace at 2am: they say what was expected,
7
+ * what happened, and what to do about it.
8
+ */
9
+ import type { DetectedFormat, ImageFormat } from './types.js';
10
+ /** Stable, machine-readable failure codes. */
11
+ export type LensErrorCode =
12
+ /** The `source` value was not a shape Lens understands. */
13
+ 'INVALID_SOURCE'
14
+ /** The file could not be read from disk or the network. */
15
+ | 'SOURCE_UNREADABLE'
16
+ /** Bytes were read but are not a recognisable image. */
17
+ | 'UNSUPPORTED_INPUT'
18
+ /** Input failed a `validate` rule (size, dimensions, format allowlist). */
19
+ | 'VALIDATION_FAILED'
20
+ /** No codec is available to do the work. Usually: sharp is not installed. */
21
+ | 'ENGINE_UNAVAILABLE'
22
+ /** The codec threw while encoding. */
23
+ | 'ENCODE_FAILED'
24
+ /** Every requested format failed, including the fallback. */
25
+ | 'ALL_FORMATS_FAILED'
26
+ /** The storage adapter threw, after exhausting retries. */
27
+ | 'UPLOAD_FAILED'
28
+ /** An adapter was required for the operation but none was configured. */
29
+ | 'ADAPTER_REQUIRED'
30
+ /** The adapter does not implement an optional method this call needed. */
31
+ | 'ADAPTER_UNSUPPORTED'
32
+ /** Adapter constructor received bad or missing configuration. */
33
+ | 'ADAPTER_MISCONFIGURED'
34
+ /** The caller's `AbortSignal` fired. */
35
+ | 'ABORTED'
36
+ /** Option values were contradictory or out of range. */
37
+ | 'INVALID_OPTIONS';
38
+ /** Extra context carried on a {@link LensError}. */
39
+ export interface LensErrorDetails {
40
+ readonly format?: ImageFormat;
41
+ readonly detected?: DetectedFormat;
42
+ readonly key?: string;
43
+ readonly adapter?: string;
44
+ readonly engine?: string;
45
+ readonly limit?: number;
46
+ readonly actual?: number;
47
+ readonly attempts?: number;
48
+ readonly [key: string]: unknown;
49
+ }
50
+ /**
51
+ * Base error for everything this library throws.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * try {
56
+ * await optimizer.optimize({ source: buf });
57
+ * } catch (err) {
58
+ * if (LensError.is(err, 'VALIDATION_FAILED')) return res.status(400).json({ error: err.message });
59
+ * throw err;
60
+ * }
61
+ * ```
62
+ */
63
+ export declare class LensError extends Error {
64
+ /** Stable failure code. Branch on this, not on `message`. */
65
+ readonly code: LensErrorCode;
66
+ /** Structured context: limits, keys, formats involved. */
67
+ readonly details: LensErrorDetails;
68
+ /** A suggested HTTP status, so route handlers do not have to map codes. */
69
+ readonly status: number;
70
+ constructor(code: LensErrorCode, message: string, options?: {
71
+ cause?: unknown;
72
+ details?: LensErrorDetails;
73
+ });
74
+ /**
75
+ * Type guard. Pass a `code` to narrow further.
76
+ *
77
+ * @example `if (LensError.is(err, 'UPLOAD_FAILED')) ...`
78
+ */
79
+ static is(error: unknown, code?: LensErrorCode): error is LensError;
80
+ /** JSON-safe representation, suitable for logs and API responses. */
81
+ toJSON(): Record<string, unknown>;
82
+ }
83
+ /** Throws `ABORTED` if the signal has already fired. Cheap enough to call often. */
84
+ export declare function throwIfAborted(signal: AbortSignal | undefined, what?: string): void;
85
+ /**
86
+ * Wraps a non-Lens error, preserving the original as `cause`.
87
+ * Existing {@link LensError}s pass through untouched so codes are not lost.
88
+ */
89
+ export declare function wrapError(code: LensErrorCode, message: string, cause: unknown, details?: LensErrorDetails): LensError;
90
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9D,8CAA8C;AAC9C,MAAM,MAAM,aAAa;AACvB,2DAA2D;AACzD,gBAAgB;AAClB,2DAA2D;GACzD,mBAAmB;AACrB,wDAAwD;GACtD,mBAAmB;AACrB,2EAA2E;GACzE,mBAAmB;AACrB,6EAA6E;GAC3E,oBAAoB;AACtB,sCAAsC;GACpC,eAAe;AACjB,6DAA6D;GAC3D,oBAAoB;AACtB,2DAA2D;GACzD,eAAe;AACjB,yEAAyE;GACvE,kBAAkB;AACpB,0EAA0E;GACxE,qBAAqB;AACvB,iEAAiE;GAC/D,uBAAuB;AACzB,wCAAwC;GACtC,SAAS;AACX,wDAAwD;GACtD,iBAAiB,CAAC;AAEtB,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAE7B,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IAEnC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAGtB,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;KAAO;IAU/D;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,KAAK,IAAI,SAAS;IAKnE,qEAAqE;IACrE,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASlC;AAkBD,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,SAAc,GAAG,IAAI,CAMxF;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,gBAAgB,GACzB,SAAS,CAIX"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Error taxonomy.
3
+ *
4
+ * Every error Lens throws is a {@link LensError} with a stable `code`, so you
5
+ * can branch on failure without string-matching messages. Messages are written
6
+ * for the developer reading a stack trace at 2am: they say what was expected,
7
+ * what happened, and what to do about it.
8
+ */
9
+ /**
10
+ * Base error for everything this library throws.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * try {
15
+ * await optimizer.optimize({ source: buf });
16
+ * } catch (err) {
17
+ * if (LensError.is(err, 'VALIDATION_FAILED')) return res.status(400).json({ error: err.message });
18
+ * throw err;
19
+ * }
20
+ * ```
21
+ */
22
+ export class LensError extends Error {
23
+ /** Stable failure code. Branch on this, not on `message`. */
24
+ code;
25
+ /** Structured context: limits, keys, formats involved. */
26
+ details;
27
+ /** A suggested HTTP status, so route handlers do not have to map codes. */
28
+ status;
29
+ constructor(code, message, options = {}) {
30
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
31
+ this.name = 'LensError';
32
+ this.code = code;
33
+ this.details = options.details ?? {};
34
+ this.status = STATUS_BY_CODE[code] ?? 500;
35
+ Error.captureStackTrace?.(this, LensError);
36
+ }
37
+ /**
38
+ * Type guard. Pass a `code` to narrow further.
39
+ *
40
+ * @example `if (LensError.is(err, 'UPLOAD_FAILED')) ...`
41
+ */
42
+ static is(error, code) {
43
+ if (!(error instanceof LensError))
44
+ return false;
45
+ return code === undefined || error.code === code;
46
+ }
47
+ /** JSON-safe representation, suitable for logs and API responses. */
48
+ toJSON() {
49
+ return {
50
+ name: this.name,
51
+ code: this.code,
52
+ message: this.message,
53
+ status: this.status,
54
+ details: this.details,
55
+ };
56
+ }
57
+ }
58
+ const STATUS_BY_CODE = {
59
+ INVALID_SOURCE: 400,
60
+ SOURCE_UNREADABLE: 400,
61
+ UNSUPPORTED_INPUT: 415,
62
+ VALIDATION_FAILED: 422,
63
+ INVALID_OPTIONS: 400,
64
+ ABORTED: 499,
65
+ ENGINE_UNAVAILABLE: 500,
66
+ ENCODE_FAILED: 500,
67
+ ALL_FORMATS_FAILED: 500,
68
+ UPLOAD_FAILED: 502,
69
+ ADAPTER_REQUIRED: 500,
70
+ ADAPTER_UNSUPPORTED: 501,
71
+ ADAPTER_MISCONFIGURED: 500,
72
+ };
73
+ /** Throws `ABORTED` if the signal has already fired. Cheap enough to call often. */
74
+ export function throwIfAborted(signal, what = 'operation') {
75
+ if (signal?.aborted) {
76
+ throw new LensError('ABORTED', `Lens ${what} was aborted by the caller's AbortSignal.`, {
77
+ cause: signal.reason,
78
+ });
79
+ }
80
+ }
81
+ /**
82
+ * Wraps a non-Lens error, preserving the original as `cause`.
83
+ * Existing {@link LensError}s pass through untouched so codes are not lost.
84
+ */
85
+ export function wrapError(code, message, cause, details) {
86
+ if (LensError.is(cause))
87
+ return cause;
88
+ const reason = cause instanceof Error ? cause.message : String(cause);
89
+ return new LensError(code, `${message} (${reason})`, { cause, details });
90
+ }
91
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8CH;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,6DAA6D;IACpD,IAAI,CAAgB;IAE7B,0DAA0D;IACjD,OAAO,CAAmB;IAEnC,2EAA2E;IAClE,MAAM,CAAS;IAExB,YACE,IAAmB,EACnB,OAAe,EACf,UAA2D,EAAE;QAE7D,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;QAC1C,KAAK,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,KAAc,EAAE,IAAoB;QAC5C,IAAI,CAAC,CAAC,KAAK,YAAY,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAChD,OAAO,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;IACnD,CAAC;IAED,qEAAqE;IACrE,MAAM;QACJ,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC;IACJ,CAAC;CACF;AAED,MAAM,cAAc,GAA2C;IAC7D,cAAc,EAAE,GAAG;IACnB,iBAAiB,EAAE,GAAG;IACtB,iBAAiB,EAAE,GAAG;IACtB,iBAAiB,EAAE,GAAG;IACtB,eAAe,EAAE,GAAG;IACpB,OAAO,EAAE,GAAG;IACZ,kBAAkB,EAAE,GAAG;IACvB,aAAa,EAAE,GAAG;IAClB,kBAAkB,EAAE,GAAG;IACvB,aAAa,EAAE,GAAG;IAClB,gBAAgB,EAAE,GAAG;IACrB,mBAAmB,EAAE,GAAG;IACxB,qBAAqB,EAAE,GAAG;CAC3B,CAAC;AAEF,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,MAA+B,EAAE,IAAI,GAAG,WAAW;IAChF,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,QAAQ,IAAI,2CAA2C,EAAE;YACtF,KAAK,EAAE,MAAM,CAAC,MAAM;SACrB,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,IAAmB,EACnB,OAAe,EACf,KAAc,EACd,OAA0B;IAE1B,IAAI,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,OAAO,IAAI,SAAS,CAAC,IAAI,EAAE,GAAG,OAAO,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,33 @@
1
+ /** Format normalisation and per-format encoding defaults. */
2
+ import type { ImageFormat, ImageFormatInput } from './types.js';
3
+ /** Every format Lens can encode to. */
4
+ export declare const ENCODABLE_FORMATS: readonly ImageFormat[];
5
+ /**
6
+ * Maps a user-supplied format name onto its canonical form.
7
+ *
8
+ * `'jpg'` and `'JPEG'` both become `'jpeg'` so results are keyed consistently
9
+ * no matter how the caller spelled it.
10
+ *
11
+ * @throws {LensError} `INVALID_OPTIONS` for an unknown name.
12
+ */
13
+ export declare function normalizeFormat(input: ImageFormatInput | string): ImageFormat;
14
+ /** Normalises a list, dropping duplicates while preserving the caller's order. */
15
+ export declare function normalizeFormats(inputs: readonly (ImageFormatInput | string)[]): readonly ImageFormat[];
16
+ /** True when the format stores an alpha channel. */
17
+ export declare function supportsAlpha(format: ImageFormat): boolean;
18
+ /** True when `quality` has any effect on the encode. */
19
+ export declare function isLossy(format: ImageFormat): boolean;
20
+ /**
21
+ * Sensible per-format encoder defaults.
22
+ *
23
+ * AVIF gets a lower quality number than JPEG on purpose: the scales are not
24
+ * comparable, and AVIF at q80 is wastefully large for the same perceived
25
+ * result. Callers who pass an explicit quality always win over this.
26
+ */
27
+ export declare function defaultQuality(format: ImageFormat): number;
28
+ /**
29
+ * Default encoder effort. AVIF is slow enough that the top setting is a
30
+ * production hazard, so we sit mid-scale.
31
+ */
32
+ export declare function defaultEffort(format: ImageFormat): number | undefined;
33
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAG7D,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAchE,uCAAuC;AACvC,eAAO,MAAM,iBAAiB,EAAE,SAAS,WAAW,EAOnD,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,GAAG,WAAW,CAW7E;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,CAAC,gBAAgB,GAAG,MAAM,CAAC,EAAE,GAC7C,SAAS,WAAW,EAAE,CAWxB;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAE1D;AAED,wDAAwD;AACxD,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAW1D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CASrE"}