qpdf-compress 0.4.0 → 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,131 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.5.0] - 2026-04-04
8
+
9
+ ### Added
10
+
11
+ - `concurrency()` — get/set max concurrent compress operations dispatched to the libuv thread pool (default: CPU cores, powered by p-limit)
12
+ - Husky + lint-staged pre-commit hook (Prettier + ESLint on staged files)
13
+
14
+ ## [0.4.0] - 2026-03-31
15
+
16
+ ### Added
17
+
18
+ - **Grayscale detection**: automatically converts RGB images where R==G==B to DeviceGray (3× raw data reduction)
19
+ - **Bitonal conversion**: converts 8-bit grayscale images that are effectively B&W to 1-bit (8× raw data reduction)
20
+ - **TrueType font subsetting**: strips unused glyph outlines from `/FontFile2` font programs via custom binary parser (cmap format 0/4, composite glyph dependency resolution, glyf/loca/hmtx table rebuilding)
21
+ - **Soft mask optimization**: losslessly optimizes JPEG `/SMask` transparency streams
22
+ - **ICC profile stripping**: replaces ICCBased color spaces with Device equivalents on images and page resources
23
+ - **Form flattening**: stamps widget annotation appearances into page content and removes `/AcroForm`
24
+ - **Page tree flattening**: pushes inherited attributes to individual pages for optimal QPDFWriter output
25
+ - **Content stream coalescing**: merges multiple content streams per page into one
26
+ - **Content stream minification**: normalizes whitespace and trims numeric formatting (trailing/leading zeros)
27
+ - **Non-image stream deduplication**: deduplicates identical font, ICC, and other non-image streams via FNV-1a hash + full byte comparison
28
+ - **Embedded file stripping**: removes `/EmbeddedFiles` from the document name tree
29
+ - **JavaScript removal**: strips `/OpenAction`, `/AA`, and `/JavaScript` from catalog, pages, and annotations
30
+ - **Accurate DPI calculation**: content stream CTM matrix parser (~170 lines) tracks `q`/`Q`/`cm`/`Do` operators to find actual rendered image dimensions, with MediaBox fallback
31
+
32
+ ### Fixed
33
+
34
+ - **Grayscale JPEG inflation**: `convertGrayscaleImages` now skips DCTDecode images — previously replacing JPEG with raw gray + Flate inflated photographic images 2–4× in lossless mode
35
+ - **Font subset size comparison**: compares uncompressed sizes instead of uncompressed subset vs Flate-compressed original
36
+ - **Stream dedup safety**: `deduplicateStreams` now checks `/DecodeParms` equality — identical raw bytes with different decode parameters produce different content
37
+ - **Content stream decoding**: decoded once per page in `subsetFonts` instead of redundantly per font
38
+
39
+ ### Changed
40
+
41
+ - Split `images.cc` into `images.cc` (image operations) and `optimize.cc` (structural optimizations)
42
+ - Added `font_subset.cc`/`font_subset.h` for TrueType binary parsing
43
+ - Source: 5 `.cc` files, 4 headers, ~3000 lines total
44
+
45
+ ## [0.3.0] - 2026-03-30
46
+
47
+ ### Changed
48
+
49
+ - **BREAKING**: Replaced `mode: 'lossy' | 'lossless'` with `lossy?: boolean` (default `false`)
50
+ - **BREAKING**: Removed `quality` parameter — quality is now automatically determined per mode
51
+ - **BREAKING**: Removed `maxDpi` parameter — DPI is now automatically determined per mode
52
+ - **BREAKING**: Options parameter is now optional — `compress(input)` defaults to lossless
53
+ - Lossless: pure structural optimization — no image re-encoding or downscaling
54
+ - Lossy: skip images at q ≤ 65, re-encode at q75, downscale to 72 DPI
55
+
56
+ ## [0.2.0] - 2026-03-30
57
+
58
+ ### Added
59
+
60
+ - CMYK → RGB conversion for JPEG images
61
+ - ICCBased color space support (extracts and converts embedded ICC profiles)
62
+ - PNG optimization (re-encodes as JPEG when beneficial)
63
+ - DPI downscaling (configurable max DPI, default 75)
64
+ - Metadata stripping (XMP, document info, thumbnails) — enabled by default
65
+ - Unused font removal
66
+ - Auto quality mode — estimates existing JPEG quality and skips re-encoding when already below target
67
+
68
+ ## [0.1.3] - 2026-03-30
69
+
70
+ ### Fixed
71
+
72
+ - Included `<cstdio>` before `<jpeglib.h>` for Linux compatibility
73
+ - Restored Node 20 to CI matrix (matching engines `>=20.11.0`)
74
+ - Used static import for `Readable` instead of dynamic `import()`
75
+
76
+ ### Added
77
+
78
+ - Build provenance attestation for prebuilt binaries via `actions/attest-build-provenance`
79
+
80
+ ## [0.1.2] - 2026-03-30
81
+
82
+ ### Fixed
83
+
84
+ - Fixed Linux cross-build tarball structure (prebuilds now install correctly)
85
+ - Fixed Debian Bookworm cross-builds (use `libjpeg62-turbo-dev` package)
86
+ - Moved `setjmp` before `jpeg_create_*` calls to prevent UB on allocation failure
87
+ - Used `uint64_t` for FNV-1a image dedup hash (correct on 32-bit ARM)
88
+ - Added component count validation in `encodeJpeg`
89
+ - Wrapped web `ReadableStream` with `Readable.fromWeb()` in install script
90
+ - Made `strerror` calls thread-safe by copying to `std::string`
91
+
92
+ ### Changed
93
+
94
+ - Bumped minimum Node.js version to 20.11.0 (`import.meta.dirname` requirement)
95
+ - Updated CI matrix to Node 22 and 24
96
+
97
+ ## [0.1.1] - 2026-03-30
98
+
99
+ ### Changed
100
+
101
+ - Replaced stb_image_write with libjpeg-turbo for lossy JPEG encoding (better quality per byte)
102
+ - Split monolithic `qpdf_addon.cc` into focused modules: `jpeg.cc`, `images.cc`, `qpdf_addon.cc`
103
+ - Extracted shared `forEachImage()` template helper
104
+ - Bumped minimum Node.js version to 20.11.0
105
+
106
+ ### Fixed
107
+
108
+ - Removed global mutex that serialized concurrent compress() calls
109
+ - Isolated setjmp/longjmp in C-style functions to avoid UB with C++ destructors
110
+ - Fixed prebuild tarball structure so install script finds the .node file
111
+ - Added mode validation (reject invalid mode strings)
112
+
113
+ ## [0.1.0] - 2026-03-30
114
+
115
+ ### Added
116
+
117
+ - Native PDF compression via QPDF 12.3.2 with single `compress()` API
118
+ - Flate compression (level 9) for all streams
119
+ - Lossless JPEG optimization via libjpeg-turbo
120
+ - Image deduplication (content-hash based)
121
+ - PDF repair merged into compress pipeline
122
+ - Prebuilt binaries for macOS (arm64, x64), Linux glibc (x64, arm64, arm), Linux musl (x64, arm64), Windows (x64, arm64)
123
+ - Automatic prebuild download with source compilation fallback
124
+ - N-API addon for ABI stability across Node.js versions
125
+
126
+ ### Infrastructure
127
+
128
+ - CI matrix: 3 OS × 3 Node versions (20, 22, 24)
129
+ - Release pipeline with prebuild generation and npm provenance
130
+ - QEMU-based cross-compilation for Linux arm64/arm and musl variants
131
+ - vcpkg integration for Windows static linking
package/README.md CHANGED
@@ -189,6 +189,24 @@ Compresses a PDF document. Automatically repairs damaged PDFs.
189
189
  - Only replaces images where the result is actually smaller
