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.
- package/.github/workflows/ci.yml +5 -3
- package/.github/workflows/release.yml +9 -3
- package/CHANGELOG.md +6 -0
- package/README.md +39 -2
- package/dist/src/buffer-writable.js +6 -19
- package/dist/src/buffer-writable.js.map +1 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +173 -83
- package/dist/src/index.js.map +1 -0
- package/dist/src/peek-transform.js +85 -98
- package/dist/src/peek-transform.js.map +1 -0
- package/dist/src/process-duplex.d.ts +13 -2
- package/dist/src/process-duplex.js +128 -108
- package/dist/src/process-duplex.js.map +1 -0
- package/dist/src/process-queue.js +41 -54
- package/dist/src/process-queue.js.map +1 -0
- package/dist/src/types.d.ts +6 -0
- package/dist/src/types.js +1 -1
- package/dist/src/types.js.map +1 -0
- package/package.json +16 -16
- package/src/index.ts +193 -48
- package/src/process-duplex.ts +112 -63
- package/src/types.ts +7 -0
- package/tsconfig.json +32 -99
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
27
|
+
this.#createResource();
|
|
41
28
|
}
|
|
42
29
|
}
|
|
43
30
|
get hits() {
|
|
44
|
-
return
|
|
31
|
+
return this.#hitCount;
|
|
45
32
|
}
|
|
46
33
|
get misses() {
|
|
47
|
-
return
|
|
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 =
|
|
49
|
+
const attempt = this.#queue.pop();
|
|
52
50
|
if (attempt) {
|
|
53
51
|
debug('acquire hit');
|
|
54
|
-
if (!
|
|
52
|
+
if (!this.#destroyed) {
|
|
55
53
|
setImmediate(() => {
|
|
56
54
|
// Double-check destroyed flag in case it changed
|
|
57
|
-
if (!
|
|
58
|
-
|
|
55
|
+
if (!this.#destroyed) {
|
|
56
|
+
this.#createResource();
|
|
59
57
|
}
|
|
60
58
|
});
|
|
61
59
|
}
|
|
62
|
-
|
|
60
|
+
this.#hitCount += 1;
|
|
63
61
|
return attempt;
|
|
64
62
|
}
|
|
65
63
|
debug('acquire miss');
|
|
66
|
-
|
|
67
|
-
return
|
|
64
|
+
this.#missCount += 1;
|
|
65
|
+
return this.#factory();
|
|
68
66
|
}
|
|
69
67
|
async destroy() {
|
|
70
|
-
debug('destroy',
|
|
71
|
-
|
|
68
|
+
debug('destroy', this.#queue.length);
|
|
69
|
+
this.#destroyed = true;
|
|
72
70
|
const destroyPromises = [];
|
|
73
|
-
while (
|
|
74
|
-
const p =
|
|
71
|
+
while (this.#queue.length > 0) {
|
|
72
|
+
const p = this.#queue.pop();
|
|
75
73
|
if (p) {
|
|
76
|
-
destroyPromises.push(Promise.resolve(
|
|
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=
|
|
81
|
+
//# sourceMappingURL=process-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-queue.js","sourceRoot":"","sources":["../../src/process-queue.ts"],"names":[],"mappings":"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"}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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=
|
|
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.
|
|
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": ">=
|
|
8
|
+
"node": ">=22.0.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"prepublish": "tsc",
|
|
13
|
-
"lint": "eslint .
|
|
13
|
+
"lint": "eslint .",
|
|
14
14
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
15
15
|
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
16
|
-
"test": "
|
|
17
|
-
"test-debug": "DEBUG=SimpleZSTD,SimpleZSTDQueue timeout
|
|
18
|
-
"test-ci": "
|
|
19
|
-
"coverage": "
|
|
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": "^
|
|
60
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
61
|
-
"@typescript-eslint/parser": "^8.
|
|
62
|
-
"eslint": "^
|
|
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": "^
|
|
65
|
-
"prettier": "^3.
|
|
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
|
|
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 {
|
|
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 {
|
|
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<
|
|
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 {
|
|
55
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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(
|
|
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
|
-
|
|
196
|
-
|
|
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(
|
|
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
|
-
|
|
285
|
-
|
|
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 = () =>
|
|
293
|
-
#decompressDictCleanup: () => void = () =>
|
|
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 = () =>
|
|
301
|
-
this.#decompressDictCleanup = () =>
|
|
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 {
|
|
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(
|
|
315
|
-
compressDictPath =
|
|
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 {
|
|
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(
|
|
327
|
-
decompressDictPath =
|
|
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 = () =>
|
|
387
|
-
this.#decompressDictCleanup = () =>
|
|
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 = () =>
|
|
420
|
-
this.#decompressDictCleanup = () =>
|
|
562
|
+
await this.#compressDictCleanup();
|
|
563
|
+
await this.#decompressDictCleanup();
|
|
564
|
+
this.#compressDictCleanup = async () => undefined;
|
|
565
|
+
this.#decompressDictCleanup = async () => undefined;
|
|
421
566
|
}
|
|
422
567
|
|
|
423
568
|
/**
|