@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 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEhG,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,uBAAuB;AACvB,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,aAAc,YAAW,cAAc;;IAClD,QAAQ,CAAC,IAAI,YAAY;gBAOb,OAAO,GAAE,oBAAyB;IAKxC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA4BvE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAI3B,kCAAkC;IAClC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIzC,4CAA4C;IAC5C,IAAI,IAAI,MAAM,EAAE;IAIhB,gCAAgC;IAChC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mEAAmE;IACnE,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * An in-memory {@link StorageAdapter}.
3
+ *
4
+ * Ships in core on purpose: it is the reference implementation of the adapter
5
+ * contract (about 40 lines of real logic), and it makes every example in the
6
+ * README runnable without credentials or a filesystem.
7
+ */
8
+ /**
9
+ * @example
10
+ * ```ts
11
+ * const adapter = new MemoryAdapter();
12
+ * const optimizer = new ImageOptimizer({ adapter });
13
+ *
14
+ * await optimizer.optimize({ source: './photo.jpg', formats: ['webp'] });
15
+ * adapter.keys(); // ['a1b2c3d4e5/photo-original.webp']
16
+ * adapter.get(key)?.data; // the encoded bytes
17
+ * ```
18
+ */
19
+ export class MemoryAdapter {
20
+ name = 'memory';
21
+ #files = new Map();
22
+ #baseUrl;
23
+ #failAttempts;
24
+ #attempts = new Map();
25
+ constructor(options = {}) {
26
+ this.#baseUrl = options.baseUrl ?? 'memory://';
27
+ this.#failAttempts = options.failAttempts ?? 0;
28
+ }
29
+ async upload(file, _ctx) {
30
+ const seen = (this.#attempts.get(file.key) ?? 0) + 1;
31
+ this.#attempts.set(file.key, seen);
32
+ if (seen <= this.#failAttempts) {
33
+ // Shaped like a transient network error so the default retry predicate
34
+ // treats it as retryable.
35
+ const error = new Error(`Simulated transient failure ${seen}/${this.#failAttempts}`);
36
+ error.code = 'ECONNRESET';
37
+ throw error;
38
+ }
39
+ this.#files.set(file.key, {
40
+ key: file.key,
41
+ data: file.data,
42
+ contentType: file.contentType,
43
+ ...(file.cacheControl ? { cacheControl: file.cacheControl } : {}),
44
+ ...(file.metadata ? { metadata: file.metadata } : {}),
45
+ });
46
+ return {
47
+ key: file.key,
48
+ url: this.getUrl(file.key),
49
+ size: file.data.byteLength,
50
+ ...(file.checksum ? { etag: file.checksum } : {}),
51
+ };
52
+ }
53
+ async exists(key) {
54
+ return this.#files.has(key);
55
+ }
56
+ async remove(key) {
57
+ this.#files.delete(key);
58
+ }
59
+ getUrl(key) {
60
+ return `${this.#baseUrl}${key}`;
61
+ }
62
+ /** Reads back a stored object. */
63
+ get(key) {
64
+ return this.#files.get(key);
65
+ }
66
+ /** Every stored key, in insertion order. */
67
+ keys() {
68
+ return [...this.#files.keys()];
69
+ }
70
+ /** Number of stored objects. */
71
+ get size() {
72
+ return this.#files.size;
73
+ }
74
+ /** Drops every object and resets the injected-failure counters. */
75
+ clear() {
76
+ this.#files.clear();
77
+ this.#attempts.clear();
78
+ }
79
+ }
80
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/adapters/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `@lens-image/core/browser` - the part of Lens that runs anywhere.
3
+ *
4
+ * Everything re-exported here is pure computation over bytes and strings, with
5
+ * no `node:` imports at all. It works in a browser, a Worker, an edge runtime
6
+ * or React Native.
7
+ *
8
+ * ### Why this exists
9
+ *
10
+ * The main entry point imports `node:crypto` and `node:fs`, so a bundler
11
+ * targeting the browser either fails or ships a polyfill for them. But the
12
+ * *useful* half for a client is Node-free: you can identify an image and read
13
+ * its dimensions from its first few kilobytes, which means you can reject a
14
+ * 40,000 x 40,000 PNG **before** uploading it rather than after.
15
+ *
16
+ * @example Validate before the upload starts
17
+ * ```ts
18
+ * import { sniff, resolveValidation, validateImage, LensError } from '@lens-image/core/browser';
19
+ *
20
+ * const policy = resolveValidation({ maxBytes: 10_000_000, allowedFormats: ['jpeg', 'png', 'webp'] });
21
+ * const header = new Uint8Array(await file.slice(0, 4096).arrayBuffer());
22
+ *
23
+ * try {
24
+ * validateImage({ ...sniff(header, file.size) }, policy, file.name);
25
+ * } catch (error) {
26
+ * if (LensError.is(error)) showToast(error.message); // the same message the server would give
27
+ * }
28
+ * ```
29
+ *
30
+ * Note the `file.slice(0, 4096)` - only the header is read, so validating a
31
+ * 40 MB file costs 4 KB.
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+ export { sniff, sniffFormat, mimeTypeFor, extensionFor, SNIFF_HEADER_BYTES } from './sniff.js';
36
+ export { validateImage, resolveValidation, DEFAULT_MAX_BYTES, DEFAULT_MAX_PIXELS, DEFAULT_ALLOWED_FORMATS, } from './validation.js';
37
+ export type { ResolvedValidation } from './validation.js';
38
+ export { LensError } from './errors.js';
39
+ export type { LensErrorCode, LensErrorDetails } from './errors.js';
40
+ export { normalizeFormat, normalizeFormats, supportsAlpha, isLossy, defaultQuality, defaultEffort, ENCODABLE_FORMATS, } from './format.js';
41
+ export { buildKey, joinKey, slugify, defaultLabel, DEFAULT_KEY_TEMPLATE } from './naming.js';
42
+ export { buildSrcset, toPicture, pickVariant } from './srcset.js';
43
+ export type { PictureData, PictureSource } from './srcset.js';
44
+ export { formatBytes } from './bytes.js';
45
+ export type { DetectedFormat, FitMode, FormatResult, ImageFormat, ImageFormatInput, ImageMetadata, KeyContext, LensWarning, OptimizeResult, Position, ResizeSpec, SizeSpec, ValidationOptions, Variant, } from './types.js';
46
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAG/F,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGnE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,cAAc,EACd,aAAa,EACb,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAG7F,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,YAAY,EACV,cAAc,EACd,OAAO,EACP,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,OAAO,GACR,MAAM,YAAY,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `@lens-image/core/browser` - the part of Lens that runs anywhere.
3
+ *
4
+ * Everything re-exported here is pure computation over bytes and strings, with
5
+ * no `node:` imports at all. It works in a browser, a Worker, an edge runtime
6
+ * or React Native.
7
+ *
8
+ * ### Why this exists
9
+ *
10
+ * The main entry point imports `node:crypto` and `node:fs`, so a bundler
11
+ * targeting the browser either fails or ships a polyfill for them. But the
12
+ * *useful* half for a client is Node-free: you can identify an image and read
13
+ * its dimensions from its first few kilobytes, which means you can reject a
14
+ * 40,000 x 40,000 PNG **before** uploading it rather than after.
15
+ *
16
+ * @example Validate before the upload starts
17
+ * ```ts
18
+ * import { sniff, resolveValidation, validateImage, LensError } from '@lens-image/core/browser';
19
+ *
20
+ * const policy = resolveValidation({ maxBytes: 10_000_000, allowedFormats: ['jpeg', 'png', 'webp'] });
21
+ * const header = new Uint8Array(await file.slice(0, 4096).arrayBuffer());
22
+ *
23
+ * try {
24
+ * validateImage({ ...sniff(header, file.size) }, policy, file.name);
25
+ * } catch (error) {
26
+ * if (LensError.is(error)) showToast(error.message); // the same message the server would give
27
+ * }
28
+ * ```
29
+ *
30
+ * Note the `file.slice(0, 4096)` - only the header is read, so validating a
31
+ * 40 MB file costs 4 KB.
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+ // --- Format detection -------------------------------------------------------
36
+ export { sniff, sniffFormat, mimeTypeFor, extensionFor, SNIFF_HEADER_BYTES } from './sniff.js';
37
+ // --- Validation -------------------------------------------------------------
38
+ export { validateImage, resolveValidation, DEFAULT_MAX_BYTES, DEFAULT_MAX_PIXELS, DEFAULT_ALLOWED_FORMATS, } from './validation.js';
39
+ // --- Errors -----------------------------------------------------------------
40
+ export { LensError } from './errors.js';
41
+ // --- Formats ----------------------------------------------------------------
42
+ export { normalizeFormat, normalizeFormats, supportsAlpha, isLossy, defaultQuality, defaultEffort, ENCODABLE_FORMATS, } from './format.js';
43
+ // --- Key templating ---------------------------------------------------------
44
+ export { buildKey, joinKey, slugify, defaultLabel, DEFAULT_KEY_TEMPLATE } from './naming.js';
45
+ // --- HTML helpers -----------------------------------------------------------
46
+ export { buildSrcset, toPicture, pickVariant } from './srcset.js';
47
+ // --- Formatting -------------------------------------------------------------
48
+ export { formatBytes } from './bytes.js';
49
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,+EAA+E;AAC/E,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE/F,+EAA+E;AAC/E,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AAGzB,+EAA+E;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,+EAA+E;AAC/E,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,cAAc,EACd,aAAa,EACb,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7F,+EAA+E;AAC/E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGlE,+EAA+E;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Byte formatting.
3
+ *
4
+ * Its own module so that `validation.ts` - and therefore the whole `./browser`
5
+ * entry point - does not have to import `source.ts`, which pulls in `node:fs`
6
+ * and `node:path` at the top level.
7
+ */
8
+ /**
9
+ * Human-readable byte count, using binary units.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * formatBytes(900); // '900 B'
14
+ * formatBytes(5_242_880); // '5.0 MiB'
15
+ * ```
16
+ */
17
+ export declare function formatBytes(bytes: number): string;
18
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../../src/bytes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Byte formatting.
3
+ *
4
+ * Its own module so that `validation.ts` - and therefore the whole `./browser`
5
+ * entry point - does not have to import `source.ts`, which pulls in `node:fs`
6
+ * and `node:path` at the top level.
7
+ */
8
+ /**
9
+ * Human-readable byte count, using binary units.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * formatBytes(900); // '900 B'
14
+ * formatBytes(5_242_880); // '5.0 MiB'
15
+ * ```
16
+ */
17
+ export function formatBytes(bytes) {
18
+ if (!Number.isFinite(bytes) || bytes < 0)
19
+ return '0 B';
20
+ if (bytes < 1024)
21
+ return `${Math.round(bytes)} B`;
22
+ if (bytes < 1024 * 1024)
23
+ return `${(bytes / 1024).toFixed(1)} KiB`;
24
+ if (bytes < 1024 * 1024 * 1024)
25
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
26
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
27
+ }
28
+ //# sourceMappingURL=bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.js","sourceRoot":"","sources":["../../src/bytes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;IAClD,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IACnE,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IACnF,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Result caching.
3
+ *
4
+ * The expensive part of an optimize run is the encode, and the result is a pure
5
+ * function of (source bytes, options). So the cache key is a hash of both, and
6
+ * a hit skips encoding *and* uploading entirely.
7
+ *
8
+ * Three strategies, in increasing order of how much they trust the world:
9
+ *
10
+ * - `'memory'` - process-lifetime LRU. Fast, but a fresh deploy starts cold and
11
+ * a second instance does the work again.
12
+ * - `'storage'` - memory, plus `adapter.exists()` so a warm bucket short-circuits
13
+ * a cold process.
14
+ * - a {@link CacheStore} - your own Redis/SQLite, shared across instances.
15
+ */
16
+ import type { CacheStore, OptimizeResult } from './types.js';
17
+ /**
18
+ * A bounded LRU map.
19
+ *
20
+ * Insertion-ordered `Map` iteration gives us the eviction order for free: the
21
+ * first key `keys().next()` yields is the least recently used, because reads
22
+ * re-insert.
23
+ */
24
+ export declare class MemoryCacheStore implements CacheStore {
25
+ #private;
26
+ /** @param max - Entries to keep before evicting the least recently used. */
27
+ constructor(max?: number);
28
+ get(key: string): OptimizeResult | undefined;
29
+ set(key: string, value: OptimizeResult): void;
30
+ delete(key: string): void;
31
+ /** Drops every entry. */
32
+ clear(): void;
33
+ /** Current entry count. Handy in tests and metrics. */
34
+ get size(): number;
35
+ }
36
+ /**
37
+ * A cache store that never stores anything.
38
+ *
39
+ * Lets the optimizer treat "caching off" as just another store, rather than
40
+ * branching on null at every call site.
41
+ */
42
+ export declare const nullCacheStore: CacheStore;
43
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7D;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,UAAU;;IAIjD,4EAA4E;gBAChE,GAAG,SAAM;IAIrB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAS5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI;IAW7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,yBAAyB;IACzB,KAAK,IAAI,IAAI;IAIb,uDAAuD;IACvD,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,UAI5B,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Result caching.
3
+ *
4
+ * The expensive part of an optimize run is the encode, and the result is a pure
5
+ * function of (source bytes, options). So the cache key is a hash of both, and
6
+ * a hit skips encoding *and* uploading entirely.
7
+ *
8
+ * Three strategies, in increasing order of how much they trust the world:
9
+ *
10
+ * - `'memory'` - process-lifetime LRU. Fast, but a fresh deploy starts cold and
11
+ * a second instance does the work again.
12
+ * - `'storage'` - memory, plus `adapter.exists()` so a warm bucket short-circuits
13
+ * a cold process.
14
+ * - a {@link CacheStore} - your own Redis/SQLite, shared across instances.
15
+ */
16
+ /**
17
+ * A bounded LRU map.
18
+ *
19
+ * Insertion-ordered `Map` iteration gives us the eviction order for free: the
20
+ * first key `keys().next()` yields is the least recently used, because reads
21
+ * re-insert.
22
+ */
23
+ export class MemoryCacheStore {
24
+ #entries = new Map();
25
+ #max;
26
+ /** @param max - Entries to keep before evicting the least recently used. */
27
+ constructor(max = 500) {
28
+ this.#max = Math.max(1, Math.floor(max));
29
+ }
30
+ get(key) {
31
+ const value = this.#entries.get(key);
32
+ if (value === undefined)
33
+ return undefined;
34
+ // Re-insert to mark as most recently used.
35
+ this.#entries.delete(key);
36
+ this.#entries.set(key, value);
37
+ return value;
38
+ }
39
+ set(key, value) {
40
+ if (this.#entries.has(key))
41
+ this.#entries.delete(key);
42
+ this.#entries.set(key, value);
43
+ while (this.#entries.size > this.#max) {
44
+ const oldest = this.#entries.keys().next();
45
+ if (oldest.done)
46
+ break;
47
+ this.#entries.delete(oldest.value);
48
+ }
49
+ }
50
+ delete(key) {
51
+ this.#entries.delete(key);
52
+ }
53
+ /** Drops every entry. */
54
+ clear() {
55
+ this.#entries.clear();
56
+ }
57
+ /** Current entry count. Handy in tests and metrics. */
58
+ get size() {
59
+ return this.#entries.size;
60
+ }
61
+ }
62
+ /**
63
+ * A cache store that never stores anything.
64
+ *
65
+ * Lets the optimizer treat "caching off" as just another store, rather than
66
+ * branching on null at every call site.
67
+ */
68
+ export const nullCacheStore = {
69
+ get: () => undefined,
70
+ set: () => undefined,
71
+ delete: () => undefined,
72
+ };
73
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAClB,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC7C,IAAI,CAAS;IAEtB,4EAA4E;IAC5E,YAAY,GAAG,GAAG,GAAG;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC1C,2CAA2C;QAC3C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAqB;QACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAE9B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAM;YACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,yBAAyB;IACzB,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,uDAAuD;IACvD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;IACpB,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;IACpB,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;CACxB,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A ~40-line replacement for `p-map` / `p-limit`.
3
+ *
4
+ * Unbounded `Promise.all` over image encodes is how you turn a 12-variant
5
+ * upload into an out-of-memory crash: sharp holds the full decoded bitmap per
6
+ * in-flight job, so twelve 4K photos is several gigabytes of RSS.
7
+ */
8
+ /** Outcome of one task in a {@link mapLimit} run. */
9
+ export type Settled<T> = {
10
+ readonly status: 'fulfilled';
11
+ readonly value: T;
12
+ readonly index: number;
13
+ } | {
14
+ readonly status: 'rejected';
15
+ readonly reason: unknown;
16
+ readonly index: number;
17
+ };
18
+ /**
19
+ * Runs `worker` over `items` with at most `limit` in flight, preserving input
20
+ * order in the output.
21
+ *
22
+ * Unlike `Promise.all`, one rejection does not discard the work that succeeded:
23
+ * every task settles and you get a per-item verdict. That is what makes
24
+ * "the AVIF encoder died but the WebP is fine" a warning instead of a failure.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const results = await mapLimit(variants, 4, (v) => encodeAndUpload(v));
29
+ * const ok = results.filter((r) => r.status === 'fulfilled');
30
+ * ```
31
+ */
32
+ export declare function mapLimit<T, R>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<R>, signal?: AbortSignal): Promise<Settled<R>[]>;
33
+ /** Sleep that rejects promptly when the signal fires, instead of after the delay. */
34
+ export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
35
+ //# sourceMappingURL=concurrency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,qDAAqD;AACrD,MAAM,MAAM,OAAO,CAAC,CAAC,IACjB;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC9C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAkCvB;AAED,qFAAqF;AACrF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * A ~40-line replacement for `p-map` / `p-limit`.
3
+ *
4
+ * Unbounded `Promise.all` over image encodes is how you turn a 12-variant
5
+ * upload into an out-of-memory crash: sharp holds the full decoded bitmap per
6
+ * in-flight job, so twelve 4K photos is several gigabytes of RSS.
7
+ */
8
+ import { LensError, throwIfAborted } from './errors.js';
9
+ /**
10
+ * Runs `worker` over `items` with at most `limit` in flight, preserving input
11
+ * order in the output.
12
+ *
13
+ * Unlike `Promise.all`, one rejection does not discard the work that succeeded:
14
+ * every task settles and you get a per-item verdict. That is what makes
15
+ * "the AVIF encoder died but the WebP is fine" a warning instead of a failure.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const results = await mapLimit(variants, 4, (v) => encodeAndUpload(v));
20
+ * const ok = results.filter((r) => r.status === 'fulfilled');
21
+ * ```
22
+ */
23
+ export async function mapLimit(items, limit, worker, signal) {
24
+ if (items.length === 0)
25
+ return [];
26
+ const size = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
27
+ const results = new Array(items.length);
28
+ let cursor = 0;
29
+ const runners = Array.from({ length: size }, async () => {
30
+ for (;;) {
31
+ const index = cursor++;
32
+ if (index >= items.length)
33
+ return;
34
+ // Stop handing out work once aborted, but let in-flight tasks settle so
35
+ // adapters get a chance to clean up rather than being orphaned.
36
+ if (signal?.aborted) {
37
+ results[index] = {
38
+ status: 'rejected',
39
+ index,
40
+ reason: new LensError('ABORTED', 'Task was skipped because the run was aborted.'),
41
+ };
42
+ continue;
43
+ }
44
+ try {
45
+ results[index] = { status: 'fulfilled', value: await worker(items[index], index), index };
46
+ }
47
+ catch (reason) {
48
+ results[index] = { status: 'rejected', reason, index };
49
+ }
50
+ }
51
+ });
52
+ await Promise.all(runners);
53
+ throwIfAborted(signal, 'run');
54
+ return results;
55
+ }
56
+ /** Sleep that rejects promptly when the signal fires, instead of after the delay. */
57
+ export function delay(ms, signal) {
58
+ return new Promise((resolve, reject) => {
59
+ if (signal?.aborted) {
60
+ reject(new LensError('ABORTED', 'Delay was aborted.'));
61
+ return;
62
+ }
63
+ // Deliberately NOT unref'd. A pending retry backoff is real outstanding
64
+ // work: unref-ing it lets Node drain the event loop and exit (or a test
65
+ // runner declare the turn over) while an upload is still waiting to be
66
+ // retried, silently dropping it.
67
+ const timer = setTimeout(() => {
68
+ signal?.removeEventListener('abort', onAbort);
69
+ resolve();
70
+ }, ms);
71
+ function onAbort() {
72
+ clearTimeout(timer);
73
+ reject(new LensError('ABORTED', 'Delay was aborted.'));
74
+ }
75
+ signal?.addEventListener('abort', onAbort, { once: true });
76
+ });
77
+ }
78
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOxD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,KAAmB,EACnB,KAAa,EACb,MAA8C,EAC9C,MAAoB;IAEpB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,MAAM,OAAO,GAAG,IAAI,KAAK,CAAa,KAAK,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI,EAAE;QACtD,SAAS,CAAC;YACR,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;YACvB,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO;YAElC,wEAAwE;YACxE,gEAAgE;YAChE,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,GAAG;oBACf,MAAM,EAAE,UAAU;oBAClB,KAAK;oBACL,MAAM,EAAE,IAAI,SAAS,CAAC,SAAS,EAAE,+CAA+C,CAAC;iBAClF,CAAC;gBACF,SAAS;YACX,CAAC;YAED,IAAI,CAAC;gBACH,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;YACjG,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;gBAChB,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YACzD,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,SAAS,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QACD,wEAAwE;QACxE,wEAAwE;QACxE,uEAAuE;QACvE,iCAAiC;QACjC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,SAAS,OAAO;YACd,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,IAAI,SAAS,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,20 @@
1
+ /** Engine selection. */
2
+ import type { ImageEngine } from './../types.js';
3
+ export { createSharpEngine, resetSharpCache, type SharpEngineOptions } from './sharp.js';
4
+ export { createPassthroughEngine, type PassthroughEngineOptions } from './passthrough.js';
5
+ /**
6
+ * Picks the best engine available in this process.
7
+ *
8
+ * Returns the sharp engine when sharp can be imported, otherwise the
9
+ * passthrough engine. Detection happens once and is memoised - an optimizer
10
+ * constructed at module scope should not pay for a dynamic import on every call.
11
+ *
12
+ * Note that the passthrough fallback is not silent: it throws a message naming
13
+ * sharp the first time you ask it to actually resize something. The goal is
14
+ * that "I forgot to install sharp" surfaces as one clear error rather than as
15
+ * mysteriously unprocessed images.
16
+ */
17
+ export declare function detectEngine(): Promise<ImageEngine>;
18
+ /** Test seam: forget the memoised engine detection. */
19
+ export declare function resetEngineDetection(): void;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engines/index.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAIxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,uBAAuB,EAAE,KAAK,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAI1F;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAMzD;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
@@ -0,0 +1,30 @@
1
+ /** Engine selection. */
2
+ import { createPassthroughEngine } from './passthrough.js';
3
+ import { createSharpEngine } from './sharp.js';
4
+ export { createSharpEngine, resetSharpCache } from './sharp.js';
5
+ export { createPassthroughEngine } from './passthrough.js';
6
+ let detected;
7
+ /**
8
+ * Picks the best engine available in this process.
9
+ *
10
+ * Returns the sharp engine when sharp can be imported, otherwise the
11
+ * passthrough engine. Detection happens once and is memoised - an optimizer
12
+ * constructed at module scope should not pay for a dynamic import on every call.
13
+ *
14
+ * Note that the passthrough fallback is not silent: it throws a message naming
15
+ * sharp the first time you ask it to actually resize something. The goal is
16
+ * that "I forgot to install sharp" surfaces as one clear error rather than as
17
+ * mysteriously unprocessed images.
18
+ */
19
+ export async function detectEngine() {
20
+ detected ??= (async () => {
21
+ const sharp = createSharpEngine();
22
+ return (await sharp.supports('webp')) ? sharp : createPassthroughEngine();
23
+ })();
24
+ return detected;
25
+ }
26
+ /** Test seam: forget the memoised engine detection. */
27
+ export function resetEngineDetection() {
28
+ detected = undefined;
29
+ }
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engines/index.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAExB,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAA2B,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,uBAAuB,EAAiC,MAAM,kBAAkB,CAAC;AAE1F,IAAI,QAA0C,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,QAAQ,KAAK,CAAC,KAAK,IAAI,EAAE;QACvB,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;QAClC,OAAO,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;IAC5E,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,oBAAoB;IAClC,QAAQ,GAAG,SAAS,CAAC;AACvB,CAAC"}
@@ -0,0 +1,41 @@
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 type { ImageEngine } from './../types.js';
16
+ /** Options for {@link createPassthroughEngine}. */
17
+ export interface PassthroughEngineOptions {
18
+ /**
19
+ * What to do when asked for work it cannot do.
20
+ *
21
+ * - `'throw'` (default) - a clear `ENCODE_FAILED`, so the optimizer records a
22
+ * warning and moves to the next format.
23
+ * - `'ignore'` - return the original bytes with their true dimensions. Honest
24
+ * about what happened, because the result reports the real output size.
25
+ */
26
+ readonly onUnsupported?: 'throw' | 'ignore';
27
+ }
28
+ /**
29
+ * Creates the passthrough engine.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // Validate and store originals, no codec required.
34
+ * const optimizer = new ImageOptimizer({
35
+ * engine: createPassthroughEngine(),
36
+ * adapter: new LocalAdapter({ root: './uploads' }),
37
+ * });
38
+ * ```
39
+ */
40
+ export declare function createPassthroughEngine(options?: PassthroughEngineOptions): ImageEngine;
41
+ //# sourceMappingURL=passthrough.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passthrough.d.ts","sourceRoot":"","sources":["../../../src/engines/passthrough.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAgB,WAAW,EAA2C,MAAM,eAAe,CAAC;AAExG,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,wBAA6B,GAAG,WAAW,CA4C3F"}