190
190
  - Skips tiny images (< 50×50 px)
191
191
 
192
+ ### `concurrency(value?): number`
193
+
194
+ Gets or sets the maximum number of concurrent compress operations dispatched to the thread pool.
195
+
196
+ The default is the number of CPU cores (`os.availableParallelism()`). A value of `0` resets to the default.
197
+
198
+ Excess calls are queued in JavaScript, preventing libuv thread pool starvation.
199
+
200
+ ```typescript
201
+ import { compress, concurrency } from 'qpdf-compress';
202
+
203
+ concurrency(); // 8 (CPU cores)
204
+ concurrency(2); // limit to 2 concurrent operations
205
+ concurrency(0); // reset to default
206
+ ```
207
+
208
+ ---
209
+
192
210
  ## ⚙️ How it works
193
211
 
194
212
  This package embeds [QPDF](https://github.com/qpdf/qpdf) (v12.3.2) as a statically linked C++ library, exposed to Node.js via N-API. Lossless JPEG optimization uses [libjpeg-turbo](https://libjpeg-turbo.org/) at the DCT coefficient level. Image recompression in lossy mode also uses libjpeg-turbo for JPEG encoding. TrueType font subsetting is handled by a custom binary parser that reads cmap tables, resolves composite glyph dependencies, and rebuilds glyf/loca/hmtx tables with only the used glyphs.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Gets or sets the maximum number of concurrent operations.
3
+ *
4
+ * The default value is the number of CPU cores (`os.availableParallelism()`).
5
+ * A value of `0` resets to the default.
6
+ */
7
+ export declare function concurrency(value?: number): number;
8
+ export declare function withConcurrency<T>(fn: () => Promise<T>): Promise<T>;
9
+ //# sourceMappingURL=concurrency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../lib/concurrency.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEnE"}
@@ -0,0 +1,22 @@
1
+ import { availableParallelism } from 'node:os';
2
+ import pLimit from 'p-limit';
3
+ let limit = pLimit(availableParallelism());
4
+ /**
5
+ * Gets or sets the maximum number of concurrent operations.
6
+ *
7
+ * The default value is the number of CPU cores (`os.availableParallelism()`).
8
+ * A value of `0` resets to the default.
9
+ */
10
+ export function concurrency(value) {
11
+ if (value !== undefined) {
12
+ if (!Number.isInteger(value) || value < 0) {
13
+ throw new TypeError('Concurrency must be a non-negative integer');
14
+ }
15
+ limit = pLimit(value === 0 ? availableParallelism() : value);
16
+ }
17
+ return limit.concurrency;
18
+ }
19
+ export function withConcurrency(fn) {
20
+ return limit(fn);
21
+ }
22
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../lib/concurrency.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,MAA8B,MAAM,SAAS,CAAC;AAErD,IAAI,KAAK,GAAkB,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,GAAG,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,KAAK,CAAC,WAAW,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,eAAe,CAAI,EAAoB;IACrD,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC;AACnB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -16,5 +16,6 @@ export declare function compress(input: PdfInput, options: CompressOptions & {
16
16
  output: string;
17
17
  }): Promise<void>;
18
18
  export declare function compress(input: PdfInput, options?: CompressOptions): Promise<Buffer>;
19
+ export { concurrency } from './concurrency.js';
19
20
  export type { CompressOptions } from './types.js';
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAY/D,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;AACjB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAqBtF,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAY/D,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;AACjB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAuBtF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { withConcurrency } from './concurrency.js';
4
5
  const require = createRequire(import.meta.url);
5
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
7
  const addonDir = resolve(__dirname, '..', 'build', 'Release');
@@ -23,10 +24,11 @@ export async function compress(input, options) {
23
24
  throw new TypeError('Input must be a Buffer or file path string');
24
25
  }
25
26
  const stripMetadata = options?.stripMetadata ?? true;
26
- return addon.compress(input, {
27
+ return withConcurrency(() => addon.compress(input, {
27
28
  ...(options?.lossy ? { lossy: true } : {}),
28
29
  ...(stripMetadata ? { stripMetadata: true } : {}),
29
30
  ...(options?.output ? { output: options.output } : {}),
30
- });
31
+ }));
31
32
  }
