@cloudpss/compress 0.4.15 → 0.4.17

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/benchmark.js CHANGED
@@ -7,13 +7,15 @@ import cppzst from '@xingrz/cppzst';
7
7
  import * as myZstdWasm from '@cloudpss/zstd/wasm';
8
8
  import * as myZstdNapi from '@cloudpss/zstd';
9
9
  import pako from 'pako';
10
+ import * as fflate from 'fflate';
11
+ import * as fzstd from 'fzstd';
10
12
  import files from '../../benchmark-files/index.js';
11
13
  import { createRequire } from 'node:module';
12
14
 
13
15
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
14
16
  const brotli = createRequire(import.meta.url)('brotli-wasm');
15
17
 
16
- const t = (/** @type {number} */ time) => time.toFixed(2) + 'ms';
18
+ const t = (/** @type {number} */ time) => (time.toFixed(2) + 'ms').padStart(8);
17
19
  const pb = (/** @type {number} */ size) => prettyBytes(size, { binary: true });
18
20
 
19
21
  await bokuwebZstd.init();
@@ -42,14 +44,17 @@ const tests = [
42
44
  createTest('gzip -d (wasm)', wasmFlate.gzip_encode_raw, wasmFlate.gzip_decode_raw),
43
45
  createTest('gzip -d (node)', zlib.gzipSync, zlib.gunzipSync),
44
46
  createTest('gzip -d (pako)', pako.gzip, pako.ungzip),
47
+ createTest('gzip -d (fflate)', fflate.gzipSync, fflate.gunzipSync),
45
48
  createTest('zstd -3 (Oz,wasm)', (buf) => bokuwebZstd.compress(buf, 3), bokuwebZstd.decompress),
46
49
  createTest('zstd -3 (O3,wasm)', (buf) => myZstdWasm.compress(buf, 3), myZstdWasm.decompress),
47
50
  createTest('zstd -4 (O3,wasm)', (buf) => myZstdWasm.compress(buf, 4), myZstdWasm.decompress),
48
51
  createTest('zstd -4 (cppzst)', (buf) => cppzst.compressSync(buf, { level: 4 }), cppzst.decompressSync),
49
52
  createTest('zstd -4 (napi)', (buf) => myZstdNapi.compress(buf, 4), myZstdNapi.decompress),
53
+ createTest('zstd -4 (fzstd)', (buf) => myZstdNapi.compress(buf, 4), fzstd.decompress),
50
54
  createTest('zstd -10 (O3,wasm)', (buf) => myZstdWasm.compress(buf, 10), myZstdWasm.decompress),
51
55
  createTest('zstd -10 (cppzst)', (buf) => cppzst.compressSync(buf, { level: 10 }), cppzst.decompressSync),
52
56
  createTest('zstd -10 (napi)', (buf) => myZstdNapi.compress(buf, 10), myZstdNapi.decompress),
57
+ createTest('zstd -10 (fzstd)', (buf) => myZstdNapi.compress(buf, 10), fzstd.decompress),
53
58
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument
54
59
  createTest('br -1 (wasm)', (buf) => brotli.compress(buf, { quality: 1 }), brotli.decompress),
55
60
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument
@@ -86,7 +91,7 @@ for await (const { file, data } of files()) {
86
91
  const compressed = result.compressed.length;
87
92
  const ratio = (data.length / result.compressed.length).toFixed(2);
88
93
  console.log(
89
- ` ${test.name}: \t${ratio}(${pb(compressed)}) \t${t(result.compressTime)} \t${t(result.decompressTime)}`,
94
+ ` ${test.name.padEnd(20)}: ${ratio.padStart(6)} (${pb(compressed).padStart(8)}) \t${t(result.compressTime)} \t${t(result.decompressTime)}`,
90
95
  );
91
96
  }
92
97
  }
@@ -1,10 +1,10 @@
1
- import { gzip, ungzip } from 'pako';
1
+ import { gzipSync, gunzipSync } from 'fflate';
2
2
  /** 压缩 */
