@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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/cjs/adapters/memory.d.ts +55 -0
- package/dist/cjs/adapters/memory.d.ts.map +1 -0
- package/dist/cjs/adapters/memory.js +84 -0
- package/dist/cjs/adapters/memory.js.map +1 -0
- package/dist/cjs/browser.d.ts +46 -0
- package/dist/cjs/browser.d.ts.map +1 -0
- package/dist/cjs/browser.js +79 -0
- package/dist/cjs/browser.js.map +1 -0
- package/dist/cjs/bytes.d.ts +18 -0
- package/dist/cjs/bytes.d.ts.map +1 -0
- package/dist/cjs/bytes.js +31 -0
- package/dist/cjs/bytes.js.map +1 -0
- package/dist/cjs/cache.d.ts +43 -0
- package/dist/cjs/cache.d.ts.map +1 -0
- package/dist/cjs/cache.js +77 -0
- package/dist/cjs/cache.js.map +1 -0
- package/dist/cjs/concurrency.d.ts +35 -0
- package/dist/cjs/concurrency.d.ts.map +1 -0
- package/dist/cjs/concurrency.js +82 -0
- package/dist/cjs/concurrency.js.map +1 -0
- package/dist/cjs/engines/index.d.ts +20 -0
- package/dist/cjs/engines/index.d.ts.map +1 -0
- package/dist/cjs/engines/index.js +38 -0
- package/dist/cjs/engines/index.js.map +1 -0
- package/dist/cjs/engines/passthrough.d.ts +41 -0
- package/dist/cjs/engines/passthrough.d.ts.map +1 -0
- package/dist/cjs/engines/passthrough.js +66 -0
- package/dist/cjs/engines/passthrough.js.map +1 -0
- package/dist/cjs/engines/sharp.d.ts +58 -0
- package/dist/cjs/engines/sharp.d.ts.map +1 -0
- package/dist/cjs/engines/sharp.js +243 -0
- package/dist/cjs/engines/sharp.js.map +1 -0
- package/dist/cjs/errors.d.ts +90 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +97 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/format.d.ts +33 -0
- package/dist/cjs/format.d.ts.map +1 -0
- package/dist/cjs/format.js +102 -0
- package/dist/cjs/format.js.map +1 -0
- package/dist/cjs/hash.d.ts +19 -0
- package/dist/cjs/hash.d.ts.map +1 -0
- package/dist/cjs/hash.js +43 -0
- package/dist/cjs/hash.js.map +1 -0
- package/dist/cjs/index.d.ts +56 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +110 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/naming.d.ts +48 -0
- package/dist/cjs/naming.d.ts.map +1 -0
- package/dist/cjs/naming.js +95 -0
- package/dist/cjs/naming.js.map +1 -0
- package/dist/cjs/optimizer.d.ts +89 -0
- package/dist/cjs/optimizer.d.ts.map +1 -0
- package/dist/cjs/optimizer.js +667 -0
- package/dist/cjs/optimizer.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/retry.d.ts +55 -0
- package/dist/cjs/retry.d.ts.map +1 -0
- package/dist/cjs/retry.js +129 -0
- package/dist/cjs/retry.js.map +1 -0
- package/dist/cjs/server/handler.d.ts +80 -0
- package/dist/cjs/server/handler.d.ts.map +1 -0
- package/dist/cjs/server/handler.js +155 -0
- package/dist/cjs/server/handler.js.map +1 -0
- package/dist/cjs/sniff.d.ts +37 -0
- package/dist/cjs/sniff.d.ts.map +1 -0
- package/dist/cjs/sniff.js +442 -0
- package/dist/cjs/sniff.js.map +1 -0
- package/dist/cjs/source.d.ts +45 -0
- package/dist/cjs/source.d.ts.map +1 -0
- package/dist/cjs/source.js +221 -0
- package/dist/cjs/source.js.map +1 -0
- package/dist/cjs/srcset.d.ts +66 -0
- package/dist/cjs/srcset.d.ts.map +1 -0
- package/dist/cjs/srcset.js +81 -0
- package/dist/cjs/srcset.js.map +1 -0
- package/dist/cjs/types.d.ts +441 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +12 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/validation.d.ts +38 -0
- package/dist/cjs/validation.d.ts.map +1 -0
- package/dist/cjs/validation.js +79 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/adapters/memory.d.ts +55 -0
- package/dist/esm/adapters/memory.d.ts.map +1 -0
- package/dist/esm/adapters/memory.js +80 -0
- package/dist/esm/adapters/memory.js.map +1 -0
- package/dist/esm/browser.d.ts +46 -0
- package/dist/esm/browser.d.ts.map +1 -0
- package/dist/esm/browser.js +49 -0
- package/dist/esm/browser.js.map +1 -0
- package/dist/esm/bytes.d.ts +18 -0
- package/dist/esm/bytes.d.ts.map +1 -0
- package/dist/esm/bytes.js +28 -0
- package/dist/esm/bytes.js.map +1 -0
- package/dist/esm/cache.d.ts +43 -0
- package/dist/esm/cache.d.ts.map +1 -0
- package/dist/esm/cache.js +73 -0
- package/dist/esm/cache.js.map +1 -0
- package/dist/esm/concurrency.d.ts +35 -0
- package/dist/esm/concurrency.d.ts.map +1 -0
- package/dist/esm/concurrency.js +78 -0
- package/dist/esm/concurrency.js.map +1 -0
- package/dist/esm/engines/index.d.ts +20 -0
- package/dist/esm/engines/index.d.ts.map +1 -0
- package/dist/esm/engines/index.js +30 -0
- package/dist/esm/engines/index.js.map +1 -0
- package/dist/esm/engines/passthrough.d.ts +41 -0
- package/dist/esm/engines/passthrough.d.ts.map +1 -0
- package/dist/esm/engines/passthrough.js +63 -0
- package/dist/esm/engines/passthrough.js.map +1 -0
- package/dist/esm/engines/sharp.d.ts +58 -0
- package/dist/esm/engines/sharp.d.ts.map +1 -0
- package/dist/esm/engines/sharp.js +206 -0
- package/dist/esm/engines/sharp.js.map +1 -0
- package/dist/esm/errors.d.ts +90 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +91 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/format.d.ts +33 -0
- package/dist/esm/format.d.ts.map +1 -0
- package/dist/esm/format.js +93 -0
- package/dist/esm/format.js.map +1 -0
- package/dist/esm/hash.d.ts +19 -0
- package/dist/esm/hash.d.ts.map +1 -0
- package/dist/esm/hash.js +38 -0
- package/dist/esm/hash.js.map +1 -0
- package/dist/esm/index.d.ts +56 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +54 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/naming.d.ts +48 -0
- package/dist/esm/naming.d.ts.map +1 -0
- package/dist/esm/naming.js +88 -0
- package/dist/esm/naming.js.map +1 -0
- package/dist/esm/optimizer.d.ts +89 -0
- package/dist/esm/optimizer.d.ts.map +1 -0
- package/dist/esm/optimizer.js +663 -0
- package/dist/esm/optimizer.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/retry.d.ts +55 -0
- package/dist/esm/retry.d.ts.map +1 -0
- package/dist/esm/retry.js +123 -0
- package/dist/esm/retry.js.map +1 -0
- package/dist/esm/server/handler.d.ts +80 -0
- package/dist/esm/server/handler.d.ts.map +1 -0
- package/dist/esm/server/handler.js +151 -0
- package/dist/esm/server/handler.js.map +1 -0
- package/dist/esm/sniff.d.ts +37 -0
- package/dist/esm/sniff.d.ts.map +1 -0
- package/dist/esm/sniff.js +435 -0
- package/dist/esm/sniff.js.map +1 -0
- package/dist/esm/source.d.ts +45 -0
- package/dist/esm/source.d.ts.map +1 -0
- package/dist/esm/source.js +183 -0
- package/dist/esm/source.js.map +1 -0
- package/dist/esm/srcset.d.ts +66 -0
- package/dist/esm/srcset.d.ts.map +1 -0
- package/dist/esm/srcset.js +76 -0
- package/dist/esm/srcset.js.map +1 -0
- package/dist/esm/types.d.ts +441 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +11 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.d.ts +38 -0
- package/dist/esm/validation.d.ts.map +1 -0
- package/dist/esm/validation.js +74 -0
- package/dist/esm/validation.js.map +1 -0
- 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"}
|