33
+ export { concurrency } from './concurrency.js';
32
34
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,KAAK,GAAgB,OAAO,CAAC,qCAAqC,CAAC,CAAC;AAqB1E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAe,EAAE,OAAyB;IACvE,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;IACrD,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC3B,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAA2B,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,KAAK,GAAgB,OAAO,CAAC,qCAAqC,CAAC,CAAC;AAqB1E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAe,EAAE,OAAyB;IACvE,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;IACrD,OAAO,eAAe,CAAC,GAAG,EAAE,CAC1B,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE;QACpB,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC,CACuB,CAAC;AAC9B,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { availableParallelism } from 'node:os';
2
+ import pLimit, { type LimitFunction } from 'p-limit';
3
+
4
+ let limit: LimitFunction = pLimit(availableParallelism());
5
+
6
+ /**
7
+ * Gets or sets the maximum number of concurrent operations.
8
+ *
9
+ * The default value is the number of CPU cores (`os.availableParallelism()`).
10
+ * A value of `0` resets to the default.
11
+ */
12
+ export function concurrency(value?: number): number {
13
+ if (value !== undefined) {
14
+ if (!Number.isInteger(value) || value < 0) {
15
+ throw new TypeError('Concurrency must be a non-negative integer');
16
+ }
17
+ limit = pLimit(value === 0 ? availableParallelism() : value);
18
+ }
19
+ return limit.concurrency;
20
+ }
21
+
22
+ export function withConcurrency<T>(fn: () => Promise<T>): Promise<T> {
23
+ return limit(fn);
24
+ }
package/lib/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { withConcurrency } from './concurrency.js';
4
5
  import type { CompressOptions, NativeAddon } from './types.js';
