@e-mc/compress 0.11.8 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -9
  2. package/index.js +258 -125
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @e-mc/compress
2
2
 
3
- * NodeJS 16 LTS
4
- * ES2021
3
+ * NodeJS 18
4
+ * ES2022
5
5
 
6
6
  ## General Usage
7
7
 
@@ -9,16 +9,16 @@
9
9
 
10
10
  ## Interface
11
11
 
12
- * [View Source](https://www.unpkg.com/@e-mc/types@0.11.8/lib/index.d.ts)
12
+ * [View Source](https://www.unpkg.com/@e-mc/types@0.12.1/lib/index.d.ts)
13
13
 
14
14
  ```typescript
15
15
  import type { IModule, ModuleConstructor } from "./index";
16
- import type { BufferResult, CompressFormat, CompressLevel, ReadableOptions, TryFileCompressor } from "./compress";
16
+ import type { BrotliCompressLevel, BufferResult, CompressFormat, CompressLevel, ReadableOptions, TryFileCompressor } from "./compress";
17
17
  import type { CompressModule, CompressSettings } from "./settings";
18
18
 
19
19
  import type { WriteStream } from "node:fs";
20
20
  import type { Readable } from "node:stream";
21
- import type { BrotliCompress, Gzip } from "node:zlib";
21
+ import type { BrotliCompress, BrotliOptions, Gzip, ZlibOptions } from "node:zlib";
22
22
 
23
23
  interface ICompress extends IModule {
24
24
  module: CompressModule;
@@ -30,15 +30,18 @@ interface ICompress extends IModule {
30
30
  getLevel(value: string, fallback?: number): number | undefined;
31
31
  getReadable(file: string | URL | Buffer, options?: ReadableOptions): Readable;
32
32
  createGzip(file: string | Buffer, options?: CompressLevel): Gzip;
33
- createBrotliCompress(file: string | Buffer, options?: CompressLevel): BrotliCompress;
33
+ createBrotliCompress(file: string | Buffer, options?: BrotliCompressLevel): BrotliCompress;
34
34
  createWriteStreamAsGzip(file: string | Buffer, output: string, options?: CompressLevel): WriteStream;
35
35
  createWriteStreamAsBrotli(file: string | Buffer, output: string, options?: CompressLevel): WriteStream;
36
+ intoGzipStream(output: string, options?: ZlibOptions): WriteStream;
37
+ intoBrotliStream(output: string, options?: BrotliOptions): WriteStream;
36
38
  writeGzip(file: string | Buffer, output: string, options?: CompressLevel): Promise<void>;
37
39
  writeBrotli(file: string | Buffer, output: string, options?: CompressLevel): Promise<void>;
38
40
  tryFile(file: string | Buffer, options: CompressFormat): Promise<BufferResult>;
39
41
  tryFile(file: string | Buffer, output: string, options?: CompressFormat): Promise<BufferResult>;
40
42
  tryImage(file: string, options: CompressFormat): Promise<BufferResult>;
41
43
  tryImage(file: string | Buffer, output: string, options?: CompressFormat): Promise<BufferResult>;
44
+ hasPermission(type: string, options?: unknown): boolean;
42
45
  set chunkSize(value: number | string | undefined): void;
43
46
  get chunkSize(): number | undefined;
44
47
  get settings(): CompressSettings;
@@ -46,6 +49,7 @@ interface ICompress extends IModule {
46
49
 
47
50
  interface CompressConstructor extends ModuleConstructor {
48
51
  singleton(): ICompress;
52
+ asBuffer(data: Buffer | Uint8Array): Buffer;
49
53
  readonly prototype: ICompress;
50
54
  new(module?: CompressModule): ICompress;
51
55
  }
@@ -116,9 +120,9 @@ instance.tryImage("/tmp/image.png", "/path/output/compressed.png", options)
116
120
 
117
121
  ## References
118
122
 
119
- - https://www.unpkg.com/@e-mc/types@0.11.8/lib/squared.d.ts
120
- - https://www.unpkg.com/@e-mc/types@0.11.8/lib/compress.d.ts
121
- - https://www.unpkg.com/@e-mc/types@0.11.8/lib/settings.d.ts
123
+ - https://www.unpkg.com/@e-mc/types@0.12.1/lib/squared.d.ts
124
+ - https://www.unpkg.com/@e-mc/types@0.12.1/lib/compress.d.ts
125
+ - https://www.unpkg.com/@e-mc/types@0.12.1/lib/settings.d.ts
122
126
 
123
127
  * https://www.npmjs.com/package/@types/node
124
128
 
package/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  "use strict";
2
- var _a;
3
2
  const path = require("node:path");
4
3
  const fs = require("node:fs");
5
4
  const stream = require("node:stream");
6
5
  const zlib = require("node:zlib");
7
6
  const wawoff2 = require("wawoff2");
8
7
  const types_1 = require("@e-mc/types");
9
- const module_1 = require("@e-mc/module");
8
+ const core_1 = require("@e-mc/core");
10
9
  const { toSfnt, toWoff } = require('woff2sfnt-sfnt2woff');
11
- const kChunkSize = Symbol('chunkSize');
10
+ const kCompress = Symbol.for('compress:constructor');
11
+ function createWorker(filename) {
12
+ return core_1.WorkerChannel.create(path.join(__dirname, 'worker', filename), 'EMC_COMPRESS');
13
+ }
14
+ const SUPPORTED_NODE21 = (0, types_1.supported)(21);
12
15
  const CACHE_IMAGE = {};
13
16
  const CACHE_FONT = Object.create(null);
14
17
  const CACHE_FONTFROM = {};
@@ -16,14 +19,15 @@ const CACHE_FONTTO = {};
16
19
  const CACHE_INIT = [false, false];
17
20
  let SINGLETON_INSTANCE;
18
21
  let TEMP_DIR = '';
19
- const GZIP_ZOPFLI = (function () {
20
- try {
21
- return require('node-zopfli');
22
- }
23
- catch {
24
- return null;
25
- }
26
- })();
22
+ const WORKER = new core_1.WorkerGroup(parseInt(process.env.EMC_COMPRESS_WORKER_GROUP_MAX || '0'));
23
+ WORKER
24
+ .add('otf', createWorker('sfnt2woff.js'))
25
+ .add('woff', createWorker('woff2sfnt.js'))
26
+ .add('woff2', createWorker('wawoff2-decompress.js'))
27
+ .add('ttf', createWorker('wawoff2-compress.js'))
28
+ .add('image', createWorker('plugin-image.js'))
29
+ .add('gz', createWorker('system-gzip.js'))
30
+ .add('br', createWorker('system-brotli.js'));
27
31
  function setCacheData(instance, index) {
28
32
  if (CACHE_INIT[index]) {
29
33
  return true;
@@ -34,7 +38,7 @@ function setCacheData(instance, index) {
34
38
  if (TEMP_DIR) {
35
39
  if (expires === 0) {
36
40
  if (CACHE_INIT.every(value => !value)) {
37
- module_1.removeDir(TEMP_DIR, true);
41
+ core_1.Module.removeDir(TEMP_DIR, true);
38
42
  }
39
43
  settings.cache_expires = 0;
40
44
  }
@@ -79,15 +83,19 @@ function setCacheData(instance, index) {
79
83
  }
80
84
  return CACHE_INIT[index] = true;
81
85
  }
82
- function getOutputSize(startLength, data) {
86
+ function getOutputSize(startLength, data, worker) {
83
87
  if (startLength === 0 || !data) {
84
- return '';
88
+ return worker ? ' (worker)' : '';
85
89
  }
86
90
  const offset = Buffer.byteLength(data) - startLength;
87
- return ` (${(offset > 0 ? '+' : '') + (0, types_1.formatSize)(offset)})`;
91
+ return ` (${(worker ? 'worker: ' : '') + (offset > 0 ? '+' : '') + (0, types_1.formatSize)(offset)})`;
88
92
  }
89
93
  const sanitizePath = (value) => value.replace(/[\\/]/g, '_');
90
- class Compress extends module_1 {
94
+ const getWorkerTimeout = (options, value) => core_1.WorkerGroup.checkTimeout(typeof options.worker === 'number' && options.worker > 0 ? options.worker : value, true);
95
+ const applyBrotliMode = (params, value) => params[zlib.constants.BROTLI_PARAM_MODE] = value.includes('text/') ? zlib.constants.BROTLI_MODE_TEXT : value.includes('font/') ? zlib.constants.BROTLI_MODE_FONT : zlib.constants.BROTLI_MODE_GENERIC;
96
+ class Compress extends core_1.Module {
97
+ module;
98
+ static [kCompress] = true;
91
99
  static singleton() {
92
100
  if (!SINGLETON_INSTANCE) {
93
101
  SINGLETON_INSTANCE = new Compress();
@@ -99,17 +107,20 @@ class Compress extends module_1 {
99
107
  }
100
108
  return SINGLETON_INSTANCE;
101
109
  }
110
+ static asBuffer(data) {
111
+ return data instanceof Uint8Array ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : data;
112
+ }
113
+ level = {
114
+ gz: 9,
115
+ br: 11,
116
+ zopfli: 15
117
+ };
118
+ compressors = {};
119
+ _moduleName = 'compress';
120
+ #chunkSize = undefined;
102
121
  constructor(module = {}) {
103
122
  super();
104
123
  this.module = module;
105
- this.level = {
106
- gz: 9,
107
- br: 11,
108
- zopfli: 15
109
- };
110
- this.compressors = {};
111
- this._moduleName = 'compress';
112
- this[_a] = undefined;
113
124
  }
114
125
  init(...args) {
115
126
  let { gzip_level, brotli_quality, zopfli_iterations, chunk_size } = (0, types_1.isPlainObject)(args[0]) && args[0].settings || this.settings;
@@ -153,14 +164,14 @@ class Compress extends module_1 {
153
164
  return stream.Readable.from(file);
154
165
  }
155
166
  try {
156
- if (!options?.throwsPermission || this.canRead(file, { ownPermissionOnly: true })) {
167
+ if (!options?.throwsPermission && !process.permission || this.canRead(file, { ownPermissionOnly: true })) {
157
168
  return fs.createReadStream(file);
158
169
  }
159
170
  options = undefined;
160
171
  throw (0, types_1.errorValue)("Operation not permitted", file.toString());
161
172
  }
162
173
  catch (err) {
163
- if (!options || options.throwsPermission && module_1.isErrorCode(err, 'EACCES') || options.throwsDoesNotExist && module_1.isErrorCode(err, 'ENOENT')) {
174
+ if (!options || options.throwsPermission && core_1.Module.isErrorCode(err, 'EACCES') || options.throwsDoesNotExist && core_1.Module.isErrorCode(err, 'ENOENT')) {
164
175
  throw err;
165
176
  }
166
177
  }
@@ -172,15 +183,18 @@ class Compress extends module_1 {
172
183
  ({ algorithm, level, chunkSize } = options);
173
184
  }
174
185
  if (algorithm === 'zopfli') {
175
- if (GZIP_ZOPFLI) {
176
- const zopfli = (0, types_1.isPlainObject)(this.module.zopfli) ? { ...this.module.zopfli } : {};
186
+ try {
187
+ const lib = require('node-zopfli');
188
+ const opts = (0, types_1.isPlainObject)(this.module.zopfli) ? { ...this.module.zopfli } : {};
177
189
  if (typeof level === 'number') {
178
- zopfli.numiterations = level;
190
+ opts.numiterations = level;
179
191
  }
180
192
  else {
181
- zopfli.numiterations ??= this.level.zopfli;
193
+ opts.numiterations ??= this.level.zopfli;
182
194
  }
183
- return this.getReadable(file, options).pipe(GZIP_ZOPFLI.createGzip(zopfli));
195
+ return this.getReadable(file, options).pipe(lib.createGzip(opts));
196
+ }
197
+ catch {
184
198
  }
185
199
  level = undefined;
186
200
  }
@@ -213,7 +227,7 @@ class Compress extends module_1 {
213
227
  else {
214
228
  brotli = { params };
215
229
  }
216
- params[zlib.constants.BROTLI_PARAM_MODE] = (mimeType ||= '').includes('text/') ? zlib.constants.BROTLI_MODE_TEXT : mimeType.includes('font/') ? zlib.constants.BROTLI_MODE_FONT : zlib.constants.BROTLI_MODE_GENERIC;
230
+ applyBrotliMode(params, mimeType || '');
217
231
  try {
218
232
  params[zlib.constants.BROTLI_PARAM_SIZE_HINT] = typeof file === 'string' ? fs.statSync(file).size : Buffer.byteLength(file);
219
233
  }
@@ -233,6 +247,12 @@ class Compress extends module_1 {
233
247
  createWriteStreamAsBrotli(file, output, options) {
234
248
  return this.createBrotliCompress(file, options).pipe(fs.createWriteStream(output));
235
249
  }
250
+ intoGzipStream(output, options) {
251
+ return zlib.createGzip(options).pipe(fs.createWriteStream(output));
252
+ }
253
+ intoBrotliStream(output, options) {
254
+ return zlib.createBrotliCompress(options).pipe(fs.createWriteStream(output));
255
+ }
236
256
  async writeGzip(file, output, options) {
237
257
  return new Promise((resolve, reject) => {
238
258
  this.createWriteStreamAsGzip(file, output, options)
@@ -255,26 +275,34 @@ class Compress extends module_1 {
255
275
  if (!(0, types_1.isString)(output)) {
256
276
  output = typeof file === 'string' ? file : '';
257
277
  }
278
+ if (output && !this.canWrite(output, { ownPermissionOnly: true })) {
279
+ return Promise.reject((0, types_1.errorValue)("Operation not permitted", output));
280
+ }
258
281
  options ||= {};
259
- const { filename, startTime = process.hrtime.bigint(), timeout = 0, sessionId, broadcastId } = options;
282
+ const { filename, startTime = process.hrtime.bigint(), timeout = 0 } = options;
260
283
  let format = options.format, hash = options.etag;
261
- if (!format) {
284
+ if (!(0, types_1.isString)(format)) {
262
285
  return Promise.reject((0, types_1.errorValue)("Missing option \"format\"", output || filename));
263
286
  }
264
- const cache = this.settings.cache ? !!(hash && (hash = module_1.asHash(hash)) || Buffer.isBuffer(file) && (hash = module_1.asHash(file))) : false;
287
+ const cache = this.settings.cache ? !!(hash && (hash = core_1.Module.asHash(hash)) || Buffer.isBuffer(file) && (hash = core_1.Module.asHash(file))) : false;
288
+ format = format.toLowerCase();
265
289
  return new Promise((resolve, reject) => {
266
- let timer = null, startLength = 0, aborted = false;
290
+ let timer = null, worker = null, startLength = 0, closed = false;
267
291
  const endProcess = (err, data = null, ext, result, fromCache) => {
292
+ if (closed) {
293
+ return;
294
+ }
268
295
  if (timer) {
269
296
  clearTimeout(timer);
270
297
  }
298
+ closed = true;
271
299
  if (err) {
272
300
  reject(err);
273
301
  return;
274
302
  }
275
303
  if (!fromCache) {
276
304
  const status = (0, types_1.isString)(result) ? path.basename(result) : filename || "Completed";
277
- this.writeTimeProcess(ext || format, status + getOutputSize(startLength, data), startTime, { type: 8, sessionId, broadcastId });
305
+ this.writeTimeProcess(ext || format, status + getOutputSize(startLength, data, !!worker), startTime, { type: 8, sessionId: options.sessionId, broadcastId: options.broadcastId });
278
306
  }
279
307
  if (result) {
280
308
  options.outFile = result;
@@ -287,14 +315,19 @@ class Compress extends module_1 {
287
315
  const startProcess = (compressing) => {
288
316
  if (timeout > 0) {
289
317
  timer = setTimeout(() => {
290
- aborted = true;
291
- endProcess((0, types_1.errorValue)("Timeout was exceeded", format));
292
- }, timeout);
318
+ if (worker) {
319
+ void worker.terminate();
320
+ }
321
+ endProcess((0, types_1.errorValue)(worker ? "Worker did not finish" : "Timeout was exceeded", format));
322
+ }, worker ? getWorkerTimeout(options, timeout) : timeout);
293
323
  }
294
- this.formatMessage(8, format, [compressing ? 'Compressing file...' : 'Decompressing file...', filename || (output ? path.basename(output) : '')], (0, types_1.isString)(file) ? file : '', { titleColor: 'magenta', sessionId, broadcastId });
324
+ this.formatMessage(8, format, [compressing ? 'Compressing file...' : 'Decompressing file...', filename || (output ? path.basename(output) : '')], (0, types_1.isString)(file) ? file : '', { titleColor: 'magenta', sessionId: options.sessionId, broadcastId: options.broadcastId });
295
325
  };
296
326
  try {
297
327
  const writeFont = (data, ext, from) => {
328
+ if (closed) {
329
+ return;
330
+ }
298
331
  if (output) {
299
332
  const pathname = (0, types_1.renameExt)(output, ext);
300
333
  fs.writeFile(pathname, data, err => {
@@ -312,7 +345,7 @@ class Compress extends module_1 {
312
345
  if (cache) {
313
346
  const pathname = path.join(from, ext);
314
347
  let tempFont;
315
- if (TEMP_DIR && module_1.createDir(tempFont = path.join(TEMP_DIR, pathname))) {
348
+ if (TEMP_DIR && core_1.Module.createDir(tempFont = path.join(TEMP_DIR, pathname))) {
316
349
  tempFont = path.join(tempFont, hash);
317
350
  }
318
351
  else {
@@ -332,7 +365,7 @@ class Compress extends module_1 {
332
365
  const cacheKey = from + `_${to}_` + hash;
333
366
  const tempFont = CACHE_FONT[cacheKey];
334
367
  if (tempFont) {
335
- if (module_1.isPath(tempFont[0])) {
368
+ if (core_1.Module.isPath(tempFont[0])) {
336
369
  try {
337
370
  const data = fs.readFileSync(tempFont[0]);
338
371
  let pathname;
@@ -343,7 +376,7 @@ class Compress extends module_1 {
343
376
  else {
344
377
  endProcess(null, data, to, '', true);
345
378
  }
346
- this.formatMessage(8, from, [pathname ? path.basename(pathname) : "Completed" + ` -> font/${to}`, 'cache'], tempFont[1].toLocaleString(), { ...module_1.LOG_STYLE_NOTICE, hintBold: true, sessionId, broadcastId });
379
+ this.formatMessage(8, from, [pathname ? path.basename(pathname) : "Completed" + ` -> font/${to}`, 'cache'], tempFont[1].toLocaleString(), { ...core_1.Module.LOG_STYLE_NOTICE, hintBold: true, sessionId: options.sessionId, broadcastId: options.broadcastId });
347
380
  return true;
348
381
  }
349
382
  catch {
@@ -354,14 +387,50 @@ class Compress extends module_1 {
354
387
  return false;
355
388
  }
356
389
  };
357
- const errorResponse = (font) => {
358
- endProcess((0, types_1.errorValue)("Unsupported MIME", (font?.mime || "Unknown") + ': ' + (output || filename || "Unknown")));
390
+ const errorResponse = (font, message = "Unsupported MIME") => {
391
+ endProcess((0, types_1.errorValue)(message, (font?.mime || "Unknown") + ': ' + (output || filename || "Unknown")));
359
392
  };
360
- const data = Buffer.isBuffer(file) ? file : fs.readFileSync(file);
393
+ const shared = Buffer.isBuffer(file);
394
+ let data;
395
+ if (shared) {
396
+ data = file;
397
+ }
398
+ else if (this.canRead(file, { ownPermissionOnly: true })) {
399
+ data = fs.readFileSync(file);
400
+ }
401
+ else {
402
+ reject((0, types_1.errorValue)("Operation not permitted", file));
403
+ return;
404
+ }
361
405
  startLength = Buffer.byteLength(data);
362
- switch (format = format.toLowerCase()) {
406
+ switch (format) {
363
407
  case 'gz':
364
408
  case 'br': {
409
+ if (this.hasPermission('worker', options)) {
410
+ const workerOptions = { chunkSize: options.chunkSize ?? this.chunkSize };
411
+ if (format === 'gz') {
412
+ workerOptions.level = options.level ?? this.level.gz;
413
+ }
414
+ else {
415
+ workerOptions.params = { [zlib.constants.BROTLI_PARAM_QUALITY]: options.level ?? this.level.br };
416
+ applyBrotliMode(workerOptions.params, options.mimeType || '');
417
+ }
418
+ const endWorker = (result) => {
419
+ if ((0, types_1.isString)(result)) {
420
+ endProcess(null, null, format, result);
421
+ }
422
+ else if (result) {
423
+ endProcess(null, result, format);
424
+ }
425
+ else {
426
+ endProcess((0, types_1.errorValue)("Worker did not finish", output));
427
+ }
428
+ };
429
+ if (worker = WORKER.get(format).sendObject({ data, options: workerOptions, output }, shared || SUPPORTED_NODE21 ? [] : [data.buffer], endWorker)) {
430
+ startProcess(true);
431
+ break;
432
+ }
433
+ }
365
434
  startProcess(true);
366
435
  let transform, chunks;
367
436
  if (output) {
@@ -376,12 +445,10 @@ class Compress extends module_1 {
376
445
  }
377
446
  transform
378
447
  .on('finish', () => {
379
- if (!aborted) {
380
- endProcess(null, chunks && Buffer.concat(chunks), format, output);
381
- }
448
+ endProcess(null, chunks && Buffer.concat(chunks), format, output);
382
449
  })
383
450
  .on('error', (err) => {
384
- aborted = true;
451
+ transform.destroy();
385
452
  endProcess(err);
386
453
  });
387
454
  break;
@@ -391,36 +458,52 @@ class Compress extends module_1 {
391
458
  if (hasFont()) {
392
459
  return;
393
460
  }
394
- startProcess(false);
395
461
  const checkResult = (result, from) => {
396
- void module_1.resolveMime(result).then(font => {
397
- switch (font?.mime) {
398
- case "font/ttf":
399
- case "font/otf":
400
- writeFont(result, font.ext, from);
401
- break;
402
- default:
403
- errorResponse(font);
404
- break;
405
- }
406
- });
462
+ if (result) {
463
+ void core_1.Module.resolveMime(result).then(font => {
464
+ switch (font?.mime) {
465
+ case "font/ttf":
466
+ case "font/otf":
467
+ writeFont(result, font.ext, from);
468
+ break;
469
+ default:
470
+ errorResponse(font);
471
+ break;
472
+ }
473
+ });
474
+ }
475
+ else {
476
+ errorResponse(null, "Worker did not finish");
477
+ }
407
478
  };
408
- void module_1.resolveMime(data)
479
+ void core_1.Module.resolveMime(data)
409
480
  .then(font => {
410
481
  switch (font?.mime) {
411
482
  case "font/woff":
412
483
  if (cache) {
413
484
  CACHE_FONTFROM[hash] = 'woff';
414
485
  }
415
- checkResult(toSfnt(data), 'woff');
486
+ if (this.hasPermission('worker', options) && (worker = WORKER.get('woff').sendBuffer(data, shared, checkResult, 'woff'))) {
487
+ startProcess(false);
488
+ }
489
+ else {
490
+ startProcess(false);
491
+ checkResult(toSfnt(data), 'woff');
492
+ }
416
493
  break;
417
494
  case "font/woff2":
418
495
  if (cache) {
419
496
  CACHE_FONTFROM[hash] = 'woff2';
420
497
  }
421
- void wawoff2.decompress(data).then(woff => {
422
- checkResult(woff, 'woff2');
423
- });
498
+ if (this.hasPermission('worker', options) && (worker = WORKER.get('woff2').sendBuffer(data, shared, checkResult, 'woff2'))) {
499
+ startProcess(false);
500
+ }
501
+ else {
502
+ startProcess(false);
503
+ void wawoff2.decompress(data).then(woff => {
504
+ checkResult(woff, 'woff2');
505
+ });
506
+ }
424
507
  break;
425
508
  default:
426
509
  errorResponse(font);
@@ -435,33 +518,50 @@ class Compress extends module_1 {
435
518
  if (hasFont()) {
436
519
  return;
437
520
  }
438
- const checkResult = (result, mimeType, from) => {
439
- void module_1.resolveMime(result).then(font => {
440
- if (font?.mime === mimeType) {
441
- writeFont(result, font.ext, from);
442
- }
443
- else {
444
- errorResponse(font);
445
- }
446
- });
521
+ const checkResult = (result, from, mimeType) => {
522
+ if (result) {
523
+ void core_1.Module.resolveMime(result).then(font => {
524
+ if (font?.mime === mimeType) {
525
+ writeFont(result, font.ext, from);
526
+ }
527
+ else {
528
+ errorResponse(font);
529
+ }
530
+ });
531
+ }
532
+ else {
533
+ errorResponse(null, "Worker did not finish");
534
+ }
447
535
  };
448
536
  startProcess(true);
449
- void module_1.resolveMime(data)
537
+ void core_1.Module.resolveMime(data)
450
538
  .then(font => {
451
539
  switch (font?.mime) {
452
540
  case "font/ttf":
453
541
  if (cache) {
454
542
  CACHE_FONTFROM[hash] = 'ttf';
455
543
  }
456
- void wawoff2.compress(data).then(result => {
457
- checkResult(result, "font/woff2", 'ttf');
458
- });
544
+ if (this.hasPermission('worker', options) && (worker = WORKER.get('ttf').sendBuffer(data, shared, checkResult, 'ttf', "font/woff2"))) {
545
+ startProcess(false);
546
+ }
547
+ else {
548
+ startProcess(false);
549
+ void wawoff2.compress(data).then(result => {
550
+ checkResult(result, 'ttf', "font/woff2");
551
+ });
552
+ }
459
553
  break;
460
554
  case "font/otf":
461
555
  if (cache) {
462
556
  CACHE_FONTFROM[hash] = 'otf';
463
557
  }
464
- checkResult(toWoff(data), "font/woff", 'otf');
558
+ if (this.hasPermission('worker', options) && (worker = WORKER.get('otf').sendBuffer(data, shared, checkResult, 'otf', "font/woff"))) {
559
+ startProcess(false);
560
+ }
561
+ else {
562
+ startProcess(false);
563
+ checkResult(toWoff(data), 'otf', "font/woff");
564
+ }
465
565
  break;
466
566
  default:
467
567
  errorResponse(font);
@@ -472,13 +572,13 @@ class Compress extends module_1 {
472
572
  break;
473
573
  }
474
574
  default: {
475
- const compressor = this.compressors[format]?.bind(this);
575
+ const compressor = this.compressors[format];
476
576
  if (typeof compressor !== 'function') {
477
577
  throw (0, types_1.errorValue)("Missing compression plugin", format);
478
578
  }
479
579
  startProcess(true);
480
580
  if (compressor.toString().startsWith('async')) {
481
- compressor.call(this, file, output, options)
581
+ compressor.call(this, data, output, options)
482
582
  .then(result => {
483
583
  if (typeof result === 'string') {
484
584
  endProcess(null, null, format, result);
@@ -490,7 +590,7 @@ class Compress extends module_1 {
490
590
  .catch(endProcess);
491
591
  }
492
592
  else {
493
- void compressor.call(this, file, output, options, endProcess);
593
+ void compressor.call(this, data, output, options, endProcess);
494
594
  }
495
595
  break;
496
596
  }
@@ -509,7 +609,13 @@ class Compress extends module_1 {
509
609
  else {
510
610
  options ||= {};
511
611
  }
512
- const { filename, startTime = process.hrtime.bigint(), timeout = 0, sessionId, broadcastId } = options;
612
+ const { filename, startTime = process.hrtime.bigint(), timeout = 0 } = options;
613
+ if (!(0, types_1.isString)(output)) {
614
+ output = typeof file === 'string' ? file : '';
615
+ }
616
+ if (output && !this.canWrite(output, { ownPermissionOnly: true })) {
617
+ return Promise.reject((0, types_1.errorValue)("Operation not permitted", output));
618
+ }
513
619
  let plugin = options.plugin;
514
620
  if ((0, types_1.isString)(plugin)) {
515
621
  if (types_1.IMPORT_MAP[plugin]) {
@@ -517,18 +623,16 @@ class Compress extends module_1 {
517
623
  }
518
624
  }
519
625
  else {
520
- return Promise.reject();
521
- }
522
- if (!(0, types_1.isString)(output)) {
523
- output = typeof file === 'string' ? file : '';
626
+ return Promise.reject((0, types_1.errorValue)("Missing compression plugin", output));
524
627
  }
525
628
  const ext = output ? path.extname(output).substring(1) : options.format || "Unknown";
526
629
  const getFormat = () => output ? path.basename(output) : ext;
527
- let data;
630
+ let data, shared = false;
528
631
  if (Buffer.isBuffer(file)) {
529
632
  data = file;
633
+ shared = true;
530
634
  }
531
- else {
635
+ else if (this.canRead(file, { ownPermissionOnly: true })) {
532
636
  try {
533
637
  data = fs.readFileSync(file);
534
638
  }
@@ -536,23 +640,26 @@ class Compress extends module_1 {
536
640
  return Promise.reject(err);
537
641
  }
538
642
  }
643
+ else {
644
+ return Promise.reject((0, types_1.errorValue)("Operation not permitted", file));
645
+ }
539
646
  if (!options.mimeType) {
540
- const out = await module_1.resolveMime(data);
647
+ const out = await core_1.Module.resolveMime(data);
541
648
  if (out) {
542
649
  options.mimeType = out.mime;
543
650
  }
544
651
  }
545
652
  const startLength = Buffer.byteLength(data);
546
653
  return new Promise(async (resolve, reject) => {
547
- let timer = null, hash, cacheKey, aborted = false;
654
+ let timer = null, hash, cacheKey, closed = false;
548
655
  const failed = (err) => {
549
- aborted = true;
550
656
  if (timer) {
551
657
  clearTimeout(timer);
552
658
  }
659
+ closed = true;
553
660
  reject(err);
554
661
  };
555
- const complete = (err, result, ctime) => {
662
+ const complete = (err, result, worker, ctime) => {
556
663
  if (err) {
557
664
  reject(err);
558
665
  return;
@@ -560,14 +667,14 @@ class Compress extends module_1 {
560
667
  const value = output ? path.basename(output) : filename || (0, types_1.isString)(file) && path.basename(file);
561
668
  const status = value ? plugin + ': ' + value : "Completed";
562
669
  if (ctime) {
563
- this.formatMessage(8, value ? path.extname(value).substring(1) : '', [status, 'cache'], ctime.toLocaleString(), { ...module_1.LOG_STYLE_NOTICE, hintBold: true, sessionId, broadcastId });
670
+ this.formatMessage(8, value ? path.extname(value).substring(1) : '', [status, 'cache'], ctime.toLocaleString(), { ...core_1.Module.LOG_STYLE_NOTICE, hintBold: true, sessionId: options.sessionId, broadcastId: options.broadcastId });
564
671
  }
565
672
  else {
566
- this.writeTimeProcess(ext, status + getOutputSize(startLength, result), startTime, { type: 8, sessionId, broadcastId });
673
+ this.writeTimeProcess(ext, status + getOutputSize(startLength, result, worker), startTime, { type: 8, sessionId: options.sessionId, broadcastId: options.broadcastId });
567
674
  }
568
675
  if (!ctime && hash && cacheKey && TEMP_DIR) {
569
676
  const pathname = path.join(TEMP_DIR, sanitizePath(plugin), hash);
570
- if (module_1.createDir(pathname)) {
677
+ if (core_1.Module.createDir(pathname)) {
571
678
  fs.writeFile(path.join(pathname, cacheKey), result, error => {
572
679
  if (!error) {
573
680
  ((CACHE_IMAGE[plugin] ||= {})[hash] ||= {})[cacheKey] = new Date();
@@ -577,8 +684,8 @@ class Compress extends module_1 {
577
684
  }
578
685
  resolve(result);
579
686
  };
580
- const success = (result, ctime) => {
581
- if (aborted) {
687
+ const success = (result, worker = false, ctime) => {
688
+ if (closed) {
582
689
  return;
583
690
  }
584
691
  if (timer) {
@@ -586,22 +693,22 @@ class Compress extends module_1 {
586
693
  }
587
694
  if (output) {
588
695
  fs.writeFile(output, result, err => {
589
- complete(err, result, ctime);
696
+ complete(err, result, worker, ctime);
590
697
  });
591
698
  }
592
699
  else {
593
- complete(null, result, ctime);
700
+ complete(null, result, worker, ctime);
594
701
  }
595
702
  };
596
703
  if (this.settings.cache && setCacheData(this, 1)) {
597
704
  let stored;
598
705
  try {
599
- hash = module_1.asHash(data);
706
+ hash = core_1.Module.asHash(data);
600
707
  stored = (CACHE_IMAGE[plugin] ||= {})[hash];
601
- cacheKey = plugin + ((0, types_1.isPlainObject)(options.options) ? ':' + module_1.asHash(module_1.asString(options.options), 'md5') : '');
708
+ cacheKey = plugin + (options.options ? ':' + core_1.Module.asHash(core_1.Module.asString(options.options)) : '') + (options.metadata ? ':' + core_1.Module.asHash(core_1.Module.asString(options.metadata)) : '');
602
709
  const ctime = stored?.[cacheKey];
603
710
  if (ctime) {
604
- success(fs.readFileSync(path.join(TEMP_DIR, sanitizePath(plugin), hash, cacheKey)), ctime);
711
+ success(fs.readFileSync(path.join(TEMP_DIR, sanitizePath(plugin), hash, cacheKey)), false, ctime);
605
712
  return;
606
713
  }
607
714
  }
@@ -612,8 +719,40 @@ class Compress extends module_1 {
612
719
  }
613
720
  }
614
721
  }
615
- this.formatMessage(8, ext, ['Compressing image...', plugin], filename || output, { titleColor: 'magenta', sessionId, broadcastId });
722
+ this.formatMessage(8, ext, ['Compressing image...', plugin], filename || output, { titleColor: 'magenta', sessionId: options.sessionId, broadcastId: options.broadcastId });
616
723
  try {
724
+ let worker = null;
725
+ const outData = (result) => {
726
+ if (result && result !== data) {
727
+ success(result, !!worker);
728
+ }
729
+ else {
730
+ failed((0, types_1.errorMessage)(plugin, "Unsupported MIME", getFormat()));
731
+ }
732
+ };
733
+ const startTimeout = (value, message) => {
734
+ if (value > 0) {
735
+ timer = setTimeout(() => {
736
+ if (worker) {
737
+ void worker.terminate();
738
+ }
739
+ failed((0, types_1.errorMessage)(plugin, message, options.mimeType));
740
+ }, value);
741
+ }
742
+ };
743
+ if (options.mimeType) {
744
+ (options.metadata ||= {}).mimeType ||= options.mimeType;
745
+ }
746
+ if (this.hasPermission('worker', options)) {
747
+ try {
748
+ if (worker = WORKER.get('image').sendObject({ plugin, data, options: options.options, metadata: options.metadata }, shared || SUPPORTED_NODE21 ? [] : [data.buffer], outData)) {
749
+ startTimeout(getWorkerTimeout(options, timeout), "Worker timeout was exceeded");
750
+ return;
751
+ }
752
+ }
753
+ catch {
754
+ }
755
+ }
617
756
  let transform;
618
757
  try {
619
758
  transform = await (0, types_1.importESM)(plugin, true);
@@ -621,35 +760,29 @@ class Compress extends module_1 {
621
760
  catch {
622
761
  transform = require(plugin);
623
762
  }
624
- const out = transform.call(this, options.options, options.mimeType);
625
- if (timeout > 0) {
626
- timer = setTimeout(() => {
627
- failed((0, types_1.errorMessage)(plugin, "Timeout was exceeded"));
628
- }, timeout);
629
- }
630
- void out(data).then(result => {
631
- if (result !== data) {
632
- success(result);
633
- }
634
- else {
635
- failed((0, types_1.errorMessage)(plugin, "Unsupported MIME", getFormat()));
636
- }
637
- });
763
+ startTimeout(timeout, "Timeout was exceeded");
764
+ const out = transform.call(this, options.options, options.metadata);
765
+ void out(data).then(outData);
638
766
  }
639
767
  catch (err) {
640
768
  failed(err);
641
769
  }
642
770
  });
643
771
  }
772
+ hasPermission(type, options) {
773
+ if (super.hasPermission(type)) {
774
+ return type === 'worker' ? core_1.WorkerChannel.hasPermission(options) : true;
775
+ }
776
+ return false;
777
+ }
644
778
  set chunkSize(value) {
645
- this[kChunkSize] = !isNaN(value = (0, types_1.alignSize)(value, 1)) ? value : undefined;
779
+ this.#chunkSize = !isNaN(value = (0, types_1.alignSize)(value, 1)) ? value : undefined;
646
780
  }
647
781
  get chunkSize() {
648
- return this[kChunkSize];
782
+ return this.#chunkSize;
649
783
  }
650
784
  get settings() {
651
785
  return this.module.settings ||= {};
652
786
  }
653
787
  }
654
- _a = kChunkSize;
655
788
  module.exports = Compress;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/compress",
3
- "version": "0.11.8",
3
+ "version": "0.12.1",
4
4
  "description": "Compress constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -23,8 +23,8 @@
23
23
  "license": "BSD-3-Clause",
24
24
  "homepage": "https://github.com/anpham6/e-mc#readme",
25
25
  "dependencies": {
26
- "@e-mc/module": "0.11.8",
27
- "@e-mc/types": "0.11.8",
26
+ "@e-mc/core": "0.12.1",
27
+ "@e-mc/types": "0.12.1",
28
28
  "wawoff2": "^2.0.1",
29
29
  "woff2sfnt-sfnt2woff": "^1.0.0"
30
30
  }