@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lens contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ <p align="center">
2
+ <a href="https://lens-image.vercel.app">
3
+ <img src="https://raw.githubusercontent.com/Sthabiso10/lens-image/main/docs/media/logo.png" alt="Lens" width="300" />
4
+ </a>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@lens-image/core"><img alt="npm" src="https://img.shields.io/npm/v/@lens-image%2Fcore.svg?color=cb3837" /></a>
9
+ <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178c6.svg" />
10
+ <img alt="MIT license" src="https://img.shields.io/badge/license-MIT-blue.svg" />
11
+ </p>
12
+
13
+ # @lens-image/core
14
+
15
+ **Zero-dependency image optimization engine for Node.js.** Resize, compress, convert formats, and hand the results to any storage backend.
16
+
17
+ ```bash
18
+ npm install @lens-image/core sharp
19
+ ```
20
+
21
+ `sharp` is an [optional peer dependency](#the-engine). `@lens-image/core` itself installs **zero packages**.
22
+
23
+ ---
24
+
25
+ ## Quick start
26
+
27
+ ```ts
28
+ import { ImageOptimizer, MemoryAdapter } from '@lens-image/core';
29
+
30
+ const optimizer = new ImageOptimizer({ adapter: new MemoryAdapter() });
31
+
32
+ const result = await optimizer.optimize({
33
+ source: './photo.jpg',
34
+ formats: ['webp', 'jpg'],
35
+ sizes: [{ width: 1200 }, { width: 600 }, { width: 300 }],
36
+ });
37
+
38
+ result.formats.webp.srcset;
39
+ ```
40
+
41
+ Swap `MemoryAdapter` for [`@lens-image/adapter-s3`](../adapter-s3), [`@lens-image/adapter-local`](../adapter-local) or [`@lens-image/adapter-cloudinary`](../adapter-cloudinary) when you want the bytes to go somewhere real.
42
+
43
+ ---
44
+
45
+ ## `new ImageOptimizer(options)`
46
+
47
+ | Option | Type | Default | |
48
+ |---|---|---|---|
49
+ | `adapter` | `StorageAdapter` |, | Where output goes. Omit for process-only mode. |
50
+ | `engine` | `ImageEngine` | auto | Codec backend. Defaults to sharp if installed. |
51
+ | `quality` | `number` | per-format | 1-100. |
52
+ | `formats` | `ImageFormat[]` | `['webp']` | Default output formats. |
53
+ | `sizes` | `SizeSpec[]` | original | Default responsive sizes. |
54
+ | `fallbackFormat` | `ImageFormat \| null` | `'jpeg'` | Used when everything else fails. `null` disables. |
55
+ | `key` | `string \| fn` | `'{hash}/{name}-{label}.{ext}'` | Output key template. |
56
+ | `prefix` | `string` | `''` | Prepended to every key. |
57
+ | `preserveMetadata` | `boolean` | `false` | Keep EXIF/ICC/XMP. |
58
+ | `autoOrient` | `boolean` | `true` | Apply EXIF orientation before resizing. |
59
+ | `concurrency` | `number` | `4` | Parallel encode+upload pipelines. |
60
+ | `validate` | `ValidationOptions \| false` | see below | Input limits. |
61
+ | `retry` | `RetryOptions` | 3 attempts | Adapter write retries. |
62
+ | `cache` | `false \| 'memory' \| 'storage' \| CacheStore` | `false` | Result caching. |
63
+ | `cacheControl` | `string` | `public, max-age=31536000, immutable` | Applied to uploads. |
64
+ | `allowRemote` | `boolean` | `false` | Permit `http(s):` sources. |
65
+ | `onWarning` | `(w) => void` |, | Called per degradation. |
66
+
67
+ Everything except `adapter`, `engine`, `cache` and `allowRemote` can be overridden per call.
68
+
69
+ ---
70
+
71
+ ## `optimizer.optimize(options)`
72
+
73
+ ```ts
74
+ await optimizer.optimize({
75
+ source: './photo.jpg',
76
+ formats: ['avif', 'webp', 'jpg'],
77
+ sizes: [{ width: 1200 }, { width: 600, fit: 'contain' }],
78
+ thumbnail: { width: 128, format: 'webp' },
79
+ metadata: { uploadedBy: 'user-42' },
80
+ signal: controller.signal,
81
+ });
82
+ ```
83
+
84
+ ### `source` accepts
85
+
86
+ | | |
87
+ |---|---|
88
+ | `string` | Filesystem path |
89
+ | `Uint8Array` / `Buffer` | Raw bytes |
90
+ | `URL` | `file:`, or `http(s):` with `allowRemote` |
91
+ | `AsyncIterable<Uint8Array>` | Node or web stream, multipart part |
92
+ | `{ data, filename }` | Bytes plus a name for key generation |
93
+
94
+ ### Sizes
95
+
96
+ ```ts
97
+ sizes: [
98
+ { width: 1200 }, // → label '1200w'
99
+ { width: 400, height: 400, fit: 'cover' }, // → label '400x400'
100
+ { width: 800, label: 'hero', quality: 90 }, // explicit label
101
+ ]
102
+ ```
103
+
104
+ `fit` is `'cover' | 'contain' | 'fill' | 'inside' | 'outside'`. The familiar CSS names. Images are never upscaled unless you set `withoutEnlargement: false`.
105
+
106
+ > Two sizes that produce the same label within one format throw `INVALID_OPTIONS`, because they'd map to the same key and one would silently overwrite the other.
107
+
108
+ ---
109
+
110
+ ## The result
111
+
112
+ ```ts
113
+ interface OptimizeResult {
114
+ id: string; // stable per (source, options), also the cache key
115
+ runId: string; // unique per call, for log correlation
116
+ source: ImageMetadata & { filename: string; checksum: string };
117
+ formats: { webp?: FormatResult; jpeg?: FormatResult; /* … */ };
118
+ variants: Variant[]; // flat, in plan order
119
+ thumbnail?: Variant;
120
+ warnings: LensWarning[];
121
+ totalSize: number;
122
+ savings: number; // 0-1, vs the largest single output
123
+ durationMs: number;
124
+ cached: boolean;
125
+ engine: string;
126
+ adapter: string | null;
127
+ }
128
+ ```
129
+
130
+ Each `FormatResult` carries `urls` (keyed by label), `variants`, `srcset`, `size`, `largest` and `smallest`.
131
+
132
+ > **Note on `jpg`.** It's accepted everywhere as an input alias and normalised to `jpeg`, so results are always keyed `result.formats.jpeg`. File extensions still come out as `.jpg`.
133
+
134
+ ---
135
+
136
+ ## Other methods
137
+
138
+ ```ts
139
+ await optimizer.inspect(source); // metadata only, no processing
140
+ await optimizer.optimizeMany([a, b], opts); // sequential, bounded memory
141
+ await optimizer.delete(result); // remove every variant
142
+ await optimizer.dispose(); // release adapter resources
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Validation
148
+
149
+ Runs against the **file header**, before any decoder sees the bytes.
150
+
151
+ ```ts
152
+ validate: {
153
+ maxBytes: 25 * 1024 * 1024, // default
154
+ maxPixels: 100_000_000, // default, decompression-bomb guard
155
+ allowedFormats: ['jpeg', 'png', 'webp', 'avif', 'gif', 'tiff'],
156
+ minWidth: 200,
157
+ minHeight: 200,
158
+ }
159
+ ```
160
+
161
+ Set `validate: false` to disable. The `maxPixels` guard matters: a 40,000 × 40,000 PNG compresses to a few hundred KB and expands to roughly 6 GB in memory.
162
+
163
+ ---
164
+
165
+ ## The engine
166
+
167
+ The codec is an interface, not a hard dependency:
168
+
169
+ ```ts
170
+ interface ImageEngine {
171
+ readonly name: string;
172
+ supports(format: ImageFormat): boolean | Promise<boolean>;
173
+ probe(input: Uint8Array): Promise<ImageMetadata>;
174
+ transform(input: Uint8Array, op: TransformOp): Promise<EncodedImage>;
175
+ }
176
+ ```
177
+
178
+ | Engine | |
179
+ |---|---|
180
+ | `createSharpEngine(opts)` | sharp-backed. Lazy dynamic import, nothing loads until you encode. |
181
+ | `createPassthroughEngine()` | No codec. Validates, hashes, keys and stores originals. Ideal for tests. |
182
+ | `detectEngine()` | sharp if available, otherwise passthrough. The default. |
183
+
184
+ ```ts
185
+ import { createSharpEngine } from '@lens-image/core';
186
+
187
+ new ImageOptimizer({
188
+ engine: createSharpEngine({
189
+ threads: 1, // we already parallelise; 4 Lens jobs × 4 libvips threads = 16
190
+ cache: false, // libvips' global cache is a leak in a long-lived server
191
+ }),
192
+ });
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Upload handler
198
+
199
+ A `Request → Response` function built only on web standards, so it drops into Next.js, Remix, Hono, Bun or Deno unmodified.
200
+
201
+ ```ts
202
+ export const POST = createUploadHandler({
203
+ optimizer,
204
+ fieldName: 'file',
205
+ maxFiles: 5,
206
+ authorize: async (req) => (await auth(req)) ? undefined : new Response('no', { status: 401 }),
207
+ options: async (req) => ({ prefix: `users/${(await auth(req)).id}` }),
208
+ onError: (error) => logger.error(error),
209
+ });
210
+ ```
211
+
212
+ Errors map to their `LensError.status` automatically. A too-large file is a 422, not a 500. Unexpected errors return a generic message; the real one goes to `onError`.
213
+
214
+ ---
215
+
216
+ ## HTML helpers
217
+
218
+ ```ts
219
+ import { toPicture, pickVariant, buildSrcset } from '@lens-image/core';
220
+
221
+ const pic = toPicture(result, { alt: 'Cabin', sizes: '(max-width: 768px) 100vw, 50vw' });
222
+ const best = pickVariant(result, 800, 'webp'); // smallest variant ≥ 800px
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Exports for adapter authors
228
+
229
+ ```ts
230
+ import {
231
+ sniff, mimeTypeFor, extensionFor, // format detection
232
+ buildKey, joinKey, slugify, // key templating
233
+ withRetry, isRetryableError, // retry
234
+ mapLimit, // bounded concurrency
235
+ sha256, shortHash, // hashing
236
+ LensError, // error taxonomy
237
+ } from '@lens-image/core';
238
+ ```
239
+
240
+ ---
241
+
242
+ ## License
243
+
244
+ MIT
@@ -0,0 +1,55 @@
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
+ import type { StorageAdapter, StorageContext, StorageFile, StorageObject } from './../types.js';
9
+ /** Options for {@link MemoryAdapter}. */
10
+ export interface MemoryAdapterOptions {
11
+ /** URL scheme used for generated URLs. Default `'memory://'`. */
12
+ readonly baseUrl?: string;
13
+ /**
14
+ * Inject a failure for the first N attempts on any key. Test-only, and the
15
+ * reason retry behaviour is easy to verify without mocking a network.
16
+ */
17
+ readonly failAttempts?: number;
18
+ }
19
+ /** A stored object. */
20
+ export interface MemoryEntry {
21
+ readonly key: string;
22
+ readonly data: Uint8Array;
23
+ readonly contentType: string;
24
+ readonly cacheControl?: string;
25
+ readonly metadata?: Readonly<Record<string, string>>;
26
+ }
27
+ /**
28
+ * @example
29
+ * ```ts
30
+ * const adapter = new MemoryAdapter();
31
+ * const optimizer = new ImageOptimizer({ adapter });
32
+ *
33
+ * await optimizer.optimize({ source: './photo.jpg', formats: ['webp'] });
34
+ * adapter.keys(); // ['a1b2c3d4e5/photo-original.webp']
35
+ * adapter.get(key)?.data; // the encoded bytes
36
+ * ```
37
+ */
38
+ export declare class MemoryAdapter implements StorageAdapter {
39
+ #private;
40
+ readonly name = "memory";
41
+ constructor(options?: MemoryAdapterOptions);
42
+ upload(file: StorageFile, _ctx: StorageContext): Promise<StorageObject>;
43
+ exists(key: string): Promise<boolean>;
44
+ remove(key: string): Promise<void>;
45
+ getUrl(key: string): string;
46
+ /** Reads back a stored object. */
47
+ get(key: string): MemoryEntry | undefined;
48
+ /** Every stored key, in insertion order. */
49
+ keys(): string[];
50
+ /** Number of stored objects. */
51
+ get size(): number;
52
+ /** Drops every object and resets the injected-failure counters. */
53
+ clear(): void;
54
+ }
55
+ //# sourceMappingURL=memory.d.ts.map
@@ -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,84 @@
1
+ "use strict";
2
+ /**
3
+ * An in-memory {@link StorageAdapter}.
4
+ *
5
+ * Ships in core on purpose: it is the reference implementation of the adapter
6
+ * contract (about 40 lines of real logic), and it makes every example in the
7
+ * README runnable without credentials or a filesystem.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.MemoryAdapter = void 0;
11
+ /**
12
+ * @example
13
+ * ```ts
14
+ * const adapter = new MemoryAdapter();
15
+ * const optimizer = new ImageOptimizer({ adapter });
16
+ *
17
+ * await optimizer.optimize({ source: './photo.jpg', formats: ['webp'] });
18
+ * adapter.keys(); // ['a1b2c3d4e5/photo-original.webp']
19
+ * adapter.get(key)?.data; // the encoded bytes
20
+ * ```
21
+ */
22
+ class MemoryAdapter {
23
+ name = 'memory';
24
+ #files = new Map();
25
+ #baseUrl;
26
+ #failAttempts;
27
+ #attempts = new Map();
28
+ constructor(options = {}) {
29
+ this.#baseUrl = options.baseUrl ?? 'memory://';
30
+ this.#failAttempts = options.failAttempts ?? 0;
31
+ }
32
+ async upload(file, _ctx) {
33
+ const seen = (this.#attempts.get(file.key) ?? 0) + 1;
34
+ this.#attempts.set(file.key, seen);
35
+ if (seen <= this.#failAttempts) {
36
+ // Shaped like a transient network error so the default retry predicate
37
+ // treats it as retryable.
38
+ const error = new Error(`Simulated transient failure ${seen}/${this.#failAttempts}`);
39
+ error.code = 'ECONNRESET';
40
+ throw error;
41
+ }
42
+ this.#files.set(file.key, {
43
+ key: file.key,
44
+ data: file.data,
45
+ contentType: file.contentType,
46
+ ...(file.cacheControl ? { cacheControl: file.cacheControl } : {}),
47
+ ...(file.metadata ? { metadata: file.metadata } : {}),
48
+ });
49
+ return {
50
+ key: file.key,
51
+ url: this.getUrl(file.key),
52
+ size: file.data.byteLength,
53
+ ...(file.checksum ? { etag: file.checksum } : {}),
54
+ };
55
+ }
56
+ async exists(key) {
57
+ return this.#files.has(key);
58
+ }
59
+ async remove(key) {
60
+ this.#files.delete(key);
61
+ }
62
+ getUrl(key) {
63
+ return `${this.#baseUrl}${key}`;
64
+ }
65
+ /** Reads back a stored object. */
66
+ get(key) {
67
+ return this.#files.get(key);
68
+ }
69
+ /** Every stored key, in insertion order. */
70
+ keys() {
71
+ return [...this.#files.keys()];
72
+ }
73
+ /** Number of stored objects. */
74
+ get size() {
75
+ return this.#files.size;
76
+ }
77
+ /** Drops every object and resets the injected-failure counters. */
78
+ clear() {
79
+ this.#files.clear();
80
+ this.#attempts.clear();
81
+ }
82
+ }
83
+ exports.MemoryAdapter = MemoryAdapter;
84
+ //# 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,79 @@
1
+ "use strict";
2
+ /**
3
+ * `@lens-image/core/browser` - the part of Lens that runs anywhere.
4
+ *
5
+ * Everything re-exported here is pure computation over bytes and strings, with
6
+ * no `node:` imports at all. It works in a browser, a Worker, an edge runtime
7
+ * or React Native.
8
+ *
9
+ * ### Why this exists
10
+ *
11
+ * The main entry point imports `node:crypto` and `node:fs`, so a bundler
12
+ * targeting the browser either fails or ships a polyfill for them. But the
13
+ * *useful* half for a client is Node-free: you can identify an image and read
14
+ * its dimensions from its first few kilobytes, which means you can reject a
15
+ * 40,000 x 40,000 PNG **before** uploading it rather than after.
16
+ *
17
+ * @example Validate before the upload starts
18
+ * ```ts
19
+ * import { sniff, resolveValidation, validateImage, LensError } from '@lens-image/core/browser';
20
+ *
21
+ * const policy = resolveValidation({ maxBytes: 10_000_000, allowedFormats: ['jpeg', 'png', 'webp'] });
22
+ * const header = new Uint8Array(await file.slice(0, 4096).arrayBuffer());
23
+ *
24
+ * try {
25
+ * validateImage({ ...sniff(header, file.size) }, policy, file.name);
26
+ * } catch (error) {
27
+ * if (LensError.is(error)) showToast(error.message); // the same message the server would give
28
+ * }
29
+ * ```
30
+ *
31
+ * Note the `file.slice(0, 4096)` - only the header is read, so validating a
32
+ * 40 MB file costs 4 KB.
33
+ *
34
+ * @packageDocumentation
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.formatBytes = exports.pickVariant = exports.toPicture = exports.buildSrcset = exports.DEFAULT_KEY_TEMPLATE = exports.defaultLabel = exports.slugify = exports.joinKey = exports.buildKey = exports.ENCODABLE_FORMATS = exports.defaultEffort = exports.defaultQuality = exports.isLossy = exports.supportsAlpha = exports.normalizeFormats = exports.normalizeFormat = exports.LensError = exports.DEFAULT_ALLOWED_FORMATS = exports.DEFAULT_MAX_PIXELS = exports.DEFAULT_MAX_BYTES = exports.resolveValidation = exports.validateImage = exports.SNIFF_HEADER_BYTES = exports.extensionFor = exports.mimeTypeFor = exports.sniffFormat = exports.sniff = void 0;
38
+ // --- Format detection -------------------------------------------------------
39
+ var sniff_js_1 = require("./sniff.js");
40
+ Object.defineProperty(exports, "sniff", { enumerable: true, get: function () { return sniff_js_1.sniff; } });
41
+ Object.defineProperty(exports, "sniffFormat", { enumerable: true, get: function () { return sniff_js_1.sniffFormat; } });
42
+ Object.defineProperty(exports, "mimeTypeFor", { enumerable: true, get: function () { return sniff_js_1.mimeTypeFor; } });
43
+ Object.defineProperty(exports, "extensionFor", { enumerable: true, get: function () { return sniff_js_1.extensionFor; } });
44
+ Object.defineProperty(exports, "SNIFF_HEADER_BYTES", { enumerable: true, get: function () { return sniff_js_1.SNIFF_HEADER_BYTES; } });
45
+ // --- Validation -------------------------------------------------------------
46
+ var validation_js_1 = require("./validation.js");
47
+ Object.defineProperty(exports, "validateImage", { enumerable: true, get: function () { return validation_js_1.validateImage; } });
48
+ Object.defineProperty(exports, "resolveValidation", { enumerable: true, get: function () { return validation_js_1.resolveValidation; } });
49
+ Object.defineProperty(exports, "DEFAULT_MAX_BYTES", { enumerable: true, get: function () { return validation_js_1.DEFAULT_MAX_BYTES; } });
50
+ Object.defineProperty(exports, "DEFAULT_MAX_PIXELS", { enumerable: true, get: function () { return validation_js_1.DEFAULT_MAX_PIXELS; } });
51
+ Object.defineProperty(exports, "DEFAULT_ALLOWED_FORMATS", { enumerable: true, get: function () { return validation_js_1.DEFAULT_ALLOWED_FORMATS; } });
52
+ // --- Errors -----------------------------------------------------------------
53
+ var errors_js_1 = require("./errors.js");
54
+ Object.defineProperty(exports, "LensError", { enumerable: true, get: function () { return errors_js_1.LensError; } });
55
+ // --- Formats ----------------------------------------------------------------
56
+ var format_js_1 = require("./format.js");
57
+ Object.defineProperty(exports, "normalizeFormat", { enumerable: true, get: function () { return format_js_1.normalizeFormat; } });
58
+ Object.defineProperty(exports, "normalizeFormats", { enumerable: true, get: function () { return format_js_1.normalizeFormats; } });
59
+ Object.defineProperty(exports, "supportsAlpha", { enumerable: true, get: function () { return format_js_1.supportsAlpha; } });
60
+ Object.defineProperty(exports, "isLossy", { enumerable: true, get: function () { return format_js_1.isLossy; } });
61
+ Object.defineProperty(exports, "defaultQuality", { enumerable: true, get: function () { return format_js_1.defaultQuality; } });
62
+ Object.defineProperty(exports, "defaultEffort", { enumerable: true, get: function () { return format_js_1.defaultEffort; } });
63
+ Object.defineProperty(exports, "ENCODABLE_FORMATS", { enumerable: true, get: function () { return format_js_1.ENCODABLE_FORMATS; } });
64
+ // --- Key templating ---------------------------------------------------------
65
+ var naming_js_1 = require("./naming.js");
66
+ Object.defineProperty(exports, "buildKey", { enumerable: true, get: function () { return naming_js_1.buildKey; } });
67
+ Object.defineProperty(exports, "joinKey", { enumerable: true, get: function () { return naming_js_1.joinKey; } });
68
+ Object.defineProperty(exports, "slugify", { enumerable: true, get: function () { return naming_js_1.slugify; } });
69
+ Object.defineProperty(exports, "defaultLabel", { enumerable: true, get: function () { return naming_js_1.defaultLabel; } });
70
+ Object.defineProperty(exports, "DEFAULT_KEY_TEMPLATE", { enumerable: true, get: function () { return naming_js_1.DEFAULT_KEY_TEMPLATE; } });
71
+ // --- HTML helpers -----------------------------------------------------------
72
+ var srcset_js_1 = require("./srcset.js");
73
+ Object.defineProperty(exports, "buildSrcset", { enumerable: true, get: function () { return srcset_js_1.buildSrcset; } });
74
+ Object.defineProperty(exports, "toPicture", { enumerable: true, get: function () { return srcset_js_1.toPicture; } });
75
+ Object.defineProperty(exports, "pickVariant", { enumerable: true, get: function () { return srcset_js_1.pickVariant; } });
76
+ // --- Formatting -------------------------------------------------------------
77
+ var bytes_js_1 = require("./bytes.js");
78
+ Object.defineProperty(exports, "formatBytes", { enumerable: true, get: function () { return bytes_js_1.formatBytes; } });
79
+ //# 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,uCAA+F;AAAtF,iGAAA,KAAK,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,8GAAA,kBAAkB,OAAA;AAE1E,+EAA+E;AAC/E,iDAMyB;AALvB,8GAAA,aAAa,OAAA;AACb,kHAAA,iBAAiB,OAAA;AACjB,kHAAA,iBAAiB,OAAA;AACjB,mHAAA,kBAAkB,OAAA;AAClB,wHAAA,uBAAuB,OAAA;AAIzB,+EAA+E;AAC/E,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAGlB,+EAA+E;AAC/E,yCAQqB;AAPnB,4GAAA,eAAe,OAAA;AACf,6GAAA,gBAAgB,OAAA;AAChB,0GAAA,aAAa,OAAA;AACb,oGAAA,OAAO,OAAA;AACP,2GAAA,cAAc,OAAA;AACd,0GAAA,aAAa,OAAA;AACb,8GAAA,iBAAiB,OAAA;AAGnB,+EAA+E;AAC/E,yCAA6F;AAApF,qGAAA,QAAQ,OAAA;AAAE,oGAAA,OAAO,OAAA;AAAE,oGAAA,OAAO,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAEvE,+EAA+E;AAC/E,yCAAkE;AAAzD,wGAAA,WAAW,OAAA;AAAE,sGAAA,SAAS,OAAA;AAAE,wGAAA,WAAW,OAAA;AAG5C,+EAA+E;AAC/E,uCAAyC;AAAhC,uGAAA,WAAW,OAAA"}
@@ -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,31 @@
1
+ "use strict";
2
+ /**
3
+ * Byte formatting.
4
+ *
5
+ * Its own module so that `validation.ts` - and therefore the whole `./browser`
6
+ * entry point - does not have to import `source.ts`, which pulls in `node:fs`
7
+ * and `node:path` at the top level.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.formatBytes = formatBytes;
11
+ /**
12
+ * Human-readable byte count, using binary units.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * formatBytes(900); // '900 B'
17
+ * formatBytes(5_242_880); // '5.0 MiB'
18
+ * ```
19
+ */
20
+ function formatBytes(bytes) {
21
+ if (!Number.isFinite(bytes) || bytes < 0)
22
+ return '0 B';
23
+ if (bytes < 1024)
24
+ return `${Math.round(bytes)} B`;
25
+ if (bytes < 1024 * 1024)
26
+ return `${(bytes / 1024).toFixed(1)} KiB`;
27
+ if (bytes < 1024 * 1024 * 1024)
28
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
29
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
30
+ }
31
+ //# sourceMappingURL=bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.js","sourceRoot":"","sources":["../../src/bytes.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAWH,kCAMC;AAfD;;;;;;;;GAQG;AACH,SAAgB,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"}