5
6
 
6
7
  const require = createRequire(import.meta.url);
@@ -45,11 +46,14 @@ export async function compress(input: PdfInput, options?: CompressOptions): Prom
45
46
  throw new TypeError('Input must be a Buffer or file path string');
46
47
  }
47
48
  const stripMetadata = options?.stripMetadata ?? true;
48
- return addon.compress(input, {
49
- ...(options?.lossy ? { lossy: true } : {}),
50
- ...(stripMetadata ? { stripMetadata: true } : {}),
51
- ...(options?.output ? { output: options.output } : {}),
52
- }) as Promise<Buffer | void>;
49
+ return withConcurrency(() =>
50
+ addon.compress(input, {
51
+ ...(options?.lossy ? { lossy: true } : {}),
52
+ ...(stripMetadata ? { stripMetadata: true } : {}),
53
+ ...(options?.output ? { output: options.output } : {}),
54
+ }),
55
+ ) as Promise<Buffer | void>;
53
56
  }
54
57
 
58
+ export { concurrency } from './concurrency.js';
55
59
  export type { CompressOptions } from './types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qpdf-compress",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Native PDF compression for Node.js, powered by QPDF",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -38,7 +38,10 @@
38
38
  "src",
39
39
  "lib",
40
40
  "scripts",
41
- "binding.gyp"
41
+ "binding.gyp",
42
+ "README.md",
43
+ "CHANGELOG.md",
44
+ "LICENSE"
42
45
  ],
43
46
  "repository": {
44
47
  "type": "git",
@@ -71,10 +74,16 @@
71
74
  "lint": "eslint --fix .",
72
75
  "lint:check": "eslint .",
73
76
  "format": "prettier --write .",
74
- "format:check": "prettier --check ."
77
+ "format:check": "prettier --check .",
78
+ "prepare": "husky"
79
+ },
80
+ "lint-staged": {
81
+ "*.{ts,js,json,md,yaml,yml}": "prettier --write",
82
+ "*.{ts,js}": "eslint --max-warnings 0"
75
83
  },
76
84
  "dependencies": {
77
- "node-addon-api": "^8.0.0"
85
+ "node-addon-api": "^8.0.0",
86
+ "p-limit": "^7.3.0"
78
87
  },
79
88
  "devDependencies": {
80
89
  "@eslint/js": "^10.0.1",
@@ -82,10 +91,12 @@
82
91
  "eslint": "^10.1.0",
83
92
  "eslint-config-prettier": "^10.1.8",
84
93
  "eslint-plugin-prettier": "^5.5.5",
94
+ "husky": "^9.1.7",
95
+ "lint-staged": "^16.4.0",
85
96
  "node-gyp": "^11.0.0",
86
97
  "prettier": "^3.8.1",
87
98
  "typescript": "^5.9.3",
88
99
  "typescript-eslint": "^8.57.2",
89
100
  "vitest": "^4.1.2"
90
101
  }
91
- }
102
+ }