simple-zstd 2.0.0 → 2.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.
@@ -1,94 +1,81 @@
1
1
  "use strict";
2
2
  // This is a generic class for creating a queue of worker processes.
3
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
4
- if (kind === "m") throw new TypeError("Private method is not writable");
5
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
6
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
7
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
8
- };
9
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
10
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
11
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
12
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
13
- };
14
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
15
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
16
5
  };
17
- var _ProcessQueue_instances, _ProcessQueue_targetSize, _ProcessQueue_queue, _ProcessQueue_factory, _ProcessQueue_destroy, _ProcessQueue_hitCount, _ProcessQueue_missCount, _ProcessQueue_destroyed, _ProcessQueue_createResource;
18
6
  Object.defineProperty(exports, "__esModule", { value: true });
19
7
  const debug_1 = __importDefault(require("debug"));
20
8
  const debug = (0, debug_1.default)('SimpleZSTDQueue');
21
9
  class ProcessQueue {
10
+ #targetSize;
11
+ #queue;
12
+ #factory;
13
+ #destroy;
14
+ #hitCount;
15
+ #missCount;
16
+ #destroyed;
22
17
  constructor(targetSize, factory, destroy) {
23
- _ProcessQueue_instances.add(this);
24
- _ProcessQueue_targetSize.set(this, void 0);
25
- _ProcessQueue_queue.set(this, void 0);
26
- _ProcessQueue_factory.set(this, void 0);
27
- _ProcessQueue_destroy.set(this, void 0);
28
- _ProcessQueue_hitCount.set(this, void 0);
29
- _ProcessQueue_missCount.set(this, void 0);
30
- _ProcessQueue_destroyed.set(this, void 0);
31
18
  debug('constructor', targetSize);
32
- __classPrivateFieldSet(this, _ProcessQueue_targetSize, targetSize, "f");
33
- __classPrivateFieldSet(this, _ProcessQueue_queue, [], "f");
34
- __classPrivateFieldSet(this, _ProcessQueue_factory, factory, "f");
35
- __classPrivateFieldSet(this, _ProcessQueue_destroy, destroy, "f");
36
- __classPrivateFieldSet(this, _ProcessQueue_hitCount, 0, "f");
37
- __classPrivateFieldSet(this, _ProcessQueue_missCount, 0, "f");
38
- __classPrivateFieldSet(this, _ProcessQueue_destroyed, false, "f");
19
+ this.#targetSize = targetSize;
20
+ this.#queue = [];
21
+ this.#factory = factory;
22
+ this.#destroy = destroy;
23
+ this.#hitCount = 0;
24
+ this.#missCount = 0;
25
+ this.#destroyed = false;
39
26
  for (let i = 0; i < targetSize || 0; i += 1) {
40
- __classPrivateFieldGet(this, _ProcessQueue_instances, "m", _ProcessQueue_createResource).call(this);
27
+ this.#createResource();
41
28
  }
42
29
  }
43
30
  get hits() {
44
- return __classPrivateFieldGet(this, _ProcessQueue_hitCount, "f");
31
+ return this.#hitCount;
45
32
  }
46
33
  get misses() {
47
- return __classPrivateFieldGet(this, _ProcessQueue_missCount, "f");
34
+ return this.#missCount;
35
+ }
36
+ async #createResource() {
37
+ debug('createResource?', this.#queue.length);
38
+ if (this.#destroyed) {
39
+ debug('createResource skipped - queue destroyed');
40
+ return;
41
+ }
42
+ if (this.#queue.length < this.#targetSize) {
43
+ debug('createResource call factory');
44
+ this.#queue.push(this.#factory());
45
+ }
48
46
  }
49
47
  async acquire() {
50
48
  debug('acquire');
51
- const attempt = __classPrivateFieldGet(this, _ProcessQueue_queue, "f").pop();
49
+ const attempt = this.#queue.pop();
52
50
  if (attempt) {
53
51
  debug('acquire hit');
54
- if (!__classPrivateFieldGet(this, _ProcessQueue_destroyed, "f")) {
52
+ if (!this.#destroyed) {
55
53
  setImmediate(() => {
56
54
  // Double-check destroyed flag in case it changed
57
- if (!__classPrivateFieldGet(this, _ProcessQueue_destroyed, "f")) {
58
- __classPrivateFieldGet(this, _ProcessQueue_instances, "m", _ProcessQueue_createResource).call(this);
55
+ if (!this.#destroyed) {
56
+ this.#createResource();
59
57
  }
60
58
  });
61
59
  }
62
- __classPrivateFieldSet(this, _ProcessQueue_hitCount, __classPrivateFieldGet(this, _ProcessQueue_hitCount, "f") + 1, "f");
60
+ this.#hitCount += 1;
63
61
  return attempt;
64
62
  }
65
63
  debug('acquire miss');
66
- __classPrivateFieldSet(this, _ProcessQueue_missCount, __classPrivateFieldGet(this, _ProcessQueue_missCount, "f") + 1, "f");
67
- return __classPrivateFieldGet(this, _ProcessQueue_factory, "f").call(this);
64
+ this.#missCount += 1;
65
+ return this.#factory();
68
66
  }
69
67
  async destroy() {
70
- debug('destroy', __classPrivateFieldGet(this, _ProcessQueue_queue, "f").length);
71
- __classPrivateFieldSet(this, _ProcessQueue_destroyed, true, "f");
68
+ debug('destroy', this.#queue.length);
69
+ this.#destroyed = true;
72
70
  const destroyPromises = [];
73
- while (__classPrivateFieldGet(this, _ProcessQueue_queue, "f").length > 0) {
74
- const p = __classPrivateFieldGet(this, _ProcessQueue_queue, "f").pop();
71
+ while (this.#queue.length > 0) {
72
+ const p = this.#queue.pop();
75
73
  if (p) {
76
- destroyPromises.push(Promise.resolve(__classPrivateFieldGet(this, _ProcessQueue_destroy, "f").call(this, p)));
74
+ destroyPromises.push(Promise.resolve(this.#destroy(p)));
77
75
  }
78
76
  }
79
77
  await Promise.all(destroyPromises);
80
78
  }
81
79
  }
82
- _ProcessQueue_targetSize = new WeakMap(), _ProcessQueue_queue = new WeakMap(), _ProcessQueue_factory = new WeakMap(), _ProcessQueue_destroy = new WeakMap(), _ProcessQueue_hitCount = new WeakMap(), _ProcessQueue_missCount = new WeakMap(), _ProcessQueue_destroyed = new WeakMap(), _ProcessQueue_instances = new WeakSet(), _ProcessQueue_createResource = async function _ProcessQueue_createResource() {
83
- debug('createResource?', __classPrivateFieldGet(this, _ProcessQueue_queue, "f").length);
84
- if (__classPrivateFieldGet(this, _ProcessQueue_destroyed, "f")) {
85
- debug('createResource skipped - queue destroyed');
86
- return;
87
- }
88
- if (__classPrivateFieldGet(this, _ProcessQueue_queue, "f").length < __classPrivateFieldGet(this, _ProcessQueue_targetSize, "f")) {
89
- debug('createResource call factory');
90
- __classPrivateFieldGet(this, _ProcessQueue_queue, "f").push(__classPrivateFieldGet(this, _ProcessQueue_factory, "f").call(this));
91
- }
92
- };
93
80
  exports.default = ProcessQueue;
94
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvY2Vzcy1xdWV1ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wcm9jZXNzLXF1ZXVlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxvRUFBb0U7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRXBFLGtEQUEwQjtBQUUxQixNQUFNLEtBQUssR0FBRyxJQUFBLGVBQUssRUFBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBRXZDLE1BQXFCLFlBQVk7SUFlL0IsWUFDRSxVQUFrQixFQUNsQixPQUFpQyxFQUNqQyxPQUE4Qzs7UUFqQmhELDJDQUFZO1FBRVosc0NBQWtDO1FBRWxDLHdDQUFtQztRQUVuQyx3Q0FBZ0Q7UUFFaEQseUNBQVU7UUFFViwwQ0FBVztRQUVYLDBDQUFvQjtRQU9sQixLQUFLLENBQUMsYUFBYSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ2pDLHVCQUFBLElBQUksNEJBQWUsVUFBVSxNQUFBLENBQUM7UUFDOUIsdUJBQUEsSUFBSSx1QkFBVSxFQUFFLE1BQUEsQ0FBQztRQUNqQix1QkFBQSxJQUFJLHlCQUFZLE9BQU8sTUFBQSxDQUFDO1FBQ3hCLHVCQUFBLElBQUkseUJBQVksT0FBTyxNQUFBLENBQUM7UUFFeEIsdUJBQUEsSUFBSSwwQkFBYSxDQUFDLE1BQUEsQ0FBQztRQUNuQix1QkFBQSxJQUFJLDJCQUFjLENBQUMsTUFBQSxDQUFDO1FBQ3BCLHVCQUFBLElBQUksMkJBQWMsS0FBSyxNQUFBLENBQUM7UUFFeEIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLFVBQVUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQzVDLHVCQUFBLElBQUksNkRBQWdCLE1BQXBCLElBQUksQ0FBa0IsQ0FBQztRQUN6QixDQUFDO0lBQ0gsQ0FBQztJQUVELElBQUksSUFBSTtRQUNOLE9BQU8sdUJBQUEsSUFBSSw4QkFBVSxDQUFDO0lBQ3hCLENBQUM7SUFFRCxJQUFJLE1BQU07UUFDUixPQUFPLHVCQUFBLElBQUksK0JBQVcsQ0FBQztJQUN6QixDQUFDO0lBY0QsS0FBSyxDQUFDLE9BQU87UUFDWCxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDakIsTUFBTSxPQUFPLEdBQUcsdUJBQUEsSUFBSSwyQkFBTyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRWxDLElBQUksT0FBTyxFQUFFLENBQUM7WUFDWixLQUFLLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDckIsSUFBSSxDQUFDLHVCQUFBLElBQUksK0JBQVcsRUFBRSxDQUFDO2dCQUNyQixZQUFZLENBQUMsR0FBRyxFQUFFO29CQUNoQixpREFBaUQ7b0JBQ2pELElBQUksQ0FBQyx1QkFBQSxJQUFJLCtCQUFXLEVBQUUsQ0FBQzt3QkFDckIsdUJBQUEsSUFBSSw2REFBZ0IsTUFBcEIsSUFBSSxDQUFrQixDQUFDO29CQUN6QixDQUFDO2dCQUNILENBQUMsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztZQUNELGlIQUFrQixDQUFDLE1BQUEsQ0FBQztZQUNwQixPQUFPLE9BQU8sQ0FBQztRQUNqQixDQUFDO1FBRUQsS0FBSyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ3RCLG1IQUFtQixDQUFDLE1BQUEsQ0FBQztRQUNyQixPQUFPLHVCQUFBLElBQUksNkJBQVMsTUFBYixJQUFJLENBQVcsQ0FBQztJQUN6QixDQUFDO0lBRUQsS0FBSyxDQUFDLE9BQU87UUFDWCxLQUFLLENBQUMsU0FBUyxFQUFFLHVCQUFBLElBQUksMkJBQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNyQyx1QkFBQSxJQUFJLDJCQUFjLElBQUksTUFBQSxDQUFDO1FBQ3ZCLE1BQU0sZUFBZSxHQUFvQixFQUFFLENBQUM7UUFDNUMsT0FBTyx1QkFBQSxJQUFJLDJCQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQzlCLE1BQU0sQ0FBQyxHQUFHLHVCQUFBLElBQUksMkJBQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztZQUM1QixJQUFJLENBQUMsRUFBRSxDQUFDO2dCQUNOLGVBQWUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyx1QkFBQSxJQUFJLDZCQUFTLE1BQWIsSUFBSSxFQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMxRCxDQUFDO1FBQ0gsQ0FBQztRQUNELE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUNyQyxDQUFDO0NBQ0Y7K1ZBL0NDLEtBQUs7SUFDSCxLQUFLLENBQUMsaUJBQWlCLEVBQUUsdUJBQUEsSUFBSSwyQkFBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzdDLElBQUksdUJBQUEsSUFBSSwrQkFBVyxFQUFFLENBQUM7UUFDcEIsS0FBSyxDQUFDLDBDQUEwQyxDQUFDLENBQUM7UUFDbEQsT0FBTztJQUNULENBQUM7SUFDRCxJQUFJLHVCQUFBLElBQUksMkJBQU8sQ0FBQyxNQUFNLEdBQUcsdUJBQUEsSUFBSSxnQ0FBWSxFQUFFLENBQUM7UUFDMUMsS0FBSyxDQUFDLDZCQUE2QixDQUFDLENBQUM7UUFDckMsdUJBQUEsSUFBSSwyQkFBTyxDQUFDLElBQUksQ0FBQyx1QkFBQSxJQUFJLDZCQUFTLE1BQWIsSUFBSSxDQUFXLENBQUMsQ0FBQztJQUNwQyxDQUFDO0FBQ0gsQ0FBQztrQkFyRGtCLFlBQVkifQ==
81
+ //# sourceMappingURL=process-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-queue.js","sourceRoot":"","sources":["../../src/process-queue.ts"],"names":[],"mappings":";AAAA,oEAAoE;;;;;AAEpE,kDAA0B;AAE1B,MAAM,KAAK,GAAG,IAAA,eAAK,EAAC,iBAAiB,CAAC,CAAC;AAEvC,MAAqB,YAAY;IAC/B,WAAW,CAAC;IAEZ,MAAM,CAA4B;IAElC,QAAQ,CAA2B;IAEnC,QAAQ,CAAwC;IAEhD,SAAS,CAAC;IAEV,UAAU,CAAC;IAEX,UAAU,CAAU;IAEpB,YACE,UAAkB,EAClB,OAAiC,EACjC,OAA8C;QAE9C,KAAK,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QAExB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QAExB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,KAAK,CAAC,0CAA0C,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,KAAK,CAAC,6BAA6B,CAAC,CAAC;YACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,KAAK,CAAC,SAAS,CAAC,CAAC;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAElC,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,aAAa,CAAC,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;gBACrB,YAAY,CAAC,GAAG,EAAE;oBAChB,iDAAiD;oBACjD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;wBACrB,IAAI,CAAC,eAAe,EAAE,CAAC;oBACzB,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;YACD,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;YACpB,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,cAAc,CAAC,CAAC;QACtB,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,MAAM,eAAe,GAAoB,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,CAAC;gBACN,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACrC,CAAC;CACF;AA1FD,+BA0FC"}
@@ -17,6 +17,12 @@ export interface DecompressOpts {
17
17
  spawnOptions?: SpawnOptions;
18
18
  streamOptions?: DuplexOptions;
19
19
  }
20
+ export interface CreateDictionaryOpts {
21
+ trainingFiles: Array<string | Buffer>;
22
+ maxDictSize?: number;
23
+ zstdOptions?: string[];
24
+ spawnOptions?: SpawnOptions;
25
+ }
20
26
  export interface PoolOpts {
21
27
  compressQueueSize?: number;
22
28
  decompressQueueSize?: number;
package/dist/src/types.js CHANGED
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "simple-zstd",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Node.js interface to the system installed zstd.",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
7
  "engines": {
8
- "node": ">=20.0.0"
8
+ "node": ">=22.0.0"
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc",
12
12
  "prepublish": "tsc",
13
- "lint": "eslint . --ext .ts",
13
+ "lint": "eslint .",
14
14
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
15
15
  "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
16
- "test": "timeout --signal=TERM --kill-after=5s 120s node --test --require ts-node/register test/test.ts || true",
17
- "test-debug": "DEBUG=SimpleZSTD,SimpleZSTDQueue timeout --signal=TERM --kill-after=5s 120s node --test --require ts-node/register test/test.ts || true",
18
- "test-ci": "timeout --signal=TERM --kill-after=5s 120s node --test --require ts-node/register test/test.ts && exit 0 || exit 1",
19
- "coverage": "timeout --signal=TERM --kill-after=5s 120s node --test --experimental-test-coverage --require ts-node/register test/test.ts || true"
16
+ "test": "node --test --require ts-node/register test/**/*.test.ts || true",
17
+ "test-debug": "DEBUG=SimpleZSTD,SimpleZSTDQueue timeout -t 120s -- node --test --require ts-node/register test/**/*.test.ts || true",
18
+ "test-ci": "node --test --require ts-node/register test/**/*.test.ts && exit 0 || exit 1",
19
+ "coverage": "node --test --experimental-test-coverage --require ts-node/register test/**/*.test.ts || true"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
@@ -54,15 +54,16 @@
54
54
  },
55
55
  "homepage": "https://github.com/Stieneee/simple-zstd#readme",
56
56
  "devDependencies": {
57
+ "@eslint/js": "^10.0.1",
57
58
  "@release-it/conventional-changelog": "^10.0.1",
58
59
  "@types/debug": "^4.1.12",
59
- "@types/node": "^24.10.0",
60
- "@typescript-eslint/eslint-plugin": "^8.46.3",
61
- "@typescript-eslint/parser": "^8.46.3",
62
- "eslint": "^9.39.1",
60
+ "@types/node": "^25.5.0",
61
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
62
+ "@typescript-eslint/parser": "^8.57.0",
63
+ "eslint": "^10.0.3",
63
64
  "eslint-config-prettier": "^10.1.8",
64
- "globals": "^16.5.0",
65
- "prettier": "^3.6.2",
65
+ "globals": "^17.4.0",
66
+ "prettier": "^3.8.1",
66
67
  "release-it": "^19.0.6",
67
68
  "ts-node": "^10.9.1",
68
69
  "typescript": "^5.9.3"
@@ -70,7 +71,6 @@
70
71
  "dependencies": {
71
72
  "debug": "^4.3.4",
72
73
  "is-zst": "^1.0.0",
73
- "tmp": "^0.2.1",
74
74
  "tmp-promise": "^3.0.3"
75
75
  },
76
76
  "packageManager": "npm@11.6.2"
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import fs from 'node:fs';
2
- import { writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { writeFile, readFile, copyFile } from 'node:fs/promises';
3
4
  import { createHash } from 'node:crypto';
4
5
  import { Readable, Duplex, PassThrough } from 'node:stream';
5
6
  import { pipeline } from 'node:stream/promises';
6
- import { execSync } from 'node:child_process';
7
+ import { execSync, execFile } from 'node:child_process';
7
8
 
8
9
  import isZst from 'is-zst';
9
- import { file } from 'tmp-promise';
10
+ import { dir } from 'tmp-promise';
10
11
  import Debug from 'debug';
11
12
 
12
13
  const debug = Debug('SimpleZSTD');
@@ -15,14 +16,35 @@ import ProcessQueue from './process-queue';
15
16
  import BufferWritable from './buffer-writable';
16
17
  import ProcessDuplex from './process-duplex';
17
18
  import PeekPassThrough from './peek-transform';
18
- import { ZSTDOpts, PoolOpts } from './types';
19
+ import { ZSTDOpts, PoolOpts, CreateDictionaryOpts } from './types';
19
20
 
20
21
  // Export types for consumers
21
- export type { ZSTDOpts, PoolOpts, CompressOpts, DecompressOpts, DictionaryObject } from './types';
22
+ export type {
23
+ ZSTDOpts,
24
+ PoolOpts,
25
+ CompressOpts,
26
+ DecompressOpts,
27
+ DictionaryObject,
28
+ CreateDictionaryOpts,
29
+ } from './types';
22
30
 
23
31
  // Dictionary cache to avoid recreating temp files for the same dictionary buffer
24
32
  // Map: hash -> { path: string, cleanup: () => void, refCount: number }
25
- const dictionaryCache = new Map<string, { path: string; cleanup: () => void; refCount: number }>();
33
+ const dictionaryCache = new Map<
34
+ string,
35
+ { path: string; cleanup: () => Promise<void>; refCount: number }
36
+ >();
37
+
38
+ async function createTempDirectory(prefix: string): Promise<{
39
+ directoryPath: string;
40
+ cleanup: () => Promise<void>;
41
+ }> {
42
+ const { path: tempDirectory, cleanup } = await dir({ prefix, unsafeCleanup: true });
43
+ return {
44
+ directoryPath: tempDirectory,
45
+ cleanup: () => Promise.resolve(cleanup()),
46
+ };
47
+ }
26
48
 
27
49
  function hashBuffer(buffer: Buffer): string {
28
50
  return createHash('sha256').update(buffer).digest('hex');
@@ -51,17 +73,18 @@ async function getCachedDictionaryPath(
51
73
  }
52
74
 
53
75
  debug(`Dictionary cache miss: ${hash.slice(0, 8)}... - creating temp file`);
54
- const { path, cleanup: tmpCleanup } = await file({ prefix: 'zstd-dict-' });
55
- await writeFile(path, dictionary);
76
+ const { directoryPath, cleanup: tmpCleanup } = await createTempDirectory('zstd-dict-cache-');
77
+ const dictionaryPath = path.join(directoryPath, 'dictionary.zstd');
78
+ await writeFile(dictionaryPath, dictionary);
56
79
 
57
80
  dictionaryCache.set(hash, {
58
- path,
81
+ path: dictionaryPath,
59
82
  cleanup: tmpCleanup,
60
83
  refCount: 1,
61
84
  });
62
85
 
63
86
  return {
64
- path,
87
+ path: dictionaryPath,
65
88
  cleanup: () => {
66
89
  const cached = dictionaryCache.get(hash);
67
90
  if (cached) {
@@ -115,7 +138,6 @@ try {
115
138
  async function CreateCompressStream(compLevel: number, opts: ZSTDOpts): Promise<Duplex> {
116
139
  let lvl = compLevel;
117
140
  let zo = opts.zstdOptions || [];
118
- let path: string | null = null;
119
141
  let cleanup: () => void = () => null;
120
142
 
121
143
  if (!lvl) lvl = 3;
@@ -126,15 +148,22 @@ async function CreateCompressStream(compLevel: number, opts: ZSTDOpts): Promise<
126
148
  zo = [...zo, '-D', `${opts.dictionary.path}`];
127
149
  } else if (Buffer.isBuffer(opts.dictionary)) {
128
150
  // Use cached dictionary to avoid recreating temp files
129
- ({ path, cleanup } = await getCachedDictionaryPath(opts.dictionary));
130
- zo = [...zo, '-D', `${path}`];
151
+ const cached = await getCachedDictionaryPath(opts.dictionary);
152
+ cleanup = cached.cleanup;
153
+ zo = [...zo, '-D', cached.path];
131
154
  }
132
155
 
133
156
  let c: Duplex;
134
157
 
135
158
  try {
136
159
  debug(bin, ['-zc', `-${lvl}`, ...zo], opts.spawnOptions, opts.streamOptions);
137
- c = new ProcessDuplex(bin, ['-zc', `-${lvl}`, ...zo], opts.spawnOptions, opts.streamOptions);
160
+ c = new ProcessDuplex({
161
+ command: bin,
162
+ args: ['-zc', `-${lvl}`, ...zo],
163
+ spawnOptions: opts.spawnOptions,
164
+ streamOptions: opts.streamOptions,
165
+ nonZeroExitPolicy: true,
166
+ });
138
167
  } catch (err) {
139
168
  // cleanup if error;
140
169
  cleanup();
@@ -143,11 +172,6 @@ async function CreateCompressStream(compLevel: number, opts: ZSTDOpts): Promise<
143
172
 
144
173
  c.on('exit', (code: number, signal) => {
145
174
  debug('c exit', code, signal);
146
- if (code !== 0) {
147
- setImmediate(() => {
148
- c.destroy(new Error(`zstd exited non zero. code: ${code} signal: ${signal}`));
149
- });
150
- }
151
175
  cleanup();
152
176
  });
153
177
 
@@ -183,7 +207,6 @@ function CompressBuffer(buffer: Buffer, c: Duplex): Promise<Buffer> {
183
207
  async function CreateDecompressStream(opts: ZSTDOpts): Promise<Duplex> {
184
208
  // Dictionary
185
209
  let zo = opts.zstdOptions || [];
186
- let path: string | null = null;
187
210
  let cleanup: () => void = () => null;
188
211
 
189
212
  let terminate = false;
@@ -192,15 +215,22 @@ async function CreateDecompressStream(opts: ZSTDOpts): Promise<Duplex> {
192
215
  zo = [...zo, '-D', `${opts.dictionary.path}`];
193
216
  } else if (Buffer.isBuffer(opts.dictionary)) {
194
217
  // Use cached dictionary to avoid recreating temp files
195
- ({ path, cleanup } = await getCachedDictionaryPath(opts.dictionary));
196
- zo = [...zo, '-D', `${path}`];
218
+ const cached = await getCachedDictionaryPath(opts.dictionary);
219
+ cleanup = cached.cleanup;
220
+ zo = [...zo, '-D', cached.path];
197
221
  }
198
222
 
199
223
  let d: Duplex;
200
224
 
201
225
  try {
202
226
  debug(bin, ['-dc', ...zo], opts.spawnOptions, opts.streamOptions);
203
- d = new ProcessDuplex(bin, ['-dc', ...zo], opts.spawnOptions, opts.streamOptions);
227
+ d = new ProcessDuplex({
228
+ command: bin,
229
+ args: ['-dc', ...zo],
230
+ spawnOptions: opts.spawnOptions,
231
+ streamOptions: opts.streamOptions,
232
+ nonZeroExitPolicy: () => !terminate,
233
+ });
204
234
  } catch (err) {
205
235
  // cleanup if error
206
236
  cleanup();
@@ -209,11 +239,6 @@ async function CreateDecompressStream(opts: ZSTDOpts): Promise<Duplex> {
209
239
 
210
240
  d.on('exit', (code: number, signal) => {
211
241
  debug('d exit', code, signal);
212
- if (code !== 0 && !terminate) {
213
- setImmediate(() => {
214
- d.destroy(new Error(`zstd exited non zero. code: ${code} signal: ${signal}`));
215
- });
216
- }
217
242
  cleanup();
218
243
  });
219
244
 
@@ -281,24 +306,138 @@ export function decompress(opts: ZSTDOpts = {}): Promise<Duplex> {
281
306
  }
282
307
 
283
308
  export async function decompressBuffer(buffer: Buffer, opts: ZSTDOpts = {}): Promise<Buffer> {
284
- const d = await CreateDecompressStream(opts);
285
- return DecompressBuffer(buffer, d);
309
+ // Preserve "smart decompression" behavior for non-zstd payloads.
310
+ if (!isZst(buffer)) {
311
+ return buffer;
312
+ }
313
+
314
+ let zo = opts.zstdOptions || [];
315
+ let cleanup: () => void = () => null;
316
+
317
+ if (opts.dictionary && 'path' in opts.dictionary) {
318
+ zo = [...zo, '-D', `${opts.dictionary.path}`];
319
+ } else if (Buffer.isBuffer(opts.dictionary)) {
320
+ const cachedDictionary = await getCachedDictionaryPath(opts.dictionary);
321
+ zo = [...zo, '-D', `${cachedDictionary.path}`];
322
+ cleanup = cachedDictionary.cleanup;
323
+ }
324
+
325
+ const args = ['-dc', ...zo];
326
+
327
+ return new Promise((resolve, reject) => {
328
+ debug(bin, args, opts.spawnOptions);
329
+ const child = execFile(
330
+ bin,
331
+ args,
332
+ {
333
+ ...(opts.spawnOptions ?? {}),
334
+ encoding: 'buffer',
335
+ maxBuffer: 512 * 1024 * 1024,
336
+ },
337
+ (error, stdout, stderr) => {
338
+ cleanup();
339
+
340
+ if (!error) {
341
+ resolve(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
342
+ return;
343
+ }
344
+
345
+ const code = typeof error.code === 'number' ? error.code : null;
346
+ const signal = error.signal ?? null;
347
+ const stdErrMessage = stderr?.toString().trim();
348
+ const errorMessage = stdErrMessage
349
+ ? `zstd decompression failed (code: ${code}, signal: ${signal}): ${stdErrMessage}`
350
+ : `zstd decompression failed (code: ${code}, signal: ${signal})`;
351
+
352
+ reject(new Error(errorMessage));
353
+ }
354
+ );
355
+
356
+ child.stdin?.end(buffer);
357
+ });
358
+ }
359
+
360
+ export async function createDictionary(opts: CreateDictionaryOpts): Promise<Buffer> {
361
+ if (!Array.isArray(opts.trainingFiles) || opts.trainingFiles.length === 0) {
362
+ throw new Error('createDictionary requires at least one training file or buffer');
363
+ }
364
+
365
+ const { path: tempRunDirectory, cleanup } = await dir({
366
+ prefix: 'zstd-dict-create-run-',
367
+ unsafeCleanup: true,
368
+ });
369
+
370
+ try {
371
+ const trainingPaths: string[] = [];
372
+ const outputPath = path.join(tempRunDirectory, 'dictionary.zstd');
373
+
374
+ for (const [index, trainingInput] of opts.trainingFiles.entries()) {
375
+ const stagedTrainingPath = path.join(tempRunDirectory, `training-${index}.bin`);
376
+
377
+ if (typeof trainingInput === 'string') {
378
+ await copyFile(trainingInput, stagedTrainingPath);
379
+ trainingPaths.push(stagedTrainingPath);
380
+ continue;
381
+ }
382
+
383
+ if (Buffer.isBuffer(trainingInput)) {
384
+ await writeFile(stagedTrainingPath, trainingInput);
385
+ trainingPaths.push(stagedTrainingPath);
386
+ continue;
387
+ }
388
+
389
+ throw new Error('createDictionary trainingFiles entries must be file paths or Buffers');
390
+ }
391
+
392
+ const args = ['--train', ...trainingPaths, '-o', outputPath];
393
+
394
+ if (opts.maxDictSize && opts.maxDictSize > 0) {
395
+ args.push(`--maxdict=${opts.maxDictSize}`);
396
+ }
397
+
398
+ if (opts.zstdOptions?.length) {
399
+ args.push(...opts.zstdOptions);
400
+ }
401
+
402
+ await new Promise<void>((resolve, reject) => {
403
+ debug(bin, args, opts.spawnOptions);
404
+ execFile(bin, args, opts.spawnOptions ?? {}, (error, _stdout, stderr) => {
405
+ if (!error) {
406
+ resolve();
407
+ return;
408
+ }
409
+
410
+ const code = typeof error.code === 'number' ? error.code : null;
411
+ const signal = error.signal ?? null;
412
+ const stdErrMessage = stderr?.toString().trim();
413
+ const errorMessage = stdErrMessage
414
+ ? `zstd dictionary training failed (code: ${code}, signal: ${signal}): ${stdErrMessage}`
415
+ : `zstd dictionary training failed (code: ${code}, signal: ${signal})`;
416
+
417
+ reject(new Error(errorMessage));
418
+ });
419
+ });
420
+
421
+ return readFile(outputPath);
422
+ } finally {
423
+ await cleanup();
424
+ }
286
425
  }
287
426
 
288
427
  // SimpleZSTD Class
289
428
  export class SimpleZSTD {
290
429
  #compressQueue!: ProcessQueue<Duplex>;
291
430
  #decompressQueue!: ProcessQueue<Duplex>;
292
- #compressDictCleanup: () => void = () => null;
293
- #decompressDictCleanup: () => void = () => null;
431
+ #compressDictCleanup: () => Promise<void> = async () => undefined;
432
+ #decompressDictCleanup: () => Promise<void> = async () => undefined;
294
433
  #ready;
295
434
  #poolOptions?: PoolOpts;
296
435
 
297
436
  private constructor(poolOptions?: PoolOpts) {
298
437
  debug('constructor', poolOptions);
299
438
  this.#poolOptions = poolOptions;
300
- this.#compressDictCleanup = () => null;
301
- this.#decompressDictCleanup = () => null;
439
+ this.#compressDictCleanup = async () => undefined;
440
+ this.#decompressDictCleanup = async () => undefined;
302
441
 
303
442
  this.#ready = new Promise((resolve, reject) => {
304
443
  (async () => {
@@ -309,10 +448,13 @@ export class SimpleZSTD {
309
448
  if (compressDict && 'path' in compressDict) {
310
449
  compressDictPath = compressDict.path;
311
450
  } else if (compressDict && Buffer.isBuffer(compressDict)) {
312
- const { path, cleanup } = await file({ prefix: 'zstd-dict-' });
451
+ const { directoryPath, cleanup } = await createTempDirectory(
452
+ 'zstd-dict-pool-compress-'
453
+ );
454
+ const dictionaryPath = path.join(directoryPath, 'dictionary.zstd');
313
455
  this.#compressDictCleanup = cleanup;
314
- await writeFile(path, compressDict);
315
- compressDictPath = path;
456
+ await writeFile(dictionaryPath, compressDict);
457
+ compressDictPath = dictionaryPath;
316
458
  }
317
459
 
318
460
  // Handle decompress queue dictionary
@@ -321,10 +463,13 @@ export class SimpleZSTD {
321
463
  if (decompressDict && 'path' in decompressDict) {
322
464
  decompressDictPath = decompressDict.path;
323
465
  } else if (decompressDict && Buffer.isBuffer(decompressDict)) {
324
- const { path, cleanup } = await file({ prefix: 'zstd-dict-' });
466
+ const { directoryPath, cleanup } = await createTempDirectory(
467
+ 'zstd-dict-pool-decompress-'
468
+ );
469
+ const dictionaryPath = path.join(directoryPath, 'dictionary.zstd');
325
470
  this.#decompressDictCleanup = cleanup;
326
- await writeFile(path, decompressDict);
327
- decompressDictPath = path;
471
+ await writeFile(dictionaryPath, decompressDict);
472
+ decompressDictPath = dictionaryPath;
328
473
  }
329
474
 
330
475
  this.#compressQueue = new ProcessQueue(
@@ -381,10 +526,10 @@ export class SimpleZSTD {
381
526
  })();
382
527
  }).catch((err) => {
383
528
  debug('ready error', err);
384
- this.#compressDictCleanup();
385
- this.#decompressDictCleanup();
386
- this.#compressDictCleanup = () => null;
387
- this.#decompressDictCleanup = () => null;
529
+ void this.#compressDictCleanup();
530
+ void this.#decompressDictCleanup();
531
+ this.#compressDictCleanup = async () => undefined;
532
+ this.#decompressDictCleanup = async () => undefined;
388
533
  });
389
534
  }
390
535
 
@@ -414,10 +559,10 @@ export class SimpleZSTD {
414
559
 
415
560
  async destroy() {
416
561
  await Promise.all([this.#compressQueue.destroy(), this.#decompressQueue.destroy()]);
417
- this.#compressDictCleanup();
418
- this.#decompressDictCleanup();
419
- this.#compressDictCleanup = () => null;
420
- this.#decompressDictCleanup = () => null;
562
+ await this.#compressDictCleanup();
563
+ await this.#decompressDictCleanup();
564
+ this.#compressDictCleanup = async () => undefined;
565
+ this.#decompressDictCleanup = async () => undefined;
421
566
  }
422
567
 
423
568
  /**