3
3
  export function compress(data) {
4
- return gzip(data);
4
+ return gzipSync(data);
5
5
  }
6
6
  /** 解压 */
7
7
  export function decompress(data) {
8
- return ungzip(data);
8
+ return gunzipSync(data);
9
9
  }
10
10
  //# sourceMappingURL=index-browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-browser.js","sourceRoot":"","sources":["../../src/gzip/index-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,SAAS;AACT,MAAM,UAAU,QAAQ,CAAC,IAAgB;IACrC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,SAAS;AACT,MAAM,UAAU,UAAU,CAAC,IAAgB;IACvC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"index-browser.js","sourceRoot":"","sources":["../../src/gzip/index-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9C,SAAS;AACT,MAAM,UAAU,QAAQ,CAAC,IAAgB;IACrC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS;AACT,MAAM,UAAU,UAAU,CAAC,IAAgB;IACvC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { gzip, gunzip } from 'zlib';
1
+ import { gzip, gunzip } from 'node:zlib';
2
2
  /** 压缩 */
3
3
  export function compress(data) {
4
4
  return new Promise((resolve, reject) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gzip/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,SAAS;AACT,MAAM,UAAU,QAAQ,CAAC,IAAgB;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACvB,IAAI,GAAG,IAAI,CAAC,MAAM;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAChC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS;AACT,MAAM,UAAU,UAAU,CAAC,IAAgB;IACvC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACzB,IAAI,GAAG,IAAI,CAAC,MAAM;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAChC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gzip/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEzC,SAAS;AACT,MAAM,UAAU,QAAQ,CAAC,IAAgB;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACvB,IAAI,GAAG,IAAI,CAAC,MAAM;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAChC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS;AACT,MAAM,UAAU,UAAU,CAAC,IAAgB;IACvC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACzB,IAAI,GAAG,IAAI,CAAC,MAAM;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAChC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,17 +1,25 @@
1
1
  /** 可用的压缩算法 */
2
2
  export type CompressionAlgorithm = 'zstd' | 'gzip' | 'none';
3
3
  /** 压缩算法的魔数 */
4
- export declare const MAGIC_NUMBERS: {
5
- zstd: number[];
6
- gzip: number[];
7
- };
4
+ export declare const CompressionAlgorithms: Readonly<{
5
+ zstd: Readonly<{
6
+ algorithm: "zstd";
7
+ magicNumber: readonly number[];
8
+ minSize: 8;
9
+ }>;
10
+ gzip: Readonly<{
11
+ algorithm: "gzip";
12
+ magicNumber: readonly number[];
13
+ minSize: 18;
14
+ }>;
15
+ }>;
8
16
  /** 使用指定算法压缩 */
9
- export declare function compress(data: Uint8Array, algorithm?: CompressionAlgorithm): CompressionResult;
17
+ export declare function compress(data: BinaryData, algorithm?: CompressionAlgorithm): CompressionResult;
10
18
  /** 压缩/解压缩结果 */
11
19
  export type CompressionResult = Promise<Uint8Array> & {
12
20
  algorithm: CompressionAlgorithm;
13
21
  };
14
22
  /** 探测压缩算法 */
15
- export declare function detectCompression(data: Uint8Array): CompressionAlgorithm;
23
+ export declare function detectCompression(data: BinaryData): CompressionAlgorithm;
16
24
  /** 探测压缩算法并解压缩 */
17
- export declare function decompress(data: Uint8Array): CompressionResult;
25
+ export declare function decompress(data: BinaryData): CompressionResult;
package/dist/index.js CHANGED
@@ -1,8 +1,16 @@
1
1
  /** 压缩算法的魔数 */
2
- export const MAGIC_NUMBERS = {
3
- zstd: [0x28, 0xb5, 0x2f, 0xfd],
4
- gzip: [0x1f, 0x8b],
5
- };
2
+ export const CompressionAlgorithms = Object.freeze({
3
+ zstd: Object.freeze({
4
+ algorithm: 'zstd',
5
+ magicNumber: Object.freeze([0x28, 0xb5, 0x2f, 0xfd]),
6
+ minSize: 8,
7
+ }),
8
+ gzip: Object.freeze({
9
+ algorithm: 'gzip',
10
+ magicNumber: Object.freeze([0x1f, 0x8b]),
11
+ minSize: 18,
12
+ }),
13
+ });
6
14
  const modules = {
7
15
  zstd: () => import('@cloudpss/zstd'),
8
16
  gzip: () => import('./gzip/index.js'),
@@ -13,46 +21,62 @@ function loadLib(algorithm) {
13
21
  throw new Error(`Unknown compression algorithm: ${algorithm}`);
14
22
  return modules[algorithm]();
15
23
  }
24
+ /** 支持的数据转为 Uint8Array */
25
+ function toUint8Array(data) {
26
+ if (data == null || typeof data != 'object' || typeof data.byteLength != 'number') {
27
+ throw new TypeError('Invalid data');
28
+ }
29
+ if (ArrayBuffer.isView(data)) {
30
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
31
+ }
32
+ return new Uint8Array(data, 0, data.byteLength);
33
+ }
16
34
  /** 使用指定算法压缩 */
17
35
  export function compress(data, algorithm = 'zstd') {
36
+ const d = toUint8Array(data);
18
37
  if (algorithm === 'none') {
19
- const ret = Promise.resolve(data);
38
+ const ret = Promise.resolve(d);
20
39
  ret.algorithm = 'none';
21
40
  return ret;
22
41
  }
23
- const ret = loadLib(algorithm).then((lib) => lib.compress(data));
42
+ const ret = loadLib(algorithm).then((lib) => lib.compress(d));
24
43
  ret.algorithm = algorithm;
25
44
  return ret;
26
45
  }
27
46
  /** 探测压缩算法 */
28
- export function detectCompression(data) {
29
- for (const key in MAGIC_NUMBERS) {
30
- const k = key;
31
- const magicNumber = MAGIC_NUMBERS[k];
32
- if (data.length <= magicNumber.length)
47
+ function detectCompressionImpl(data) {
48
+ for (const key in CompressionAlgorithms) {
49
+ const info = CompressionAlgorithms[key];
50
+ if (data.length < info.minSize)
33
51
  continue;
34
52
  let match = true;
35
- for (let i = 0; i < magicNumber.length; i++) {
36
- if (data[i] !== magicNumber[i]) {
53
+ for (let i = 0; i < info.magicNumber.length; i++) {
54
+ if (data[i] !== info.magicNumber[i]) {
37
55
  match = false;
38
56
  break;
39
57
  }
40
58
  }
41
59
  if (match)
42
- return k;
60
+ return info.algorithm;
43
61
  }
44
62
  return 'none';
45
63
  }
64
+ /** 探测压缩算法 */
65
+ export function detectCompression(data) {
66
+ const d = toUint8Array(data);
67
+ return detectCompressionImpl(d);
68
+ }
46
69
  /** 探测压缩算法并解压缩 */
47
70
  export function decompress(data) {
48
- const algorithm = detectCompression(data);
71
+ const d = toUint8Array(data);
72
+ const algorithm = detectCompressionImpl(d);
49
73
  if (algorithm === 'none') {
50
74
  // 不是压缩数据
51
- const result = Promise.resolve(data);
75
+ const result = Promise.resolve(d);
52
76
  result.algorithm = 'none';
53
77
  return result;
54
78
  }
55
- const result = loadLib(algorithm).then(({ decompress }) => decompress(data));
79
+ const result = loadLib(algorithm).then(({ decompress }) => decompress(d));
56
80
  result.algorithm = algorithm;
57
81
  return result;
58
82
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc;AACd,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,OAAO,GAAG;IACZ,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC;IACpC,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC;CACxC,CAAC;AAEF,YAAY;AACZ,SAAS,OAAO,CAAC,SAAgD;IAC7D,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,SAAS,EAAE,CAAC,CAAC;IAC5F,OAAO,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;AAChC,CAAC;AAED,eAAe;AACf,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,YAAkC,MAAM;IAC/E,IAAI,SAAS,KAAK,MAAM,EAAE;QACtB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAsB,CAAC;QACvD,GAAG,CAAC,SAAS,GAAG,MAAM,CAAC;QACvB,OAAO,GAAG,CAAC;KACd;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAsB,CAAC;IACtF,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;IAC1B,OAAO,GAAG,CAAC;AACf,CAAC;AAKD,aAAa;AACb,MAAM,UAAU,iBAAiB,CAAC,IAAgB;IAC9C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE;QAC7B,MAAM,CAAC,GAAG,GAAiC,CAAC;QAC5C,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,MAAM;YAAE,SAAS;QAChD,IAAI,KAAK,GAAG,IAAI,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACzC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE;gBAC5B,KAAK,GAAG,KAAK,CAAC;gBACd,MAAM;aACT;SACJ;QACD,IAAI,KAAK;YAAE,OAAO,CAAC,CAAC;KACvB;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,UAAU,CAAC,IAAgB;IACvC,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAE1C,IAAI,SAAS,KAAK,MAAM,EAAE;QACtB,SAAS;QACT,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAsB,CAAC;QAC1D,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC;QAC1B,OAAO,MAAM,CAAC;KACjB;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAsB,CAAC;IAClG,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,OAAO,MAAM,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,3 @@
1
+ import { config } from '../../jest.config.js';
2
+
3
+ export default { ...config };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudpss/compress",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
4
4
  "author": "CloudPSS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,22 +18,25 @@
18
18
  "scripts": {
19
19
  "start": "yarn clean && tsc --watch",
20
20
  "build": "yarn clean && tsc",
21
+ "test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest",
21
22
  "prepublishOnly": "yarn build",
22
23
  "clean": "rimraf dist",
23
24
  "benchmark": "node ./benchmark"
24
25
  },
25
26
  "dependencies": {
26
27
  "@cloudpss/zstd": "^0.2.0",
27
- "pako": "^2.1.0"
28
+ "fflate": "^0.7.4"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@bokuweb/zstd-wasm": "^0.0.20",
31
- "@types/node": "^18.15.12",
32
+ "@types/node": "^18.16.1",
32
33
  "@types/pako": "^2.0.0",
33
34
  "@xingrz/cppzst": "^2.1.0-alpha.8",
34
35
  "brotli-wasm": "^1.3.1",
36
+ "fzstd": "^0.1.0",
37
+ "pako": "^2.1.0",
35
38
  "pretty-bytes": "^6.1.0",
36
- "type-fest": "^3.8.0",
39
+ "type-fest": "^3.9.0",
37
40
  "wasm-flate": "1.0.2"
38
41
  }
39
42
  }
@@ -1,11 +1,11 @@
1
- import { gzip, ungzip } from 'pako';
1
+ import { gzipSync, gunzipSync } from 'fflate';
2
2
 
3
3
  /** 压缩 */
4
4
  export function compress(data: Uint8Array): Uint8Array {
5
- return gzip(data);
5
+ return gzipSync(data);
6
6
  }
7
7
 
8
8
  /** 解压 */
9
9
  export function decompress(data: Uint8Array): Uint8Array {
10
- return ungzip(data);
10
+ return gunzipSync(data);
11
11
  }
package/src/gzip/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { gzip, gunzip } from 'zlib';
1
+ import { gzip, gunzip } from 'node:zlib';
2
2
 
3
3
  /** 压缩 */
4
4
  export function compress(data: Uint8Array): Promise<Uint8Array> {
package/src/index.ts CHANGED
@@ -1,11 +1,29 @@
1
1
  /** 可用的压缩算法 */
2
2
  export type CompressionAlgorithm = 'zstd' | 'gzip' | 'none';
3
3
 
4
+ /** 压缩算法的信息 */
5
+ interface CompressionInfo {
6
+ /** 算法 */
7
+ readonly algorithm: CompressionAlgorithm;
8
+ /** 压缩算法的魔数 */
9
+ readonly magicNumber: readonly number[];
10
+ /** 压缩算法的最小结果大小 */
11
+ readonly minSize: number;
12
+ }
13
+
4
14
  /** 压缩算法的魔数 */
5
- export const MAGIC_NUMBERS = {
6
- zstd: [0x28, 0xb5, 0x2f, 0xfd],
7
- gzip: [0x1f, 0x8b],
8
- };
15
+ export const CompressionAlgorithms = Object.freeze({
16
+ zstd: Object.freeze({
17
+ algorithm: 'zstd' as const,
18
+ magicNumber: Object.freeze([0x28, 0xb5, 0x2f, 0xfd]),
19
+ minSize: 8,
20
+ }),
21
+ gzip: Object.freeze({
22
+ algorithm: 'gzip' as const,
23
+ magicNumber: Object.freeze([0x1f, 0x8b]),
24
+ minSize: 18,
25
+ }),
26
+ } satisfies Record<string, CompressionInfo>);
9
27
 
10
28
  const modules = {
11
29
  zstd: () => import('@cloudpss/zstd'),
@@ -18,14 +36,26 @@ function loadLib(algorithm: Exclude<CompressionAlgorithm, 'none'>): ReturnType<(
18
36
  return modules[algorithm]();
19
37
  }
20
38
 
39
+ /** 支持的数据转为 Uint8Array */
40
+ function toUint8Array(data: BinaryData): Uint8Array {
41
+ if (data == null || typeof data != 'object' || typeof data.byteLength != 'number') {
42
+ throw new TypeError('Invalid data');
43
+ }
44
+ if (ArrayBuffer.isView(data)) {
45
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
46
+ }
47
+ return new Uint8Array(data, 0, data.byteLength);
48
+ }
49
+
21
50
  /** 使用指定算法压缩 */
22
- export function compress(data: Uint8Array, algorithm: CompressionAlgorithm = 'zstd'): CompressionResult {
51
+ export function compress(data: BinaryData, algorithm: CompressionAlgorithm = 'zstd'): CompressionResult {
52
+ const d = toUint8Array(data);
23
53
  if (algorithm === 'none') {
24
- const ret = Promise.resolve(data) as CompressionResult;
54
+ const ret = Promise.resolve(d) as CompressionResult;
25
55
  ret.algorithm = 'none';
26
56
  return ret;
27
57
  }
28
- const ret = loadLib(algorithm).then((lib) => lib.compress(data)) as CompressionResult;
58
+ const ret = loadLib(algorithm).then((lib) => lib.compress(d)) as CompressionResult;
29
59
  ret.algorithm = algorithm;
30
60
  return ret;
31
61
  }
@@ -34,35 +64,41 @@ export function compress(data: Uint8Array, algorithm: CompressionAlgorithm = 'zs
34
64
  export type CompressionResult = Promise<Uint8Array> & { algorithm: CompressionAlgorithm };
35
65
 
36
66
  /** 探测压缩算法 */
37
- export function detectCompression(data: Uint8Array): CompressionAlgorithm {
38
- for (const key in MAGIC_NUMBERS) {
39
- const k = key as keyof typeof MAGIC_NUMBERS;
40
- const magicNumber = MAGIC_NUMBERS[k];
41
- if (data.length <= magicNumber.length) continue;
67
+ function detectCompressionImpl(data: Uint8Array): CompressionAlgorithm {
68
+ for (const key in CompressionAlgorithms) {
69
+ const info = CompressionAlgorithms[key as keyof typeof CompressionAlgorithms];
70
+ if (data.length < info.minSize) continue;
42
71
  let match = true;
43
- for (let i = 0; i < magicNumber.length; i++) {
44
- if (data[i] !== magicNumber[i]) {
72
+ for (let i = 0; i < info.magicNumber.length; i++) {
73
+ if (data[i] !== info.magicNumber[i]) {
45
74
  match = false;
46
75
  break;
47
76
  }
48
77
  }
49
- if (match) return k;
78
+ if (match) return info.algorithm;
50
79
  }
51
80
  return 'none';
52
81
  }
53
82
 
83
+ /** 探测压缩算法 */
84
+ export function detectCompression(data: BinaryData): CompressionAlgorithm {
85
+ const d = toUint8Array(data);
86
+ return detectCompressionImpl(d);
87
+ }
88
+
54
89
  /** 探测压缩算法并解压缩 */
55
- export function decompress(data: Uint8Array): CompressionResult {
56
- const algorithm = detectCompression(data);
90
+ export function decompress(data: BinaryData): CompressionResult {
91
+ const d = toUint8Array(data);
92
+ const algorithm = detectCompressionImpl(d);
57
93
 
58
94
  if (algorithm === 'none') {
59
95
  // 不是压缩数据
60
- const result = Promise.resolve(data) as CompressionResult;
96
+ const result = Promise.resolve(d) as CompressionResult;
61
97
  result.algorithm = 'none';
62
98
  return result;
63
99
  }
64
100
 
65
- const result = loadLib(algorithm).then(({ decompress }) => decompress(data)) as CompressionResult;
101
+ const result = loadLib(algorithm).then(({ decompress }) => decompress(d)) as CompressionResult;
66
102
  result.algorithm = algorithm;
67
103
  return result;
68
104
  }
package/tests/e2e.js ADDED
@@ -0,0 +1,132 @@
1
+ import { compress, decompress, detectCompression } from '@cloudpss/compress';
2
+ import { randomFillSync } from 'node:crypto';
3
+
4
+ describe('compress/decompress', () => {
5
+ const data = Buffer.from('hello world');
6
+ it('default', async () => {
7
+ const compressed = await compress(data);
8
+ expect(detectCompression(compressed)).toBe('zstd');
9
+ const decompressing = decompress(compressed);
10
+ const decompressed = await decompressing;
11
+ expect(Buffer.compare(decompressed, data)).toBe(0);
12
+ expect(decompressing.algorithm).toBe('zstd');
13
+ });
14
+ it('zstd', async () => {
15
+ const compressed = await compress(data, 'zstd');
16
+ expect(detectCompression(compressed)).toBe('zstd');
17
+ const decompressing = decompress(compressed);
18
+ const decompressed = await decompressing;
19
+ expect(Buffer.compare(decompressed, data)).toBe(0);
20
+ expect(decompressing.algorithm).toBe('zstd');
21
+ });
22
+ it('gzip', async () => {
23
+ const compressed = await compress(data, 'gzip');
24
+ expect(detectCompression(compressed)).toBe('gzip');
25
+ const decompressing = decompress(compressed);
26
+ const decompressed = await decompressing;
27
+ expect(Buffer.compare(decompressed, data)).toBe(0);
28
+ expect(decompressing.algorithm).toBe('gzip');
29
+ });
30
+ it('none', async () => {
31
+ const compressed = await compress(data, 'none');
32
+ expect(detectCompression(compressed)).toBe('none');
33
+ const decompressing = decompress(compressed);
34
+ const decompressed = await decompressing;
35
+ expect(Buffer.compare(compressed, data)).toBe(0);
36
+ expect(Buffer.compare(decompressed, data)).toBe(0);
37
+ expect(decompressing.algorithm).toBe('none');
38
+ });
39
+ });
40
+
41
+ describe('compress/decompress empty', () => {
42
+ const data = Buffer.alloc(0);
43
+ it('default', async () => {
44
+ const compressed = await compress(data);
45
+ expect(detectCompression(compressed)).toBe('zstd');
46
+ const decompressing = decompress(compressed);
47
+ const decompressed = await decompressing;
48
+ expect(decompressed.length).toBe(0);
49
+ expect(decompressing.algorithm).toBe('zstd');
50
+ });
51
+ it('zstd', async () => {
52
+ const compressed = await compress(data, 'zstd');
53
+ expect(detectCompression(compressed)).toBe('zstd');
54
+ const decompressing = decompress(compressed);
55
+ const decompressed = await decompressing;
56
+ expect(decompressed.length).toBe(0);
57
+ expect(decompressing.algorithm).toBe('zstd');
58
+ });
59
+ it('gzip', async () => {
60
+ const compressed = await compress(data, 'gzip');
61
+ expect(detectCompression(compressed)).toBe('gzip');
62
+ const decompressing = decompress(compressed);
63
+ const decompressed = await decompressing;
64
+ expect(decompressed.length).toBe(0);
65
+ expect(decompressing.algorithm).toBe('gzip');
66
+ });
67
+ it('none', async () => {
68
+ const compressed = await compress(data, 'none');
69
+ expect(detectCompression(compressed)).toBe('none');
70
+ const decompressing = decompress(compressed);
71
+ const decompressed = await decompressing;
72
+ expect(compressed.length).toBe(0);
73
+ expect(decompressed.length).toBe(0);
74
+ expect(decompressing.algorithm).toBe('none');
75
+ });
76
+ });
77
+
78
+ describe(`other data types`, () => {
79
+ it('ArrayBuffer', async () => {
80
+ const data = new ArrayBuffer(10);
81
+ randomFillSync(Buffer.from(data), 0, 10);
82
+ const compressed = await compress(data);
83
+ const decompressing = decompress(compressed);
84
+ const decompressed = await decompressing;
85
+ expect(Buffer.compare(decompressed, Buffer.from(data))).toBe(0);
86
+ expect(decompressing.algorithm).toBe('zstd');
87
+ });
88
+
89
+ it(`DataView`, async () => {
90
+ const data = new ArrayBuffer(10);
91
+ randomFillSync(Buffer.from(data), 0, 10);
92
+ const compressed = await compress(new DataView(data));
93
+ const decompressing = decompress(compressed);
94
+ const decompressed = await decompressing;
95
+ expect(Buffer.compare(decompressed, Buffer.from(data))).toBe(0);
96
+ expect(decompressing.algorithm).toBe('zstd');
97
+ });
98
+ });
99
+
100
+ describe('rejects bad input', () => {
101
+ it('compress', () => {
102
+ // @ts-expect-error test
103
+ expect(() => compress(123)).toThrow();
104
+ // @ts-expect-error test
105
+ expect(() => compress([1, 2, 3])).toThrow();
106
+ // @ts-expect-error test
107
+ expect(() => compress(Buffer.alloc(10), 'xxx')).toThrow();
108
+ });
109
+ it('decompress', () => {
110
+ // @ts-expect-error test
111
+ expect(() => decompress(123)).toThrow();
112
+ });
113
+ });
114
+
115
+ describe('detectCompression', () => {
116
+ it('detects zstd', () => {
117
+ const compressed = Buffer.from('28b52ffd00000000', 'hex');
118
+ expect(detectCompression(compressed)).toBe('zstd');
119
+ });
120
+ it('detects gzip', () => {
121
+ const compressed = Buffer.from('1f8b08000000000000000000000000000000', 'hex');
122
+ expect(detectCompression(compressed)).toBe('gzip');
123
+ });
124
+ it('detects none', () => {
125
+ const compressed = Buffer.from('00000000000000000000', 'hex');
126
+ expect(detectCompression(compressed)).toBe('none');
127
+ const compressed2 = Buffer.from('28b52ffd000000', 'hex');
128
+ expect(detectCompression(compressed2)).toBe('none');
129
+ const compressed3 = Buffer.from('1f8b080000000000000000000000000000', 'hex');
130
+ expect(detectCompression(compressed3)).toBe('none');
131
+ });
132
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig",
3
+ "include": ["./"],
4
+ "compilerOptions": {
5
+ "checkJs": true,
6
+ "noEmit": true,
7
+ "rootDir": "."
8
+ }
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig",
3
+ "include": ["./src/"],
4
+ "compilerOptions": {
5
+ "outDir": "./dist"
6
+ }
7
+ }