소스 검색

Add tests

Arjun Barrett 4 년 전
부모
커밋
fd72227021
13개의 변경된 파일396개의 추가작업 그리고 10개의 파일을 삭제
  1. 1 0
      .npmignore
  2. 3 2
      README.md
  3. 5 1
      package.json
  4. 6 4
      src/index.ts
  5. 22 0
      test/0-valid.ts
  6. 24 0
      test/1-size.ts
  7. 46 0
      test/2-perf.ts
  8. 2 0
      test/data/.gitignore
  9. 2 0
      test/results/.gitignore
  10. 8 0
      test/tsconfig.json
  11. 186 0
      test/util.ts
  12. 3 2
      tsconfig.json
  13. 88 1
      yarn.lock

+ 1 - 0
.npmignore

@@ -1,5 +1,6 @@
 docs/
 src/
+test/
 .gitignore
 .npmignore
 tsconfig.esm.json

+ 3 - 2
README.md

@@ -55,8 +55,8 @@ const massiveAgain = fflate.unzlib(notSoMassive);
 const compressed = new Uint8Array(
   await fetch('/GZIPorZLIBorDEFLATE').then(res => res.arrayBuffer())
 );
-// Again, Node.js Buffers work too. For example, the above could instead be:
-// Buffer.from('H4sIAAAAAAAA//NIzcnJVyjPL8pJUQQAlRmFGwwAAAA=', 'base64');
+// Above example with Node.js Buffers:
+// Buffer.from('H4sIAAAAAAAAE8tIzcnJBwCGphA2BQAAAA==', 'base64');
 
 const decompressed = fflate.decompress(compressed);
 ```
@@ -114,5 +114,6 @@ Before you decide that `fflate` is the end-all compression library, you should n
 `fflate` makes heavy use of typed arrays (`Uint8Array`, `Uint16Array`, etc.). Typed arrays can be polyfilled at the cost of performance, but the most recent browser that doesn't support them [is from 2011](https://caniuse.com/typedarrays), so I wouldn't bother.
 
 Other than that, `fflate` is completely ES3, meaning you probably won't even need a bundler to use it.
+
 ## License
 MIT

+ 5 - 1
package.json

@@ -26,14 +26,18 @@
     "build": "yarn build:lib && yarn build:docs",
     "build:lib": "tsc && tsc --project tsconfig.esm.json",
     "build:docs": "typedoc --mode library --plugin typedoc-plugin-markdown --hideProjectName --hideBreadcrumbs --readme none --disableSources",
-    "prepare": "yarn build"
+    "test": "TS_NODE_PROJECT=test/tsconfig.json uvu test -- -b -r ts-node/register",
+    "prepack": "yarn build"
   },
   "devDependencies": {
+    "@types/node": "^14.11.2",
     "pako": "^1.0.11",
     "tiny-inflate": "^1.0.3",
+    "ts-node": "^9.0.0",
     "typedoc": "^0.17.0-3",
     "typedoc-plugin-markdown": "^3.0.2",
     "typescript": "^4.0.2",
+    "uvu": "^0.3.3",
     "uzip": "^0.20200919.0"
   }
 }

+ 6 - 4
src/index.ts

@@ -138,8 +138,8 @@ const bits16 = (d: Uint8Array, p: number) => {
 const inflt = (dat: Uint8Array, buf?: Uint8Array) => {
   // have to estimate size
   const noBuf = !buf;
-  // 4x - assumes ~25% compression ratio
-  if (noBuf) buf = new u8(dat.length << 2);
+  // Assumes roughly 33% compression ratio average
+  if (noBuf) buf = new u8(dat.length * 3);
   // ensure buffer can fit at least l elements
   const cbuf = (l: number) => {
     let bl = buf.length;
@@ -268,7 +268,7 @@ const inflt = (dat: Uint8Array, buf?: Uint8Array) => {
       }
     }
   }
-  return buf.slice(0, bt);
+  return bt == buf.length ? buf : buf.slice(0, bt);
 }
 
 // starting at p, write the minimum number of bits that can hold v to ds
@@ -497,7 +497,7 @@ const dflt = (dat: Uint8Array, lvl: number, plvl: number, pre: number, post: num
   // writing to this writes to the output buffer
   const w = o.subarray(pre, o.length - post);
   let pos = 0;
-  if (!lvl || dat.length < 4) {
+  if (!lvl) {
     for (let i = 0; i < s; i += 65535) {
       // end
       const e = i + 65535;
@@ -510,6 +510,8 @@ const dflt = (dat: Uint8Array, lvl: number, plvl: number, pre: number, post: num
         pos = wfblk(w, pos, dat.subarray(i, s));
       }
     }
+  } else if (s < 8) {
+    pos = wfblk(w, 0, dat);
   } else {
     const opt = deo[lvl - 1];
     const n = opt >>> 13, c = opt & 8191;

+ 22 - 0
test/0-valid.ts

@@ -0,0 +1,22 @@
+import { testSuites, workers, bClone } from './util';
+import * as assert from 'uvu/assert';
+
+// Name is to ensure that this runs first
+// Note that workers are not used here to optimize performance but rather
+// to prevent infinite loops from hanging the process.
+testSuites({
+  async compression(file) {
+    const fileClone = bClone(file);
+    const cProm = workers.fflate.deflate(fileClone, [fileClone.buffer]);
+    cProm.timeout(10000);
+    const buf = await cProm;
+    assert.ok(file.equals(await workers.zlib.inflate(buf, [buf.buffer])));
+  },
+  async decompression(file) {
+    const fileClone = bClone(file);
+    const data = await workers.zlib.deflate(fileClone, [fileClone.buffer]);
+    const dProm = workers.fflate.inflate(data, [data.buffer]);
+    dProm.timeout(5000);
+    assert.ok(file.equals(await dProm));
+  }
+});

+ 24 - 0
test/1-size.ts

@@ -0,0 +1,24 @@
+import { testSuites, workers, bClone } from './util';
+import { writeFileSync } from 'fs';
+import { join } from 'path';
+import { performance } from 'perf_hooks';
+import * as assert from 'uvu/assert';
+
+const sizePerf: Record<string, Record<string, [number, number]>> = {};
+
+testSuites({
+  async main(file, name) {
+    sizePerf[name] = {};
+    for (const lib of (['fflate', 'pako', 'uzip', 'zlib'] as const)) {
+      const clone = bClone(file);
+      const ts = performance.now();
+      sizePerf[name][lib] = [(await workers[lib].deflate([clone, { level: 9 }], [clone.buffer])).length, performance.now() - ts];
+    }
+    for (const lib of ['pako', 'uzip', 'zlib']) {
+      // Less than 5% larger
+      assert.ok(((sizePerf[name].fflate[0] - sizePerf[name][lib][0]) / sizePerf[name][lib][0]) < 0.05);
+    }
+  }
+}).then(() => {
+  writeFileSync(join(__dirname, 'results', 'longTimings.json'), JSON.stringify(sizePerf, null, 2));
+})

+ 46 - 0
test/2-perf.ts

@@ -0,0 +1,46 @@
+import { testSuites, workers, bClone, TestHandler } from './util';
+import { writeFileSync } from 'fs';
+import { join } from 'path';
+
+const preprocessors = {
+  inflate: workers.zlib.deflate,
+  gunzip: workers.zlib.gzip,
+  unzlib: workers.zlib.zlib
+};
+
+const cache: Record<string, Record<string, Buffer>> = {
+  deflate: {},
+  inflate: {},
+  gzip: {},
+  gunzip: {},
+  zlib: {},
+  unzlib: {}
+};
+
+const flattenedWorkers: Record<string, TestHandler> = {};
+for (const k in workers) {
+  for (const l in workers[k]) {
+    flattenedWorkers[k + '.' + l] = async (file, name, resetTimer) => {
+      const fileClone = bClone(file);
+      let buf = fileClone;
+      if (preprocessors[l]) {
+        buf = bClone(cache[l][name] || (cache[l][name] = Buffer.from(
+          await preprocessors[l as keyof typeof preprocessors](buf, [buf.buffer])
+        )));
+        resetTimer();
+      }
+      const opt2 = preprocessors[l]
+        ? k === 'tinyInflate'
+          ? new Uint8Array(file.length)
+          : null
+        : { level: 1 };
+      await workers[k][l]([buf, opt2], opt2 instanceof Uint8Array
+        ? [buf.buffer, opt2.buffer]
+        : [buf.buffer]);
+    }
+  }
+}
+
+testSuites(flattenedWorkers).then(perf => {
+  writeFileSync(join(__dirname, 'results', 'timings.json'), JSON.stringify(perf, null, 2));
+});

+ 2 - 0
test/data/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 2 - 0
test/results/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 8 - 0
test/tsconfig.json

@@ -0,0 +1,8 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "module": "CommonJS",
+    "moduleResolution": "node"
+  },
+  "include": ["./*.ts"]
+}

+ 186 - 0
test/util.ts

@@ -0,0 +1,186 @@
+import { existsSync, readFile, writeFile } from 'fs';
+import { resolve } from 'path';
+import { get } from 'https';
+import { suite } from 'uvu';
+import { performance } from 'perf_hooks';
+import { Worker } from 'worker_threads';
+
+const testFilesRaw = {
+  basic: Buffer.from('Hello world!'),
+  text: 'https://www.gutenberg.org/files/2701/old/moby10b.txt',
+  smallImage: 'https://www.hlevkin.com/TestImages/new/Rainier.bmp',
+  image: 'https://www.hlevkin.com/TestImages/new/Maltese.bmp',
+  largeImage: 'https://www.hlevkin.com/TestImages/new/Sunrise.bmp'
+};
+
+export type TestFile = keyof typeof testFilesRaw;
+
+const testFilesPromise = (async () => {
+  let res = {} as Record<TestFile, Buffer>;
+  for (const name in testFilesRaw) {
+    let data = testFilesRaw[name];
+    if (typeof data == 'string') {
+      const fn = resolve(__dirname, 'data', name);
+      if (!existsSync(fn)) {
+        console.log('Downloading ' + data + '...');
+        data = await new Promise((r, re) => get(data as string, res => {
+          const len = +res.headers['content-length'];
+          const buf = Buffer.allocUnsafe(len);
+          let i = 0;
+          res.on('data', chunk => {
+            buf.set(chunk, i);
+            console.log((100 * (i += chunk.length) / len).toFixed(1) + '%\x1B[1A');
+          });
+          res.on('error', re);
+          res.on('end', () => {
+            console.log('Complete');
+            writeFile(fn, buf, () => r(buf));
+          });
+        }));
+      } else {
+        data = await new Promise((res, rej) => 
+          readFile(fn, (err, buf) => err ? rej(err) : res(buf))
+        );
+      }
+    }
+    res[name] = data as Buffer;
+  }
+  return res;
+})();
+
+export type TestHandler = (file: Buffer, name: string, resetTimer: () => void) => unknown | Promise<unknown>;
+
+export const testSuites = async <T extends Record<string, TestHandler>>(suites: T) => {
+  const perf = {} as Record<keyof T, Promise<Record<TestFile, number>>>;
+  for (const k in suites) {
+    perf[k] = new Promise(async setPerf => {
+      const ste = suite(k);
+      let testFiles: typeof testFilesPromise extends Promise<infer T> ? T : never;
+      ste.before(() => testFilesPromise.then(v => {
+        testFiles = v;
+      }));
+      const localPerf: Record<string, number> = {};
+      for (const name in testFilesRaw) {
+        ste(name, async () => {
+          let ts = performance.now();
+          await suites[k](testFiles[name], name, () => {
+            ts = performance.now();
+          });
+          localPerf[name] = performance.now() - ts;
+        });
+      }
+      ste.after(() => {
+        setPerf(localPerf);
+      });
+      ste.run();
+    })
+  }
+  const resolvedPerf = {} as Record<keyof T, Record<TestFile, number>>;
+  for (const k in suites) resolvedPerf[k] = await perf[k];
+  return resolvedPerf;
+}
+
+// create worker string
+const cws = (pkg: string, method: string = '_cjsDefault') => `
+  const ${method == '_cjsDefault' ? method : `{ ${method} }`} = require('${pkg}');
+  const { Worker, workerData, parentPort } = require('worker_threads');
+  try {
+    const buf = ${method}(...(Array.isArray(workerData) ? workerData : [workerData]));
+    parentPort.postMessage(buf, [buf.buffer]);
+  } catch (err) {
+    parentPort.postMessage({ err });
+  }
+`;
+
+// create callback worker string
+const cbws = (pkg: string, method: string, alias = 'run') => `
+  const { ${method}: ${alias} } = require('${pkg}');
+  const { Worker, workerData, parentPort } = require('worker_threads');
+  ${alias}(...(Array.isArray(workerData) ? workerData : [workerData]), (err, buf) => {
+    if (err) parentPort.postMessage({ err });
+    else parentPort.postMessage(buf, [buf.buffer]);
+  });
+`;
+
+export type Workerized = (workerData: Uint8Array | [Uint8Array, {}], transferable?: ArrayBuffer[]) => WorkerizedResult;
+export interface WorkerizedResult extends PromiseLike<Uint8Array> {
+  timeout(ms: number): void;
+};
+
+// Worker creator
+const wc = (pkg: string, method?: string, cb = false): Workerized => {
+  const str = cb ? cbws(pkg, method) : cws(pkg, method);
+  return (workerData, transferable) => {
+    const worker = new Worker(str, {
+      eval: true,
+      workerData,
+      transferList: transferable
+    });
+    let terminated = false;
+    return {
+      timeout(ms: number) {
+        const tm = setTimeout(() => {
+          worker.terminate();
+          terminated = true;
+        }, ms);
+        worker.once('message', () => clearTimeout(tm));
+      },
+      then(res, rej) {
+        return new Promise((res, rej) => {
+          worker
+            .once('message', msg => {
+              if (msg.err) rej(msg.err);
+              res(msg);
+            })
+            .once('error', rej)
+            .once('exit', code => {
+              if (terminated) rej(new Error('Timed out'));
+              else if (code !== 0) rej(new Error('Exited with status code ' + code));
+            });
+        }).then(res, rej);
+      }
+    };
+  }
+}
+
+const fflate = resolve(__dirname, '..');
+
+export const workers = {
+  fflate: {
+    deflate: wc(fflate, 'deflate'),
+    inflate: wc(fflate, 'inflate'),
+    gzip: wc(fflate, 'gzip'),
+    gunzip: wc(fflate, 'gunzip'),
+    zlib: wc(fflate, 'zlib'),
+    unzlib: wc(fflate, 'unzlib')
+  },
+  pako: {
+    deflate: wc('pako', 'deflateRaw'),
+    inflate: wc('pako', 'inflateRaw'),
+    gzip: wc('pako', 'gzip'),
+    gunzip: wc('pako', 'ungzip'),
+    zlib: wc('pako', 'deflate'),
+    unzlib: wc('pako', 'inflate')
+  },
+  uzip: {
+    deflate: wc('uzip', 'deflateRaw'),
+    inflate: wc('uzip', 'inflateRaw')
+  },
+  tinyInflate: {
+    inflate: wc('tiny-inflate')
+  },
+  zlib: {
+    deflate: wc('zlib', 'deflateRaw', true),
+    inflate: wc('zlib', 'inflateRaw', true),
+    gzip: wc('zlib', 'gzip', true),
+    gunzip: wc('zlib', 'gunzip', true),
+    zlib: wc('zlib', 'deflate', true),
+    unzlib: wc('zlib', 'inflate', true)
+  }
+};
+
+export const bClone = (buf: Buffer) => {
+  const clone = Buffer.allocUnsafe(buf.length);
+  clone.set(buf);
+  return clone;
+}

+ 3 - 2
tsconfig.json

@@ -1,7 +1,8 @@
 {
   "compilerOptions": {
     "declaration": true,
-    "outDir": "lib/"
+    "outDir": "lib/",
+    "lib": ["ES5"]
   },
-  "files": ["src/index.ts"]
+  "include": ["src/index.ts"]
 }

+ 88 - 1
yarn.lock

@@ -7,6 +7,16 @@
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
+"@types/node@^14.11.2":
+  version "14.11.2"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256"
+  integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==
+
+arg@^4.1.0:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
+  integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
+
 backbone@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12"
@@ -27,11 +37,26 @@ brace-expansion@^1.1.7:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
+buffer-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
 [email protected]:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
+dequal@^1.0.0, dequal@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
+  integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
+
+diff@^4.0.1, diff@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
 fs-extra@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -110,6 +135,11 @@ jsonfile@^4.0.0:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+kleur@^4.0.3:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.1.tgz#80b49dd7d1afeba41b8dcdf4ecfff9252205fc52"
+  integrity sha512-BsNhM6T/yTWFG580CRnYhT3LfUuPK7Hwrm+W2H0G8lK/nogalP5Nsrh/cHjxVVkzl0sFm7z8b8rNcZCfKxeoxA==
+
 lodash@^4.17.15:
   version "4.17.20"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
@@ -120,6 +150,11 @@ lunr@^2.3.8:
   resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
   integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
 
+make-error@^1.1.1:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+  integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
 marked@^0.8.0:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355"
@@ -137,6 +172,11 @@ minimist@^1.2.5:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
+mri@^1.1.0:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
+  integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==
+
 neo-async@^2.6.0:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
@@ -183,6 +223,13 @@ resolve@^1.1.6:
   dependencies:
     path-parse "^1.0.6"
 
+sade@^1.7.3:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.3.tgz#a217ccc4fb4abb2d271648bf48f6628b2636fa1b"
+  integrity sha512-m4BctppMvJ60W1dXnHq7jMmFe3hPJZDAH85kQ3ACTo7XZNVUuTItCQ+2HfyaMeV5cKrbw7l4vD/6We3GBxvdJw==
+  dependencies:
+    mri "^1.1.0"
+
 shelljs@^0.8.3:
   version "0.8.4"
   resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
@@ -192,7 +239,15 @@ shelljs@^0.8.3:
     interpret "^1.0.0"
     rechoir "^0.6.2"
 
-source-map@^0.6.1:
+source-map-support@^0.5.17:
+  version "0.5.19"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+  integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
+source-map@^0.6.0, source-map@^0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@@ -202,6 +257,22 @@ tiny-inflate@^1.0.3:
   resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
   integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
 
+totalist@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
+  integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
+
+ts-node@^9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3"
+  integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==
+  dependencies:
+    arg "^4.1.0"
+    diff "^4.0.1"
+    make-error "^1.1.1"
+    source-map-support "^0.5.17"
+    yn "3.1.1"
+
 [email protected]:
   version "0.8.0-0"
   resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.8.0-0.tgz#80b7080837b2c9eba36c2fe06601ebe01973a0cd"
@@ -255,6 +326,17 @@ universalify@^0.1.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
+uvu@^0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.3.3.tgz#ad2edd8d2fca50c4dcb39fb2e843f4c15e48ac94"
+  integrity sha512-5oeS+bkoRM5FGjXMXCZvjLLeUB9P5HXn2LFiTQaItopJFBtXuJ2XxWkWJNe2NgWthxFUOZR+eyhodgiaFqv7wQ==
+  dependencies:
+    dequal "^1.0.0"
+    diff "^4.0.2"
+    kleur "^4.0.3"
+    sade "^1.7.3"
+    totalist "^1.1.0"
+
 uzip@^0.20200919.0:
   version "0.20200919.0"
   resolved "https://registry.yarnpkg.com/uzip/-/uzip-0.20200919.0.tgz#a4ae1d13265f086021e2e7933412b9b8d9f06155"
@@ -269,3 +351,8 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
[email protected]:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
+  integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==