From 5ee11f5c08299a44f96e2a4e1d5627079cfd6e44 Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 14:19:41 +0300 Subject: [PATCH 1/9] first version --- .eslintignore | 2 + .eslintrc.json | 56 + .gitignore | 3 + .npmignore | 3 + .npmrc | 1 + README.md | 150 ++- package-lock.json | 2752 +++++++++++++++++++++++++++++++++++++++++++ package.json | 58 + spec/bucket.spec.ts | 132 +++ spec/object.spec.ts | 427 +++++++ spec/utils.ts | 44 + src/bucket.ts | 82 ++ src/client.ts | 41 + src/index.ts | 3 + src/object.ts | 109 ++ tsconfig.json | 20 + 16 files changed, 3881 insertions(+), 2 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .npmrc create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 spec/bucket.spec.ts create mode 100644 spec/object.spec.ts create mode 100644 spec/utils.ts create mode 100644 src/bucket.ts create mode 100644 src/client.ts create mode 100644 src/index.ts create mode 100644 src/object.ts create mode 100644 tsconfig.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..0a95885 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +/dist +node_modules \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..f8aa4d2 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,56 @@ +{ + "extends": [ + "standard-with-typescript", + "plugin:mocha/recommended" + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "plugins": [ + "mocha" + ], + "rules": { + "semi": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/promise-function-async": "off", + "@typescript-eslint/return-await": "off", + "@typescript-eslint/member-delimiter-style": [ + "error", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ], + "@typescript-eslint/indent": [ + "error", + 4, + { + "SwitchCase": 1, + "FunctionDeclaration": { + "parameters": "first" + }, + "FunctionExpression": { + "parameters": "first" + } + } + ], + "max-len": [ + "warn", + 120, + 4, + { + "ignoreUrls": true, + "ignoreTemplateLiterals": true + } + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17f112b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +/dist +node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..9ec9b31 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +.idea +/src +tsconfig.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..cffe8cd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/README.md b/README.md index 34f6294..79733b3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,148 @@ -# maester-client -Client library for object storage +# Maester Client + +The official object-storage client for elasticio-sailor-nodejs. + +## Usage + +### Create client +``` +const Client = require('@elasticio/maester-client'); + +const jwtPayload = { + tenantId: 'tenant-id', + contractId: 'contract-id', + workspaceId: 'workspace-id', + flowId: 'flow-id', + userId: 'user-id' +}; + +const jwtSecret = 'my-super-secret'; + +const client = new Client('http://maester.local:3002'); +client.sign(jwtPayload, jwtSecret); +``` + +### Buckets API + +Get bucket: + +``` +const bucket = await client.buckets.get(id); +``` + +List buckets: + +``` +const buckets = await client.buckets.list({ + page: { + number: 1, + size: 25 + } +}); +``` + +List buckets by external ID: + +``` +const buckets = await client.buckets.list({ + externalId: 'my-external-id' +}); +``` + +Create bucket: + +``` +const bucket = await client.buckets.create({ + objects: ['object-1', 'object-2', ..., 'object-N'], + extrenalId: 'my-external-id +}); +``` + +Update bucket: + +``` +const bucket = await client.buckets.update(id, { + closed: true +}); +``` + +Delete bucket: + +``` +await client.buckets.delete(id); +``` + +### Objects API + +Get object: + +``` +const object = await client.objects.get(id); +console.log(object.data); +``` + +Get object as stream: + +``` +const object = await client.objects.get(id, 'stream'); +object.data.pipe(...) +``` + +Create object: + +``` +const response = await client.objects.create(data); +``` + +where `data` can be `string`, `Buffer`, `FormData` or `Stream` + +Create object with metadata: + +``` +const response = await client.objects.create(data, { + metadata: { + key: 'value' + } +}); +``` + +Create object and add it to a bucket: + +``` +const response = await client.objects.create(data, { + bucket: 'bucket-id' +}); +``` + +Create object and override content type: + +``` +const response = await client.objects.create(data, { + contentType: 'text/plain' +}); +``` + +Create multiple objects at once: + +``` +const FormData = require('form-data'); + +const data = new FormData(); +data.append('data', 'hello world', { contentType: 'text/plain' }); +data.append('data', JSON.stringify(json), { contentType: 'application/json' }); +data.append('data', fs.createReadStream('/foo/bar.jpg')) +data.append('data', Buffer.allocUnsafe(1024)); + +const response = await client.objects.create(data, { + bucket: 'bucket-id', + metadata: { + description: 'my stuff' + } +}); +``` + +Delete object: + +``` +await client.objects.delete(id); +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e8fdd5f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2752 @@ +{ + "name": "elasticio-maester-client", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/chai": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz", + "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", + "dev": true + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/dicer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/dicer/-/dicer-0.2.0.tgz", + "integrity": "sha512-S85N2bzETv74kRmD5Mmai22/vExHS6ONR6vQvh6geWbo025nOEUsOQmublwKGBvAzHL1oRpOaMtCRxK7DAKzaA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, + "@types/jsonwebtoken": { + "version": "8.3.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.9.tgz", + "integrity": "sha512-00rI8GbOKuRtoYxltFSRTVUXCRLbuYwln2/nUMPtFU9JGS7if+nnmLjeoFGmqsNCmblPLAaeQ/zMLVsHr6T5bg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mocha": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", + "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", + "dev": true + }, + "@types/nock": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", + "integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==", + "dev": true, + "requires": { + "nock": "*" + } + }, + "@types/node": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz", + "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==", + "dev": true + }, + "@types/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz", + "integrity": "sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.28.0", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz", + "integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.28.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.28.0.tgz", + "integrity": "sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/typescript-estree": "2.28.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz", + "integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + } + } + }, + "eslint-config-standard": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz", + "integrity": "sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==", + "dev": true + }, + "eslint-config-standard-with-typescript": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-15.0.1.tgz", + "integrity": "sha512-Bsk21CA1WV5K+MVrYM8CV7dbnflNFmrL22Dahi3M3VQp8hPFVnsAlgXmN9CMTXEKpP048semsZeIdYQttSoqBA==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^2.24.0", + "eslint-config-standard": "^14.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", + "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-plugin-es": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz", + "integrity": "sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", + "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-plugin-mocha": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", + "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "ramda": "^0.27.0" + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-plugin-standard": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", + "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.0.tgz", + "integrity": "sha512-/5qB+Mb0m2bh86tjGbA8pB0qBfdmCIK6ZNPjcw4/TtEH0+tTf0wLA5HK4KMTweSMwLGHwBDWCBV+6+2+EuHmgg==", + "dev": true, + "requires": { + "estraverse": "^5.0.0" + }, + "dependencies": { + "estraverse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", + "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.3", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nock": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/nock/-/nock-12.0.3.tgz", + "integrity": "sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.13", + "propagate": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" + }, + "ramda": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", + "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz", + "integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..83c5c70 --- /dev/null +++ b/package.json @@ -0,0 +1,58 @@ +{ + "name": "@elasticio/maester-client", + "version": "1.0.0", + "description": "The official object-storage client for sailor-nodejs.", + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "mocha -r ts-node/register spec/**/*.spec.ts", + "pretest": "eslint --ext .ts .", + "prepublish": "npm run build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/elasticio/maester-client.git" + }, + "keywords": [ + "elasticio", + "platform", + "ipaas", + "nodejs" + ], + "author": "Elastic.io", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/elasticio/maester-client/issues" + }, + "homepage": "https://github.com/elasticio/maester-client#readme", + "devDependencies": { + "@types/chai": "4.2.11", + "@types/dicer": "0.2.0", + "@types/jsonwebtoken": "8.3.9", + "@types/mocha": "7.0.2", + "@types/nock": "11.1.0", + "@types/qs": "6.9.1", + "@typescript-eslint/eslint-plugin": "2.28.0", + "@typescript-eslint/parser": "2.28.0", + "chai": "4.2.0", + "eslint": "6.8.0", + "eslint-config-standard-with-typescript": "15.0.1", + "eslint-plugin-import": "2.20.2", + "eslint-plugin-mocha": "6.3.0", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-promise": "4.2.1", + "eslint-plugin-standard": "4.0.1", + "mocha": "7.1.1", + "nock": "12.0.3", + "ts-node": "8.8.2", + "typescript": "3.8.3" + }, + "dependencies": { + "axios": "0.19.2", + "form-data": "3.0.0", + "jsonwebtoken": "8.5.1", + "qs": "6.9.3" + } +} diff --git a/spec/bucket.spec.ts b/spec/bucket.spec.ts new file mode 100644 index 0000000..52e0b9e --- /dev/null +++ b/spec/bucket.spec.ts @@ -0,0 +1,132 @@ +import nock from 'nock'; +import { expect } from 'chai'; +import { describe, it, before } from 'mocha'; + +import { baseUri, randomObjectId, getClient, getToken } from './utils'; + +describe('buckets', () => { + function randomBucket() { + return { + id: randomObjectId(), + objects: [randomObjectId(), randomObjectId(), randomObjectId()], + externalId: randomObjectId(), + closed: true, + createdAt: new Date().toISOString() + }; + } + + function transformResponse(response: any) { + return { + ...response, + createdAt: new Date(response.createdAt) + } + } + + before(function () { + this.client = getClient(); + this.options = { + reqheaders: { + authorization: `Bearer ${getToken()}` + } + }; + }); + + it('should get bucket', async function () { + const response = randomBucket(); + + const scope = nock(baseUri, this.options) + .get(`/buckets/${response.id}`) + .reply(200, response); + + const bucket = await this.client.buckets.get(response.id); + + expect(scope.isDone()).to.be.true; + expect(bucket).to.deep.equal(transformResponse(response)); + }); + + it('should list buckets', async function () { + const response = { + data: [ + randomBucket() + ], + meta: { + page: 1, + perPage: 25, + total: 1, + totalPages: 1 + } + }; + + const params = { + workspaceId: randomObjectId(), + externalId: response.data[0].externalId, + page: { + number: response.meta.page, + size: response.meta.perPage + } + }; + + const scope = nock(baseUri, this.options) + .get('/buckets') + .query(params) + .reply(200, response); + + const buckets = await this.client.buckets.list(params); + + expect(scope.isDone()).to.be.true; + expect(buckets).to.deep.equal({ + data: [ + transformResponse(response.data[0]) + ], + meta: { + ...response.meta + } + }); + }); + + it('should create bucket', async function () { + const response = randomBucket(); + + const scope = nock(baseUri, this.options) + .post('/buckets') + .reply(201, response); + + const bucket = await this.client.buckets.create({ + objects: response.objects, + externalId: response.externalId + }); + + expect(scope.isDone()).to.be.true; + expect(bucket).to.deep.equal(transformResponse(response)); + }); + + it('should update bucket', async function () { + const response = randomBucket(); + + const body = { + objects: response.objects, + closed: response.closed + }; + + const scope = nock(baseUri, this.options) + .patch(`/buckets/${response.id}`, body) + .reply(200, response); + + const bucket = await this.client.buckets.update(response.id, body); + + expect(scope.isDone()).to.be.true; + expect(bucket).to.deep.equal(transformResponse(response)); + }); + + it('should delete bucket', async function () { + const id = randomObjectId(); + + const scope = nock(baseUri, this.options) + .delete(`/buckets/${id}`) + .reply(204); + + await this.client.buckets.delete(id); + + expect(scope.isDone()).to.be.true; + }); +}); diff --git a/spec/object.spec.ts b/spec/object.spec.ts new file mode 100644 index 0000000..7a03823 --- /dev/null +++ b/spec/object.spec.ts @@ -0,0 +1,427 @@ +import nock from 'nock'; +import { createHash, randomBytes } from 'crypto'; +import { expect } from 'chai'; +import { describe, it, before } from 'mocha'; +import { Readable } from 'stream'; +import FormData from 'form-data'; + +import { baseUri, randomObjectId, getClient, getToken, matchFormData } from './utils'; +import { metaToHeaders } from '../src'; + +describe('objects', function () { + interface RandomCreateObjectParams { + contentType?: string; + metadata?: Record; + bucket?: string; + } + + function randomCreateObjectResponse(data: string | Buffer, params?: RandomCreateObjectParams) { + const { contentType, metadata } = params ?? {}; + return { + objectId: randomObjectId(), + contentType: contentType ?? 'application/octet-stream', + contentLength: data.length, + md5: createHash('md5').update(data).digest().toString('hex'), + createdAt: new Date().toISOString(), + metadata: metadata ?? {} + }; + } + + function transformResponse(response: any) { + const res = { + ...response, + id: response.objectId, + createdAt: new Date(response.createdAt) + }; + delete res.objectId; + return res; + } + + before(function () { + this.client = getClient(); + this.authorization = { + reqheaders: { + authorization: `Bearer ${getToken()}` + } + }; + }); + + describe('get object', function () { + it('should get object as text', async function () { + const id = randomObjectId(); + const data = 'test123'; + const contentType = 'text/plain'; + const contentLength = data.length; + const metadata = { + field1: 'test', + 'field-2': '123' + }; + + const scope = nock(baseUri, this.options) + .defaultReplyHeaders({ + 'content-type': contentType, + 'content-length': contentLength.toString(), + ...metaToHeaders(metadata) + }) + .get(`/objects/${id}`) + .reply(200, data); + + const response = await this.client.objects.get(id); + + expect(scope.isDone()).to.be.true; + expect(response.contentType).to.equal(contentType); + expect(response.contentLength).to.equal(contentLength); + expect(response.metadata).to.deep.equal(metadata); + expect(response.data).to.equal(data); + }); + + it('should get object as JSON', async function () { + const id = randomObjectId(); + const data = { + key: 'value' + }; + const contentType = 'application/json'; + const contentLength = JSON.stringify(data).length; + const metadata = { + field1: 'test', + 'field-2': '123' + }; + + const scope = nock(baseUri, this.options) + .defaultReplyHeaders({ + 'content-type': contentType, + 'content-length': contentLength.toString(), + ...metaToHeaders(metadata) + }) + .get(`/objects/${id}`) + .reply(200, data); + + const response = await this.client.objects.get(id); + + expect(scope.isDone()).to.be.true; + expect(response.contentType).to.equal(contentType); + expect(response.contentLength).to.equal(contentLength); + expect(response.metadata).to.deep.equal(metadata); + expect(response.data).to.deep.equal(data); + }); + + it('should get object as stream', async function () { + const id = randomObjectId(); + const data = randomBytes(1024); + const contentType = 'application/octet-stream'; + const contentLength = data.length; + const metadata = { + field1: 'test', + 'field-2': '123' + }; + + const scope = nock(baseUri, this.options) + .defaultReplyHeaders({ + 'content-type': contentType, + 'content-length': contentLength.toString(), + ...metaToHeaders(metadata) + }) + .get(`/objects/${id}`) + .reply(200, data); + + const response = await this.client.objects.get(id, 'stream'); + + expect(scope.isDone()).to.be.true; + expect(response.contentType).to.equal(contentType); + expect(response.contentLength).to.equal(contentLength); + expect(response.metadata).to.deep.equal(metadata); + + const buffer: Buffer = await new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + response.data.on('data', (chunk: Buffer) => chunks.push(chunk)); + response.data.on('error', reject); + response.data.on('end', () => resolve(Buffer.concat(chunks))) + }); + + expect(buffer.toString()).to.equal(data.toString()); + }); + }); + + describe('create object', function () { + describe('ordinary', function () { + it('should create object from text', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', response.contentType) + .matchHeader('content-length', response.contentLength.toString()) + .post('/objects', data) + .reply(201, response); + + const object = await this.client.objects.create(data); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object with metadata', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data, { + metadata: { + field1: 'test', + 'field-2': '123' + } + }); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', response.contentType) + .matchHeader('content-length', response.contentLength.toString()) + .matchHeader('x-meta-field1', response.metadata.field1) + .matchHeader('x-meta-field-2', response.metadata['field-2']) + .post('/objects', data) + .reply(201, response); + + const object = await this.client.objects.create(data, { + metadata: response.metadata + }); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object and attach it bucket', async function () { + const data = 'test123'; + const bucket = randomObjectId(); + const response = randomCreateObjectResponse(data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', response.contentType) + .matchHeader('content-length', response.contentLength.toString()) + .matchHeader('x-meta-bucket', bucket) + .post('/objects', data) + .reply(201, response); + + const object = await this.client.objects.create(data, { bucket }); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object and override content type', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data, { contentType: 'text/plain' }); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', response.contentType) + .matchHeader('content-length', response.contentLength.toString()) + .post('/objects', data) + .reply(201, response); + + const object = await this.client.objects.create(data, { + contentType: response.contentType + }); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object from buffer', async function () { + const data = randomBytes(1024); + const response = randomCreateObjectResponse(data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', response.contentType) + .matchHeader('content-length', response.contentLength.toString()) + .post('/objects', data) + .reply(201, response); + + const object = await this.client.objects.create(data); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object from stream', async function () { + const data = randomBytes(1024); + const stream = Readable.from([data]); + const response = randomCreateObjectResponse(data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', response.contentType) + .post('/objects', data) + .reply(201, response); + + const object = await this.client.objects.create(stream); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + }); + + describe('multipart/form-data', function () { + it('should create object from text', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data); + + const formData = new FormData(); + formData.append('data', data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .post('/objects', body => + matchFormData(body, 'data', data)) + .reply(201, response); + + const object = await this.client.objects.create(formData); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object with metadata', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data, { + metadata: { + field1: 'test', + 'field-2': '123' + } + }); + + const formData = new FormData(); + formData.append('data', data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .matchHeader('x-meta-field1', response.metadata.field1) + .matchHeader('x-meta-field-2', response.metadata['field-2']) + .post('/objects', body => + matchFormData(body, 'data', data)) + .reply(201, response); + + const object = await this.client.objects.create(formData, { + metadata: response.metadata + }); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object and attach it bucket', async function () { + const data = 'test123'; + const bucket = randomObjectId(); + const response = randomCreateObjectResponse(data); + + const formData = new FormData(); + formData.append('data', data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .post('/objects', body => + matchFormData(body, 'data', data)) + .reply(201, response); + + const object = await this.client.objects.create(formData, { bucket }); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object and override content type', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data, { contentType: 'text/plain' }); + + const formData = new FormData(); + formData.append('data', data, { contentType: response.contentType }); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType)) + .reply(201, response); + + const object = await this.client.objects.create(formData); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object from buffer', async function () { + const data = randomBytes(1024); + const response = randomCreateObjectResponse(data); + + const formData = new FormData(); + formData.append('data', data); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .post('/objects', body => + matchFormData(body, 'data', data, 'application/octet-stream')) + .reply(201, response); + + const object = await this.client.objects.create(formData); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create object from stream', async function () { + const data = randomBytes(1024); + const stream = Readable.from([data]); + const response = randomCreateObjectResponse(data); + + const formData = new FormData(); + formData.append('data', stream); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .post('/objects', body => + matchFormData(body, 'data', data, 'application/octet-stream')) + .reply(201, response); + + const object = await this.client.objects.create(formData); + + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); + + it('should create multiple objects', async function () { + const data1 = 'test123'; + const data2 = randomBytes(1024); + const data3 = randomBytes(1024); + const stream = Readable.from([data3]); + const response = [ + randomCreateObjectResponse(data1), + randomCreateObjectResponse(data2), + randomCreateObjectResponse(data3) + ]; + + const formData = new FormData(); + formData.append('data', data1); + formData.append('data', data2); + formData.append('data', stream); + + const scope = nock(baseUri, this.options) + .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) + .post('/objects', body => + matchFormData(body, 'data', data1) && + matchFormData(body, 'data', data2, 'application/octet-stream') && + matchFormData(body, 'data', data3, 'application/octet-stream') + ) + .reply(201, response); + + const objects = await this.client.objects.create(formData); + + expect(scope.isDone()).to.be.true; + expect(objects).to.deep.equal(response.map(transformResponse)); + }); + }); + }); + + it('should delete objects', async function () { + const id = randomObjectId(); + + const scope = nock(baseUri, this.options) + .delete(`/objects/${id}`) + .reply(204); + + await this.client.objects.delete(id); + + expect(scope.isDone()).to.be.true; + }); +}); diff --git a/spec/utils.ts b/spec/utils.ts new file mode 100644 index 0000000..c2ac352 --- /dev/null +++ b/spec/utils.ts @@ -0,0 +1,44 @@ +import { randomBytes } from 'crypto'; +import { sign } from 'jsonwebtoken'; + +import { Client } from '../src'; + +export const baseUri = 'http://127.0.0.1'; + +export const jwtPayload = { + tenantId: 'tenant-id', + contractId: 'contract-id', + workspaceId: 'workspace-id', + flowId: 'flow-id', + userId: 'user-id' +}; + +export const jwtSecret = 'secret'; + +export function randomObjectId(): string { + return randomBytes(16).toString('hex'); +} + +export function getClient(): Client { + const client = new Client(baseUri); + client.sign(jwtPayload, jwtSecret); + return client; +} + +export function getToken(): string { + return sign(jwtPayload, jwtSecret); +} + +export function matchFormData(body: string, name: string, data?: string | Buffer, contentType?: string): boolean { + // nock returns hex-encoded body for binary responses + const rawBody = Buffer.from(body, 'hex').toString() || body; + let search = `Content-Disposition: form-data; name="${name}"\r\n`; + if (contentType) { + search += `Content-Type: ${contentType}\r\n`; + } + search += '\r\n'; + if (data) { + search = search + String(data) + '\r\n'; + } + return rawBody.includes(search); +} diff --git a/src/bucket.ts b/src/bucket.ts new file mode 100644 index 0000000..f87f1b9 --- /dev/null +++ b/src/bucket.ts @@ -0,0 +1,82 @@ +import { AxiosInstance, AxiosResponse } from 'axios'; + +export class Bucket { + public readonly id: string; + public readonly objects: string[]; + public readonly externalId: string | null; + public readonly closed: boolean; + public readonly createdAt: Date; + + constructor(data: any) { + const { id, objects, externalId, closed, createdAt } = data; + this.id = id ?? ''; + this.objects = objects ?? []; + this.externalId = externalId ?? null; + this.closed = closed ?? false; + this.createdAt = new Date(createdAt); + } +} + +export interface BucketList { + data: Bucket[]; + meta: { + page: number; + perPage: number; + total: number; + totalPages: number; + }; +} + +export interface ListBucketsParams { + externalId?: string; + page?: { + number?: number; + size?: number; + }; +} + +export interface CreateBucketData { + objects: string[]; + externalId?: string; +} + +export interface UpdateBucketData { + objects?: string[]; + externalId?: string | null; + closed?: boolean; +} + +export default class BucketRepository { + private readonly client: AxiosInstance; + + constructor(client: AxiosInstance) { + this.client = client; + } + + public get(id: string): Promise { + return this.client.get(`/buckets/${id}`) + .then((res: AxiosResponse) => new Bucket(res.data)); + } + + public list(params?: ListBucketsParams): Promise { + return this.client.get('/buckets', { params }) + .then((response: AxiosResponse) => ({ + data: response.data.data.map((bucket: any) => new Bucket(bucket)), + meta: response.data.meta + })); + } + + public create(data: CreateBucketData): Promise { + return this.client.post('/buckets', data) + .then((res: AxiosResponse) => new Bucket(res.data)); + } + + public update(id: string, data: UpdateBucketData): Promise { + return this.client.patch(`/buckets/${id}`, data) + .then((res: AxiosResponse) => new Bucket(res.data)); + } + + public delete(id: string): Promise { + return this.client.delete(`/buckets/${id}`); + } +} diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..6b0bc70 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,41 @@ +import qs from 'qs'; +import { sign } from 'jsonwebtoken'; +import axios, { AxiosInstance } from 'axios'; + +import pkg from '../package.json'; +import BucketRepository from './bucket'; +import ObjectRepository from './object'; + +export interface JwtPayload { + tenantId: string; + contractId: string; + workspaceId: string; + flowId: string; + userId: string; +} + +export class Client { + private readonly client: AxiosInstance; + public readonly version = pkg.version; + + public readonly buckets: BucketRepository; + public readonly objects: ObjectRepository; + + constructor(baseUri: string) { + this.client = axios.create({ + baseURL: baseUri, + paramsSerializer: params => qs.stringify(params), + headers: { + 'User-Agent': `maester-client/${this.version}` + } + }); + this.buckets = new BucketRepository(this.client); + this.objects = new ObjectRepository(this.client); + } + + public sign(jwtPayload: JwtPayload, jwtSecret: string): Client { + const jwtToken = sign(jwtPayload, jwtSecret); + this.client.defaults.headers.common.authorization = `Bearer ${jwtToken}`; + return this; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5a07290 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from './client'; +export * from './bucket'; +export * from './object'; diff --git a/src/object.ts b/src/object.ts new file mode 100644 index 0000000..6b25802 --- /dev/null +++ b/src/object.ts @@ -0,0 +1,109 @@ +import Stream from 'stream'; +import FormData from 'form-data'; +import { AxiosInstance, AxiosResponse, ResponseType } from 'axios'; + +export const USER_META_HEADER_PREFIX = 'x-meta-'; + +export type ObjectMetadata = Record; + +export function metaToHeaders(meta: ObjectMetadata): Record { + const headers: Record = {}; + for (const [key, value] of Object.entries(meta)) { + headers[`${USER_META_HEADER_PREFIX}${key}`] = String(value); + } + return headers; +} + +export function headersToMeta(headers: Record): ObjectMetadata { + const meta: ObjectMetadata = {}; + for (const [key, value] of Object.entries(headers)) { + if (key.indexOf(USER_META_HEADER_PREFIX) === 0) { + meta[key.substr(USER_META_HEADER_PREFIX.length)] = value; + } + } + return meta; +} + +export class GetObjectResponse { + public readonly contentType: string; + public readonly contentLength: number; + public readonly metadata: ObjectMetadata; + public readonly data: any; + + constructor(res: AxiosResponse) { + const { headers, data } = res; + this.contentType = headers['content-type'] ?? ''; + this.contentLength = parseInt(headers['content-length'] ?? 0); + this.metadata = headersToMeta(headers); + this.data = data; + } +} + +export interface CreateObjectParams { + contentType?: string; + bucket?: string; + metadata?: ObjectMetadata; +} + +export class CreateObjectResponse { + public readonly id: string; + public readonly contentType: string; + public readonly contentLength: number; + public readonly md5: string; + public readonly createdAt: Date; + public readonly metadata: ObjectMetadata; + + constructor(data: any) { + const { objectId, contentType, contentLength, md5, createdAt, metadata } = data; + this.id = objectId ?? ''; + this.contentType = contentType ?? ''; + this.contentLength = contentLength ?? 0; + this.md5 = md5 ?? ''; + this.createdAt = new Date(createdAt); + this.metadata = metadata ?? {}; + } +} + +export default class ObjectRepository { + private readonly client: AxiosInstance; + + constructor(client: AxiosInstance) { + this.client = client; + } + + public get(id: string, responseType?: ResponseType): Promise { + return this.client.get(`/objects/${id}`, { responseType }) + .then((res: AxiosResponse) => new GetObjectResponse(res)); + } + + public create(data: string | Buffer | FormData | Stream, + params?: CreateObjectParams): Promise { + const { contentType, bucket, metadata } = params ?? {}; + const userMetadata = metadata ?? {}; + + if (bucket) { + if (data instanceof FormData) { + data.append('bucket', bucket); + } else { + userMetadata.bucket = bucket; + } + } + + const headers = { + ...metaToHeaders(userMetadata) + }; + + headers['content-type'] = data instanceof FormData + ? `multipart/form-data; boundary=${data.getBoundary()}` + : contentType ?? 'application/octet-stream'; + + return this.client.post('/objects', data, { headers }) + .then((res: AxiosResponse) => Array.isArray(res.data) + ? res.data.map(data => new CreateObjectResponse(data)) + : new CreateObjectResponse(res.data)); + } + + public delete(id: string): Promise { + return this.client.delete(`/objects/${id}`); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..098a033 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "outDir": "./dist", + "strict": true, + "sourceMap": true, + "declaration": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "lib": [ + "es2018", + "dom" + ] + }, + "include": [ + "src/**/*", + "spec/**/*" + ] +} From d2a96097660a2744560bc7a19b7a1c4b1cac1716 Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 15:05:31 +0300 Subject: [PATCH 2/9] get rid of sign --- README.md | 13 +----- package-lock.json | 101 ++------------------------------------------ package.json | 1 - spec/bucket.spec.ts | 17 ++++---- spec/object.spec.ts | 42 +++++++++--------- spec/utils.ts | 25 ----------- src/client.ts | 26 ++++-------- 7 files changed, 43 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index 79733b3..dae8cd9 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,7 @@ The official object-storage client for elasticio-sailor-nodejs. ``` const Client = require('@elasticio/maester-client'); -const jwtPayload = { - tenantId: 'tenant-id', - contractId: 'contract-id', - workspaceId: 'workspace-id', - flowId: 'flow-id', - userId: 'user-id' -}; - -const jwtSecret = 'my-super-secret'; - -const client = new Client('http://maester.local:3002'); -client.sign(jwtPayload, jwtSecret); +const client = new Client('http://maester.local:3002', 'my-token'); ``` ### Buckets API diff --git a/package-lock.json b/package-lock.json index e8fdd5f..21592d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "elasticio-maester-client", + "name": "@elasticio/maester-client", "version": "1.0.0", "lockfileVersion": 1, "requires": true, @@ -357,11 +357,6 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -604,14 +599,6 @@ "esutils": "^2.0.2" } }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -1523,49 +1510,6 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -1604,41 +1548,6 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -2237,11 +2146,6 @@ "tslib": "^1.9.0" } }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2251,7 +2155,8 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true }, "set-blocking": { "version": "2.0.0", diff --git a/package.json b/package.json index 83c5c70..2dcfcec 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "dependencies": { "axios": "0.19.2", "form-data": "3.0.0", - "jsonwebtoken": "8.5.1", "qs": "6.9.3" } } diff --git a/spec/bucket.spec.ts b/spec/bucket.spec.ts index 52e0b9e..efeb2a9 100644 --- a/spec/bucket.spec.ts +++ b/spec/bucket.spec.ts @@ -2,7 +2,8 @@ import nock from 'nock'; import { expect } from 'chai'; import { describe, it, before } from 'mocha'; -import { baseUri, randomObjectId, getClient, getToken } from './utils'; +import { randomObjectId } from './utils'; +import { Client } from '../src'; describe('buckets', () => { function randomBucket() { @@ -23,10 +24,10 @@ describe('buckets', () => { } before(function () { - this.client = getClient(); + this.client = new Client('http://127.0.0.1', 'token'); this.options = { reqheaders: { - authorization: `Bearer ${getToken()}` + authorization: `Bearer ${this.client.token as string}` } }; }); @@ -34,7 +35,7 @@ describe('buckets', () => { it('should get bucket', async function () { const response = randomBucket(); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .get(`/buckets/${response.id}`) .reply(200, response); @@ -66,7 +67,7 @@ describe('buckets', () => { } }; - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .get('/buckets') .query(params) .reply(200, response); @@ -87,7 +88,7 @@ describe('buckets', () => { it('should create bucket', async function () { const response = randomBucket(); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .post('/buckets') .reply(201, response); @@ -108,7 +109,7 @@ describe('buckets', () => { closed: response.closed }; - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .patch(`/buckets/${response.id}`, body) .reply(200, response); @@ -121,7 +122,7 @@ describe('buckets', () => { it('should delete bucket', async function () { const id = randomObjectId(); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .delete(`/buckets/${id}`) .reply(204); diff --git a/spec/object.spec.ts b/spec/object.spec.ts index 7a03823..50c11fa 100644 --- a/spec/object.spec.ts +++ b/spec/object.spec.ts @@ -5,8 +5,8 @@ import { describe, it, before } from 'mocha'; import { Readable } from 'stream'; import FormData from 'form-data'; -import { baseUri, randomObjectId, getClient, getToken, matchFormData } from './utils'; -import { metaToHeaders } from '../src'; +import { randomObjectId, matchFormData } from './utils'; +import { Client, metaToHeaders } from '../src'; describe('objects', function () { interface RandomCreateObjectParams { @@ -38,10 +38,10 @@ describe('objects', function () { } before(function () { - this.client = getClient(); + this.client = new Client('http://127.0.0.1', 'token'); this.authorization = { reqheaders: { - authorization: `Bearer ${getToken()}` + authorization: `Bearer ${this.client.token as string}` } }; }); @@ -57,7 +57,7 @@ describe('objects', function () { 'field-2': '123' }; - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .defaultReplyHeaders({ 'content-type': contentType, 'content-length': contentLength.toString(), @@ -87,7 +87,7 @@ describe('objects', function () { 'field-2': '123' }; - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .defaultReplyHeaders({ 'content-type': contentType, 'content-length': contentLength.toString(), @@ -115,7 +115,7 @@ describe('objects', function () { 'field-2': '123' }; - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .defaultReplyHeaders({ 'content-type': contentType, 'content-length': contentLength.toString(), @@ -148,7 +148,7 @@ describe('objects', function () { const data = 'test123'; const response = randomCreateObjectResponse(data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', response.contentType) .matchHeader('content-length', response.contentLength.toString()) .post('/objects', data) @@ -169,7 +169,7 @@ describe('objects', function () { } }); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', response.contentType) .matchHeader('content-length', response.contentLength.toString()) .matchHeader('x-meta-field1', response.metadata.field1) @@ -190,7 +190,7 @@ describe('objects', function () { const bucket = randomObjectId(); const response = randomCreateObjectResponse(data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', response.contentType) .matchHeader('content-length', response.contentLength.toString()) .matchHeader('x-meta-bucket', bucket) @@ -207,7 +207,7 @@ describe('objects', function () { const data = 'test123'; const response = randomCreateObjectResponse(data, { contentType: 'text/plain' }); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', response.contentType) .matchHeader('content-length', response.contentLength.toString()) .post('/objects', data) @@ -225,7 +225,7 @@ describe('objects', function () { const data = randomBytes(1024); const response = randomCreateObjectResponse(data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', response.contentType) .matchHeader('content-length', response.contentLength.toString()) .post('/objects', data) @@ -242,7 +242,7 @@ describe('objects', function () { const stream = Readable.from([data]); const response = randomCreateObjectResponse(data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', response.contentType) .post('/objects', data) .reply(201, response); @@ -262,7 +262,7 @@ describe('objects', function () { const formData = new FormData(); formData.append('data', data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .post('/objects', body => matchFormData(body, 'data', data)) @@ -286,7 +286,7 @@ describe('objects', function () { const formData = new FormData(); formData.append('data', data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .matchHeader('x-meta-field1', response.metadata.field1) .matchHeader('x-meta-field-2', response.metadata['field-2']) @@ -310,7 +310,7 @@ describe('objects', function () { const formData = new FormData(); formData.append('data', data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .post('/objects', body => matchFormData(body, 'data', data)) @@ -329,7 +329,7 @@ describe('objects', function () { const formData = new FormData(); formData.append('data', data, { contentType: response.contentType }); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .post('/objects', body => matchFormData(body, 'data', data, response.contentType)) @@ -348,7 +348,7 @@ describe('objects', function () { const formData = new FormData(); formData.append('data', data); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .post('/objects', body => matchFormData(body, 'data', data, 'application/octet-stream')) @@ -368,7 +368,7 @@ describe('objects', function () { const formData = new FormData(); formData.append('data', stream); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .post('/objects', body => matchFormData(body, 'data', data, 'application/octet-stream')) @@ -396,7 +396,7 @@ describe('objects', function () { formData.append('data', data2); formData.append('data', stream); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) .post('/objects', body => matchFormData(body, 'data', data1) && @@ -416,7 +416,7 @@ describe('objects', function () { it('should delete objects', async function () { const id = randomObjectId(); - const scope = nock(baseUri, this.options) + const scope = nock(this.client.baseUri, this.options) .delete(`/objects/${id}`) .reply(204); diff --git a/spec/utils.ts b/spec/utils.ts index c2ac352..ac225a6 100644 --- a/spec/utils.ts +++ b/spec/utils.ts @@ -1,34 +1,9 @@ import { randomBytes } from 'crypto'; -import { sign } from 'jsonwebtoken'; - -import { Client } from '../src'; - -export const baseUri = 'http://127.0.0.1'; - -export const jwtPayload = { - tenantId: 'tenant-id', - contractId: 'contract-id', - workspaceId: 'workspace-id', - flowId: 'flow-id', - userId: 'user-id' -}; - -export const jwtSecret = 'secret'; export function randomObjectId(): string { return randomBytes(16).toString('hex'); } -export function getClient(): Client { - const client = new Client(baseUri); - client.sign(jwtPayload, jwtSecret); - return client; -} - -export function getToken(): string { - return sign(jwtPayload, jwtSecret); -} - export function matchFormData(body: string, name: string, data?: string | Buffer, contentType?: string): boolean { // nock returns hex-encoded body for binary responses const rawBody = Buffer.from(body, 'hex').toString() || body; diff --git a/src/client.ts b/src/client.ts index 6b0bc70..3a28775 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,41 +1,33 @@ import qs from 'qs'; -import { sign } from 'jsonwebtoken'; import axios, { AxiosInstance } from 'axios'; import pkg from '../package.json'; import BucketRepository from './bucket'; import ObjectRepository from './object'; -export interface JwtPayload { - tenantId: string; - contractId: string; - workspaceId: string; - flowId: string; - userId: string; -} - export class Client { private readonly client: AxiosInstance; public readonly version = pkg.version; + public readonly baseUri: string; + public readonly token: string; + public readonly buckets: BucketRepository; public readonly objects: ObjectRepository; - constructor(baseUri: string) { + constructor(baseUri: string, token: string) { + this.baseUri = baseUri; + this.token = token; + this.client = axios.create({ - baseURL: baseUri, + baseURL: this.baseUri, paramsSerializer: params => qs.stringify(params), headers: { + Authorization: `Bearer ${this.token}`, 'User-Agent': `maester-client/${this.version}` } }); this.buckets = new BucketRepository(this.client); this.objects = new ObjectRepository(this.client); } - - public sign(jwtPayload: JwtPayload, jwtSecret: string): Client { - const jwtToken = sign(jwtPayload, jwtSecret); - this.client.defaults.headers.common.authorization = `Bearer ${jwtToken}`; - return this; - } } From cac4e57224f6b7d16764015b9e363c46490c4a5e Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 16:36:34 +0300 Subject: [PATCH 3/9] get rid of FormData --- README.md | 25 ++-- spec/object.spec.ts | 354 ++++++++++++++------------------------------ src/object.ts | 47 ++++-- 3 files changed, 163 insertions(+), 263 deletions(-) diff --git a/README.md b/README.md index dae8cd9..2c84138 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Create object: const response = await client.objects.create(data); ``` -where `data` can be `string`, `Buffer`, `FormData` or `Stream` +Where `data` can be `string`, `Buffer`, `Stream` or array of these values. Create object with metadata: @@ -103,10 +103,11 @@ const response = await client.objects.create(data, { }); ``` -Create object and override content type: +Create object and override its content type: ``` -const response = await client.objects.create(data, { +const response = await client.objects.create({ + data: 'hello world', contentType: 'text/plain' }); ``` @@ -114,13 +115,17 @@ const response = await client.objects.create(data, { Create multiple objects at once: ``` -const FormData = require('form-data'); - -const data = new FormData(); -data.append('data', 'hello world', { contentType: 'text/plain' }); -data.append('data', JSON.stringify(json), { contentType: 'application/json' }); -data.append('data', fs.createReadStream('/foo/bar.jpg')) -data.append('data', Buffer.allocUnsafe(1024)); +const data = [ + { + data: 'hello world' + }, + { + data: JSON.stringify(json), + contentType: 'application/json' + }, + data: fs.createReadStream('/foo/bar.jpg'), + Buffer.allocUnsafe(1024) +]; const response = await client.objects.create(data, { bucket: 'bucket-id', diff --git a/spec/object.spec.ts b/spec/object.spec.ts index 50c11fa..a0a295c 100644 --- a/spec/object.spec.ts +++ b/spec/object.spec.ts @@ -3,7 +3,6 @@ import { createHash, randomBytes } from 'crypto'; import { expect } from 'chai'; import { describe, it, before } from 'mocha'; import { Readable } from 'stream'; -import FormData from 'form-data'; import { randomObjectId, matchFormData } from './utils'; import { Client, metaToHeaders } from '../src'; @@ -143,273 +142,150 @@ describe('objects', function () { }); describe('create object', function () { - describe('ordinary', function () { - it('should create object from text', async function () { - const data = 'test123'; - const response = randomCreateObjectResponse(data); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', response.contentType) - .matchHeader('content-length', response.contentLength.toString()) - .post('/objects', data) - .reply(201, response); - - const object = await this.client.objects.create(data); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); - - it('should create object with metadata', async function () { - const data = 'test123'; - const response = randomCreateObjectResponse(data, { - metadata: { - field1: 'test', - 'field-2': '123' - } - }); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', response.contentType) - .matchHeader('content-length', response.contentLength.toString()) - .matchHeader('x-meta-field1', response.metadata.field1) - .matchHeader('x-meta-field-2', response.metadata['field-2']) - .post('/objects', data) - .reply(201, response); - - const object = await this.client.objects.create(data, { - metadata: response.metadata - }); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); - - it('should create object and attach it bucket', async function () { - const data = 'test123'; - const bucket = randomObjectId(); - const response = randomCreateObjectResponse(data); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', response.contentType) - .matchHeader('content-length', response.contentLength.toString()) - .matchHeader('x-meta-bucket', bucket) - .post('/objects', data) - .reply(201, response); - - const object = await this.client.objects.create(data, { bucket }); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); + it('should create object from text', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data); - it('should create object and override content type', async function () { - const data = 'test123'; - const response = randomCreateObjectResponse(data, { contentType: 'text/plain' }); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType)) + .reply(201, response); - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', response.contentType) - .matchHeader('content-length', response.contentLength.toString()) - .post('/objects', data) - .reply(201, response); + const object = await this.client.objects.create(data); - const object = await this.client.objects.create(data, { - contentType: response.contentType - }); + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); + it('should create object with metadata', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data, { + metadata: { + field1: 'test', + 'field-2': '123' + } }); - it('should create object from buffer', async function () { - const data = randomBytes(1024); - const response = randomCreateObjectResponse(data); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', response.contentType) - .matchHeader('content-length', response.contentLength.toString()) - .post('/objects', data) - .reply(201, response); - - const object = await this.client.objects.create(data); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType) && + matchFormData(body, 'field1', response.metadata.field1) && + matchFormData(body, 'field-2', response.metadata['field-2'])) + .reply(201, response); + + const object = await this.client.objects.create(data, { + metadata: response.metadata }); - it('should create object from stream', async function () { - const data = randomBytes(1024); - const stream = Readable.from([data]); - const response = randomCreateObjectResponse(data); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', response.contentType) - .post('/objects', data) - .reply(201, response); - - const object = await this.client.objects.create(stream); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); }); - describe('multipart/form-data', function () { - it('should create object from text', async function () { - const data = 'test123'; - const response = randomCreateObjectResponse(data); - - const formData = new FormData(); - formData.append('data', data); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .post('/objects', body => - matchFormData(body, 'data', data)) - .reply(201, response); - - const object = await this.client.objects.create(formData); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); - - it('should create object with metadata', async function () { - const data = 'test123'; - const response = randomCreateObjectResponse(data, { - metadata: { - field1: 'test', - 'field-2': '123' - } - }); - - const formData = new FormData(); - formData.append('data', data); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .matchHeader('x-meta-field1', response.metadata.field1) - .matchHeader('x-meta-field-2', response.metadata['field-2']) - .post('/objects', body => - matchFormData(body, 'data', data)) - .reply(201, response); - - const object = await this.client.objects.create(formData, { - metadata: response.metadata - }); - - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); + it('should create object and attach it bucket', async function () { + const data = 'test123'; + const bucket = randomObjectId(); + const response = randomCreateObjectResponse(data); - it('should create object and attach it bucket', async function () { - const data = 'test123'; - const bucket = randomObjectId(); - const response = randomCreateObjectResponse(data); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType) && + matchFormData(body, 'bucket', bucket)) + .reply(201, response); - const formData = new FormData(); - formData.append('data', data); + const object = await this.client.objects.create(data, { bucket }); - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .post('/objects', body => - matchFormData(body, 'data', data)) - .reply(201, response); + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); - const object = await this.client.objects.create(formData, { bucket }); + it('should create object and override content type', async function () { + const data = 'test123'; + const response = randomCreateObjectResponse(data, { contentType: 'text/plain' }); - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType)) + .reply(201, response); + + const object = await this.client.objects.create({ + data, + contentType: response.contentType }); - it('should create object and override content type', async function () { - const data = 'test123'; - const response = randomCreateObjectResponse(data, { contentType: 'text/plain' }); - - const formData = new FormData(); - formData.append('data', data, { contentType: response.contentType }); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .post('/objects', body => - matchFormData(body, 'data', data, response.contentType)) - .reply(201, response); - - const object = await this.client.objects.create(formData); + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); + it('should create object from buffer', async function () { + const data = randomBytes(1024); + const response = randomCreateObjectResponse(data); - it('should create object from buffer', async function () { - const data = randomBytes(1024); - const response = randomCreateObjectResponse(data); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType)) + .reply(201, response); - const formData = new FormData(); - formData.append('data', data); + const object = await this.client.objects.create(data); - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .post('/objects', body => - matchFormData(body, 'data', data, 'application/octet-stream')) - .reply(201, response); + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); - const object = await this.client.objects.create(formData); + it('should create object from stream', async function () { + const data = randomBytes(1024); + const stream = Readable.from([data]); + const response = randomCreateObjectResponse(data); - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, response.contentType)) + .reply(201, response); - it('should create object from stream', async function () { - const data = randomBytes(1024); - const stream = Readable.from([data]); - const response = randomCreateObjectResponse(data); + const object = await this.client.objects.create(stream); - const formData = new FormData(); - formData.append('data', stream); + expect(scope.isDone()).to.be.true; + expect(object).to.deep.equal(transformResponse(response)); + }); - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .post('/objects', body => - matchFormData(body, 'data', data, 'application/octet-stream')) - .reply(201, response); + it('should create multiple objects', async function () { + const data1 = 'test123'; + const data2 = randomBytes(1024); + const data3 = randomBytes(1024); + const stream = Readable.from([data3]); + const response = [ + randomCreateObjectResponse(data1, { contentType: 'text/plain' }), + randomCreateObjectResponse(data2), + randomCreateObjectResponse(data3) + ]; + + const data = [ + { + data: data1, + contentType: response[0].contentType + }, + data2, + stream + ]; - const object = await this.client.objects.create(formData); + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data1, response[0].contentType) && + matchFormData(body, 'data', data2, response[1].contentType) && + matchFormData(body, 'data', data3, response[2].contentType) + ) + .reply(201, response); - expect(scope.isDone()).to.be.true; - expect(object).to.deep.equal(transformResponse(response)); - }); + const objects = await this.client.objects.create(data); - it('should create multiple objects', async function () { - const data1 = 'test123'; - const data2 = randomBytes(1024); - const data3 = randomBytes(1024); - const stream = Readable.from([data3]); - const response = [ - randomCreateObjectResponse(data1), - randomCreateObjectResponse(data2), - randomCreateObjectResponse(data3) - ]; - - const formData = new FormData(); - formData.append('data', data1); - formData.append('data', data2); - formData.append('data', stream); - - const scope = nock(this.client.baseUri, this.options) - .matchHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`) - .post('/objects', body => - matchFormData(body, 'data', data1) && - matchFormData(body, 'data', data2, 'application/octet-stream') && - matchFormData(body, 'data', data3, 'application/octet-stream') - ) - .reply(201, response); - - const objects = await this.client.objects.create(formData); - - expect(scope.isDone()).to.be.true; - expect(objects).to.deep.equal(response.map(transformResponse)); - }); + expect(scope.isDone()).to.be.true; + expect(objects).to.deep.equal(response.map(transformResponse)); }); }); diff --git a/src/object.ts b/src/object.ts index 6b25802..b8ef703 100644 --- a/src/object.ts +++ b/src/object.ts @@ -3,9 +3,20 @@ import FormData from 'form-data'; import { AxiosInstance, AxiosResponse, ResponseType } from 'axios'; export const USER_META_HEADER_PREFIX = 'x-meta-'; +export const DEFAULT_CONTENT_TYPE = 'application/octet-stream'; +export type PlainOrArray = T | T[]; export type ObjectMetadata = Record; +export interface ObjectData { + data: string | Buffer | Stream; + contentType?: 'string'; +} + +export function isObjectData(data: any): data is ObjectData { + return typeof data === 'object' && 'data' in data; +} + export function metaToHeaders(meta: ObjectMetadata): Record { const headers: Record = {}; for (const [key, value] of Object.entries(meta)) { @@ -76,28 +87,36 @@ export default class ObjectRepository { .then((res: AxiosResponse) => new GetObjectResponse(res)); } - public create(data: string | Buffer | FormData | Stream, - params?: CreateObjectParams): Promise { - const { contentType, bucket, metadata } = params ?? {}; - const userMetadata = metadata ?? {}; + public create(data: PlainOrArray, + params?: CreateObjectParams): Promise> { + const { bucket, metadata } = params ?? {}; + + const dataArray = Array.isArray(data) ? data : [data]; + const formData = new FormData(); + + for (const item of dataArray) { + const [data, contentType] = isObjectData(item) + ? [item.data, item.contentType] + : [item, DEFAULT_CONTENT_TYPE]; + + formData.append('data', data, { contentType }); + } if (bucket) { - if (data instanceof FormData) { - data.append('bucket', bucket); - } else { - userMetadata.bucket = bucket; + formData.append('bucket', bucket); + } + + if (metadata) { + for (const [key, value] of Object.entries(metadata)) { + formData.append(key, value); } } const headers = { - ...metaToHeaders(userMetadata) + 'content-type': `multipart/form-data; boundary=${formData.getBoundary()}` }; - headers['content-type'] = data instanceof FormData - ? `multipart/form-data; boundary=${data.getBoundary()}` - : contentType ?? 'application/octet-stream'; - - return this.client.post('/objects', data, { headers }) + return this.client.post('/objects', formData, { headers }) .then((res: AxiosResponse) => Array.isArray(res.data) ? res.data.map(data => new CreateObjectResponse(data)) : new CreateObjectResponse(res.data)); From 0b05d4ad791d4623c77e4dc45ae84074057fad8b Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 16:54:44 +0300 Subject: [PATCH 4/9] add get object methods --- README.md | 18 +++++++++++- spec/object.spec.ts | 70 ++++++++++++++++++++++++++++++++++----------- src/object.ts | 12 ++++++++ 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2c84138..b16a66b 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,26 @@ const object = await client.objects.get(id); console.log(object.data); ``` +Object's property `data` has value of type `string`, `object`, `Buffer` or `Stream`. + +Get object as JSON: + +``` +const object = await client.objects.getJSON(id); +console.log(object.data); +``` + +Get object as buffer: + +``` +const object = await client.objects.getBuffer(id); +console.log(object.data.toString()) +``` + Get object as stream: ``` -const object = await client.objects.get(id, 'stream'); +const object = await client.objects.getStream(id); object.data.pipe(...) ``` diff --git a/spec/object.spec.ts b/spec/object.spec.ts index a0a295c..5f22467 100644 --- a/spec/object.spec.ts +++ b/spec/object.spec.ts @@ -46,7 +46,30 @@ describe('objects', function () { }); describe('get object', function () { - it('should get object as text', async function () { + it('should get object', async function () { + const id = randomObjectId(); + const data = 'test123'; + const contentType = 'text/plain'; + const contentLength = data.length; + + const scope = nock(this.client.baseUri, this.options) + .defaultReplyHeaders({ + 'content-type': contentType, + 'content-length': contentLength.toString() + }) + .get(`/objects/${id}`) + .reply(200, data); + + const response = await this.client.objects.get(id); + + expect(scope.isDone()).to.be.true; + expect(response.contentType).to.equal(contentType); + expect(response.contentLength).to.equal(contentLength); + expect(response.metadata).to.deep.equal({}); + expect(response.data).to.equal(data); + }); + + it('should get object with metadata', async function () { const id = randomObjectId(); const data = 'test123'; const contentType = 'text/plain'; @@ -81,54 +104,67 @@ describe('objects', function () { }; const contentType = 'application/json'; const contentLength = JSON.stringify(data).length; - const metadata = { - field1: 'test', - 'field-2': '123' - }; const scope = nock(this.client.baseUri, this.options) .defaultReplyHeaders({ 'content-type': contentType, - 'content-length': contentLength.toString(), - ...metaToHeaders(metadata) + 'content-length': contentLength.toString() }) .get(`/objects/${id}`) .reply(200, data); - const response = await this.client.objects.get(id); + const response = await this.client.objects.getJSON(id); expect(scope.isDone()).to.be.true; expect(response.contentType).to.equal(contentType); expect(response.contentLength).to.equal(contentLength); - expect(response.metadata).to.deep.equal(metadata); + expect(response.metadata).to.deep.equal({}); expect(response.data).to.deep.equal(data); }); + it('should get object as buffer', async function () { + const id = randomObjectId(); + const data = 'test123'; + const contentType = 'application/octet-stream'; + const contentLength = data.length; + + const scope = nock(this.client.baseUri, this.options) + .defaultReplyHeaders({ + 'content-type': contentType, + 'content-length': contentLength.toString() + }) + .get(`/objects/${id}`) + .reply(200, data); + + const response = await this.client.objects.getBuffer(id); + + expect(scope.isDone()).to.be.true; + expect(response.contentType).to.equal(contentType); + expect(response.contentLength).to.equal(contentLength); + expect(response.metadata).to.deep.equal({}); + expect(response.data.toString()).to.equal(data); + }); + it('should get object as stream', async function () { const id = randomObjectId(); const data = randomBytes(1024); const contentType = 'application/octet-stream'; const contentLength = data.length; - const metadata = { - field1: 'test', - 'field-2': '123' - }; const scope = nock(this.client.baseUri, this.options) .defaultReplyHeaders({ 'content-type': contentType, - 'content-length': contentLength.toString(), - ...metaToHeaders(metadata) + 'content-length': contentLength.toString() }) .get(`/objects/${id}`) .reply(200, data); - const response = await this.client.objects.get(id, 'stream'); + const response = await this.client.objects.getStream(id); expect(scope.isDone()).to.be.true; expect(response.contentType).to.equal(contentType); expect(response.contentLength).to.equal(contentLength); - expect(response.metadata).to.deep.equal(metadata); + expect(response.metadata).to.deep.equal({}); const buffer: Buffer = await new Promise((resolve, reject) => { const chunks: Buffer[] = []; diff --git a/src/object.ts b/src/object.ts index b8ef703..01aac9c 100644 --- a/src/object.ts +++ b/src/object.ts @@ -87,6 +87,18 @@ export default class ObjectRepository { .then((res: AxiosResponse) => new GetObjectResponse(res)); } + public getJSON(id: string): Promise { + return this.get(id, 'json'); + } + + public getBuffer(id: string): Promise { + return this.get(id, 'arraybuffer'); + } + + public getStream(id: string): Promise { + return this.get(id, 'stream'); + } + public create(data: PlainOrArray, params?: CreateObjectParams): Promise> { const { bucket, metadata } = params ?? {}; From a9f7120b9ddee8cb93447be4c2f49387be7e17aa Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 18:26:08 +0300 Subject: [PATCH 5/9] streams --- README.md | 12 ++++++++++++ spec/object.spec.ts | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/object.ts | 14 +++++++++++-- 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b16a66b..d2d9e84 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ const object = await client.objects.getStream(id); object.data.pipe(...) ``` +Readable stream example: + +``` +client.objects.getReadStream(id).pipe(fs.createWriteStream('/foo/bar.jpg')); +``` + Create object: ``` @@ -151,6 +157,12 @@ const response = await client.objects.create(data, { }); ``` +Writable stream example: + +``` +fs.createReadStream('/foo/bar.jpg').pipe(client.objects.createWriteStream()); +``` + Delete object: ``` diff --git a/spec/object.spec.ts b/spec/object.spec.ts index 5f22467..0ba0941 100644 --- a/spec/object.spec.ts +++ b/spec/object.spec.ts @@ -175,6 +175,34 @@ describe('objects', function () { expect(buffer.toString()).to.equal(data.toString()); }); + + it('should get read stream', async function () { + const id = randomObjectId(); + const data = randomBytes(1024); + const contentType = 'application/octet-stream'; + const contentLength = data.length; + + const scope = nock(this.client.baseUri, this.options) + .defaultReplyHeaders({ + 'content-type': contentType, + 'content-length': contentLength.toString() + }) + .get(`/objects/${id}`) + .reply(200, data); + + const stream = await this.client.objects.getReadStream(id); + + expect(scope.isDone()).to.be.true; + + const buffer: Buffer = await new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + stream.on('data', (chunk: Buffer) => chunks.push(chunk)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(chunks))) + }); + + expect(buffer.toString()).to.equal(data.toString()); + }); }); describe('create object', function () { @@ -323,6 +351,26 @@ describe('objects', function () { expect(scope.isDone()).to.be.true; expect(objects).to.deep.equal(response.map(transformResponse)); }); + + it('should create write stream', async function () { + const data = randomBytes(1024); + const stream = Readable.from([data]); + + const scope = nock(this.client.baseUri, this.options) + .matchHeader('content-type', /multipart\/form-data/) + .post('/objects', body => + matchFormData(body, 'data', data, 'application/octet-stream')) + .reply(201); + + const writeable = stream.pipe(this.client.objects.createWriteStream()); + + await new Promise((resolve, reject) => { + writeable.on('error', reject); + writeable.on('finish', resolve); + }); + + expect(scope.isDone()).to.be.true; + }); }); it('should delete objects', async function () { diff --git a/src/object.ts b/src/object.ts index 01aac9c..eaee8a7 100644 --- a/src/object.ts +++ b/src/object.ts @@ -1,5 +1,5 @@ -import Stream from 'stream'; import FormData from 'form-data'; +import { Stream, Readable, Writable, PassThrough } from 'stream'; import { AxiosInstance, AxiosResponse, ResponseType } from 'axios'; export const USER_META_HEADER_PREFIX = 'x-meta-'; @@ -99,7 +99,11 @@ export default class ObjectRepository { return this.get(id, 'stream'); } - public create(data: PlainOrArray, + public getReadStream(id: string): Promise { + return this.getStream(id).then(res => res.data as Readable); + } + + public create(data: PlainOrArray, params?: CreateObjectParams): Promise> { const { bucket, metadata } = params ?? {}; @@ -134,6 +138,12 @@ export default class ObjectRepository { : new CreateObjectResponse(res.data)); } + public createWriteStream(params?: CreateObjectParams): Writable { + const stream = new PassThrough(); + this.create(stream, params).catch(err => stream.destroy(err)); + return stream; + } + public delete(id: string): Promise { return this.client.delete(`/objects/${id}`); } From e66f5add2660efaecc37130a9c34a5f0b70b19f5 Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 18:41:26 +0300 Subject: [PATCH 6/9] streams --- .eslintignore | 2 +- README.md | 4 ++-- spec/object.spec.ts | 7 +++---- src/object.ts | 8 ++++++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.eslintignore b/.eslintignore index 0a95885..793cb00 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ /dist -node_modules \ No newline at end of file +node_modules diff --git a/README.md b/README.md index d2d9e84..084d3c9 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,10 @@ const object = await client.objects.getStream(id); object.data.pipe(...) ``` -Readable stream example: +Create read stream example: ``` -client.objects.getReadStream(id).pipe(fs.createWriteStream('/foo/bar.jpg')); +client.objects.createReadStream(id).pipe(fs.createWriteStream('/foo/bar.jpg')); ``` Create object: diff --git a/spec/object.spec.ts b/spec/object.spec.ts index 0ba0941..6ca9cb1 100644 --- a/spec/object.spec.ts +++ b/spec/object.spec.ts @@ -176,7 +176,7 @@ describe('objects', function () { expect(buffer.toString()).to.equal(data.toString()); }); - it('should get read stream', async function () { + it('should create read stream', async function () { const id = randomObjectId(); const data = randomBytes(1024); const contentType = 'application/octet-stream'; @@ -190,9 +190,7 @@ describe('objects', function () { .get(`/objects/${id}`) .reply(200, data); - const stream = await this.client.objects.getReadStream(id); - - expect(scope.isDone()).to.be.true; + const stream = this.client.objects.createReadStream(id); const buffer: Buffer = await new Promise((resolve, reject) => { const chunks: Buffer[] = []; @@ -201,6 +199,7 @@ describe('objects', function () { stream.on('end', () => resolve(Buffer.concat(chunks))) }); + expect(scope.isDone()).to.be.true; expect(buffer.toString()).to.equal(data.toString()); }); }); diff --git a/src/object.ts b/src/object.ts index eaee8a7..8256e5f 100644 --- a/src/object.ts +++ b/src/object.ts @@ -99,8 +99,12 @@ export default class ObjectRepository { return this.get(id, 'stream'); } - public getReadStream(id: string): Promise { - return this.getStream(id).then(res => res.data as Readable); + public createReadStream(id: string): Readable { + const pass = new PassThrough(); + this.getStream(id) + .then(res => (res.data as Readable).pipe(pass)) + .catch(err => pass.destroy(err)); + return pass; } public create(data: PlainOrArray, From c75805e2cada9ab09483cd0b9152452203889107 Mon Sep 17 00:00:00 2001 From: andkom Date: Thu, 16 Apr 2020 18:59:23 +0300 Subject: [PATCH 7/9] fixes --- README.md | 2 +- spec/object.spec.ts | 2 +- src/bucket.ts | 14 +++++++++++--- src/object.ts | 25 +++++++++++++++---------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 084d3c9..c459c45 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ const data = [ data: JSON.stringify(json), contentType: 'application/json' }, - data: fs.createReadStream('/foo/bar.jpg'), + fs.createReadStream('/foo/bar.jpg'), Buffer.allocUnsafe(1024) ]; diff --git a/spec/object.spec.ts b/spec/object.spec.ts index 6ca9cb1..9998e92 100644 --- a/spec/object.spec.ts +++ b/spec/object.spec.ts @@ -113,7 +113,7 @@ describe('objects', function () { .get(`/objects/${id}`) .reply(200, data); - const response = await this.client.objects.getJSON(id); + const response = await this.client.objects.get(id); expect(scope.isDone()).to.be.true; expect(response.contentType).to.equal(contentType); diff --git a/src/bucket.ts b/src/bucket.ts index f87f1b9..b2e53dc 100644 --- a/src/bucket.ts +++ b/src/bucket.ts @@ -1,19 +1,27 @@ import { AxiosInstance, AxiosResponse } from 'axios'; +export interface BucketData { + id?: string; + objects?: string[]; + externalId?: string | null; + closed?: boolean; + createdAt?: string; +} + export class Bucket { public readonly id: string; public readonly objects: string[]; public readonly externalId: string | null; public readonly closed: boolean; - public readonly createdAt: Date; + public readonly createdAt: Date | null; - constructor(data: any) { + constructor(data: BucketData) { const { id, objects, externalId, closed, createdAt } = data; this.id = id ?? ''; this.objects = objects ?? []; this.externalId = externalId ?? null; this.closed = closed ?? false; - this.createdAt = new Date(createdAt); + this.createdAt = createdAt ? new Date(createdAt) : null; } } diff --git a/src/object.ts b/src/object.ts index 8256e5f..32490a6 100644 --- a/src/object.ts +++ b/src/object.ts @@ -1,5 +1,5 @@ import FormData from 'form-data'; -import { Stream, Readable, Writable, PassThrough } from 'stream'; +import { Readable, Writable, PassThrough } from 'stream'; import { AxiosInstance, AxiosResponse, ResponseType } from 'axios'; export const USER_META_HEADER_PREFIX = 'x-meta-'; @@ -9,7 +9,7 @@ export type PlainOrArray = T | T[]; export type ObjectMetadata = Record; export interface ObjectData { - data: string | Buffer | Stream; + data: string | Buffer | Readable; contentType?: 'string'; } @@ -39,7 +39,7 @@ export class GetObjectResponse { public readonly contentType: string; public readonly contentLength: number; public readonly metadata: ObjectMetadata; - public readonly data: any; + public readonly data: object | string | Buffer | Readable; constructor(res: AxiosResponse) { const { headers, data } = res; @@ -56,21 +56,30 @@ export interface CreateObjectParams { metadata?: ObjectMetadata; } +export interface CreateObjectResponseData { + objectId?: string; + contentType?: string; + contentLength?: number; + md5?: string; + createdAt?: string; + metadata?: ObjectMetadata; +} + export class CreateObjectResponse { public readonly id: string; public readonly contentType: string; public readonly contentLength: number; public readonly md5: string; - public readonly createdAt: Date; + public readonly createdAt: Date | null; public readonly metadata: ObjectMetadata; - constructor(data: any) { + constructor(data: CreateObjectResponseData) { const { objectId, contentType, contentLength, md5, createdAt, metadata } = data; this.id = objectId ?? ''; this.contentType = contentType ?? ''; this.contentLength = contentLength ?? 0; this.md5 = md5 ?? ''; - this.createdAt = new Date(createdAt); + this.createdAt = createdAt ? new Date(createdAt) : null; this.metadata = metadata ?? {}; } } @@ -87,10 +96,6 @@ export default class ObjectRepository { .then((res: AxiosResponse) => new GetObjectResponse(res)); } - public getJSON(id: string): Promise { - return this.get(id, 'json'); - } - public getBuffer(id: string): Promise { return this.get(id, 'arraybuffer'); } From 1d0c314fd9ffc74ded74d424697b1d1dca9dfaab Mon Sep 17 00:00:00 2001 From: andkom Date: Fri, 17 Apr 2020 10:53:14 +0300 Subject: [PATCH 8/9] fix package name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dcfcec..8375142 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@elasticio/maester-client", + "name": "@elastic.io/maester-client", "version": "1.0.0", "description": "The official object-storage client for sailor-nodejs.", "main": "dist/src/index.js", From 89883be28118be0e57ac9e43aaeae5259f4cd25d Mon Sep 17 00:00:00 2001 From: andkom Date: Fri, 17 Apr 2020 11:58:30 +0300 Subject: [PATCH 9/9] fix package --- .npmignore | 3 +++ tsconfig.json | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.npmignore b/.npmignore index 9ec9b31..d007303 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,6 @@ .idea /src +/dist/spec +.eslintignore +.eslintrc.json tsconfig.json diff --git a/tsconfig.json b/tsconfig.json index 098a033..80986b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,14 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2017", + "target": "ES2019", "outDir": "./dist", "strict": true, - "sourceMap": true, "declaration": true, "esModuleInterop": true, "resolveJsonModule": true, "lib": [ - "es2018", - "dom" + "ES2019" ] }, "include": [