From 3e36b3091f630b8cafe2af3bdef4dbfc770928e1 Mon Sep 17 00:00:00 2001 From: Bob den Os Date: Wed, 26 Mar 2025 14:48:42 +0100 Subject: [PATCH 1/2] Create performance measurement APIs --- lib/cds-test.js | 1 + lib/perf.js | 196 ++++++++++++++++++++++++++ package.json | 1 + test/app/test/sample-bookshop.test.js | 6 +- test/cds-test.test.js | 21 +++ 5 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 lib/perf.js diff --git a/lib/cds-test.js b/lib/cds-test.js index 9f683fd..9bfbe88 100644 --- a/lib/cds-test.js +++ b/lib/cds-test.js @@ -11,6 +11,7 @@ class Test extends require('./axios') { get cds() { return require('@sap/cds') } get sleep() { return super.sleep = require('util').promisify(setTimeout) } get data() { return super.data = new (require('./data'))} + get perf() { return super.perf = new (require('./perf'))(this)} /** * Launches a cds server with arbitrary port and returns a subclass which diff --git a/lib/perf.js b/lib/perf.js new file mode 100644 index 0000000..14c08cf --- /dev/null +++ b/lib/perf.js @@ -0,0 +1,196 @@ +const cds = require('@sap/cds') +const { LIGHT_GRAY: GREEN, DIMMED, RESET } = require('@sap/cds/lib/utils/colors') + +const DEFAULTS = { + warmup: { + duration: '1s', + }, + duration: '3s', + connections: 3, +} + +class Performance { + constructor(test) { + this._test = test + this._reports = 0 + } + + get autocannon() { + const autocannon = require('autocannon') + super._histUtil = require('autocannon/lib/histUtil') + super._aggregateResult = require('autocannon/lib/aggregateResult') + super._timestring = require('timestring') + return super.autocannon = autocannon + } + fn(..._) { return this._run(this._args('FN', _)) } + get(..._) { return this.autocannon(this._args('GET', _)) } + put(..._) { return this.autocannon(...this._args('PUT', _)) } + post(..._) { return this.autocannon(...this._args('POST', _)) } + patch(..._) { return this.autocannon(...this._args('PATCH', _)) } + delete(..._) { return this.autocannon(...this._args('DELETE', _)) } + options(..._) { return this.autocannon(...this._args('OPTIONS', _)) } + + /** @type typeof _.options */ get FN() { return this.fn.bind(this) } + /** @type typeof _.get */ get GET() { return this.get.bind(this) } + /** @type typeof _.put */ get PUT() { return this.put.bind(this) } + /** @type typeof _.post */ get POST() { return this.post.bind(this) } + /** @type typeof _.patch */ get PATCH() { return this.patch.bind(this) } + /** @type typeof _.delete */ get DELETE() { return this.delete.bind(this) } + /** @type typeof _.delete */ get DEL() { return this.delete.bind(this) } //> to avoid conflicts with cds.ql.DELETE + /** @type typeof _.options */ get OPTIONS() { return this.options.bind(this) } + + _args(METHOD, args) { + const first = args[0], last = args[args.length - 1] + if (first.raw) { + if (first[first.length - 1] === '' && typeof last === 'object') + return this._defaults(METHOD, last, { url: String.raw(...args.slice(0, -1)) }) + return this._defaults(METHOD, { url: String.raw(...args) }) + } + else if (typeof first === 'string') args[0] = { url: first } + else if (typeof first === 'function') args[0] = { fn: first, title: first.name } + else if (typeof first !== 'string') + throw new Error(`Argument path is expected to be a string or function but got ${typeof first}`) + return this._defaults(METHOD, ...args) + } + + _defaults(method = 'GET', ...opts) { + let fn + if (typeof method === 'function') fn = method + + const o = Object.assign({ fn, method }, DEFAULTS, ...opts) + if (o.url) { + o.title ??= o.url + const { auth } = this._test.axios.defaults + o.headers ??= {} + if (auth) { + o.headers.authorization = `Basic ${btoa(`${auth.username}:${auth.password}`)}` + } + const { baseURL } = this._test.axios.defaults || '' + const sep = baseURL.at(-1) !== '/' && o.url?.[0] !== '/' ? '/' : '' + o.url = /^https?:/.test(o.url) ? o.url : `${baseURL}${sep}${o.url}` + } + return o + } + + async _run(opts) { + this.autocannon + + let { fn, args } = opts + if (args) fn = fn.bind(null, ...args) + + if (opts.warmup) await this._run({ ...opts, fn, args: undefined, ...opts.warmup, warmup: undefined }) + + const { getHistograms, encodeHist } = this._histUtil + + const histograms = getHistograms(opts.histograms) + const { latencies, requests, throughput } = histograms + + const statusCodeStats = {} + + let stop = false + let count = 0 + let errors = 0 + let nextTrack + let totalRequests = 0 + let totalCompletedRequests = 0 + + const runners = new Array(opts.connections) + + const startTime = process.hrtime.bigint() + const endTime = startTime + BigInt((typeof opts.duration === 'string' ? this._timestring(opts.duration) : opts.duration) * 1e9) + + for (let r = 0; r < runners.length; r++) { + runners[r] = run() + } + await Promise.all(runners) + + const result = { + latencies: encodeHist(latencies), + requests: encodeHist(requests), + throughput: encodeHist(throughput), + totalCompletedRequests, + totalRequests, + totalBytes: 0, + samples: Math.floor(Number(process.hrtime.bigint() - startTime) / 1e9), + errors, + timeouts: 0, + mismatches: 0, + non2xx: Object.keys(statusCodeStats).reduce((l, c) => l + (c[0] === '2' ? 0 : statusCodeStats[c]), 0), + statusCodeStats, + resets: 0, + duration: Number(process.hrtime.bigint() - startTime) / 1e9, + start: new Date(Number(startTime)), + finish: new Date(), + '1xx': 0, + '2xx': statusCodeStats['200']?.count || 0, + '3xx': 0, + '4xx': 0, + '5xx': statusCodeStats['500']?.count || 0, + } + + return this._aggregateResult(result, opts, histograms) + + async function run() { + while (!stop) { + const now = process.hrtime.bigint() + if (!nextTrack) nextTrack = now + BigInt(1e9) + if (now >= nextTrack) { + nextTrack = now + BigInt(1e9) + requests.recordValue(count) + count = 0 + } + + if (now >= endTime) { + stop = true + break + } + + totalRequests++ + try { + const s = process.hrtime.bigint() + + const ret = fn() + if (ret?.then) await ret; + + const d = process.hrtime.bigint() - s + latencies.recordValue(Number(d) / 1000000) + + count++ + totalCompletedRequests++ + (statusCodeStats['200'] ??= { count: 0 }).count++ + } catch { + errors++ + (statusCodeStats['500'] ??= { count: 0 }).count++ + } + } + } + } + + async _report(result, options = {}) { + let { requests, latency, throughput, title = `${this._reports++}` } = result + + // Collect the result into a file for further processing later + if (options.store) { + result.file = cds.utils.path.relative(process.cwd(), require.main.filename) + const stack = {} + Error.captureStackTrace(stack) + result.line = /:(\d*:\d*)\)/.exec(stack.stack.split('\n').find(l => l.indexOf(result.file) > -1))?.[1] + + const benchmark = `${result.file}:${title}` + cds.utils.fs.writeFileSync(cds.utils.path.resolve(process.cwd(), 'results.bench'), `${JSON.stringify({ [benchmark]: result })}\n`, { flag: 'a' }) + } + + // TODO: determine a good default report format of the available measured information + console.log( // eslint-disable-line no-console + title.padEnd(50), + GREEN + (requests.average >>> 0).toLocaleString().padStart(5), DIMMED + 'req/s' + RESET, + GREEN + (throughput.average / 1024 / 1024 >>> 0).toLocaleString().padStart(5), DIMMED + 'MiB/s' + RESET, + GREEN + (latency.average >>> 0).toLocaleString().padStart(5), DIMMED + 'ms' + RESET, + ) + } + /** @type typeof _._report */ get report() { return this._report.bind(this) } + +} + +// ? const _ = Performance.prototype // eslint-disable-line no-unused-vars +module.exports = Performance \ No newline at end of file diff --git a/package.json b/package.json index db8f6ad..93114f9 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "test:node": "node --test" }, "dependencies": { + "autocannon": "^8.0.0", "axios": "^1", "chai": "^4.4.1", "chai-as-promised": "^7.1.1", diff --git a/test/app/test/sample-bookshop.test.js b/test/app/test/sample-bookshop.test.js index e334f7e..1941c56 100644 --- a/test/app/test/sample-bookshop.test.js +++ b/test/app/test/sample-bookshop.test.js @@ -2,11 +2,15 @@ const cds_test = require('../../../lib/cds-test') const describe = global.describe ?? require('node:test').describe describe('Sample tests', () => { - const { GET, expect } = cds_test(__dirname+'/..') + const { GET, expect, perf } = cds_test(__dirname+'/..') it('serves Books', async () => { const { data } = await GET`/odata/v4/catalog/Books` expect(data.value.length).to.be.greaterThanOrEqual(5) }) + it('measures Books', async () => { + perf.report(await perf.GET `/odata/v4/catalog/Books`) + }) + }) \ No newline at end of file diff --git a/test/cds-test.test.js b/test/cds-test.test.js index 7e0af4c..d1025ba 100644 --- a/test/cds-test.test.js +++ b/test/cds-test.test.js @@ -91,6 +91,27 @@ describe('cds_test', ()=>{ }) }) + describe ('perf', ()=> { + it('should support perf', ()=> { + expect (test.perf).to.exist + const { perf } = test + expect (perf.get).to.exist + expect (perf.put).to.exist + expect (perf.post).to.exist + expect (perf.delete).to.exist + expect (perf.fn).to.exist + }) + + it('should support REST shortcuts', ()=> { + const { GET,PUT,POST,PATCH,DEL,FN} = test.perf + expect (GET).to.exist + expect (PUT).to.exist + expect (POST).to.exist + expect (PATCH).to.exist + expect (DEL).to.exist + expect (FN).to.exist + }) + }) describe ('logs', ()=> { From 5b77d068893c160f0e4206d08a8feb8387a1fee1 Mon Sep 17 00:00:00 2001 From: Bob den Os Date: Wed, 26 Mar 2025 15:19:05 +0100 Subject: [PATCH 2/2] Update package-lock.json --- package-lock.json | 360 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 355 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b927eea..223b4d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.3.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { + "autocannon": "^8.0.0", "axios": "^1", "chai": "^4.4.1", "chai-as-promised": "^7.1.1", @@ -30,6 +31,12 @@ "@sap/cds": ">=8.8" } }, + "node_modules/@assemblyscript/loader": { + "version": "0.19.23", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.23.tgz", + "integrity": "sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==", + "license": "Apache-2.0" + }, "node_modules/@cap-js/db-service": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@cap-js/db-service/-/db-service-1.18.0.tgz", @@ -57,6 +64,25 @@ "@sap/cds": ">=7.6" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@minimistjs/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@minimistjs/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha512-Q/ONBiM2zNeYUy0mVSO44mWWKYM3UHuEK43PKIOzJCbvUnPoMH1K+gk3cf1kgnCVJFlWmddahQQCmrmBGlk9jQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.1.0" + } + }, "node_modules/@sap/cds": { "version": "8.8.3", "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.8.3.tgz", @@ -149,6 +175,30 @@ "node": ">= 0.6" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/antlr4": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.9.3.tgz", @@ -181,6 +231,40 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/autocannon": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/autocannon/-/autocannon-8.0.0.tgz", + "integrity": "sha512-fMMcWc2JPFcUaqHeR6+PbmEpTxCrPZyBUM95oG4w3ngJ8NfBNas/ZXA+pTHXLqJ0UlFVTcy05GC25WxKx/M20A==", + "license": "MIT", + "dependencies": { + "@minimistjs/subarg": "^1.0.0", + "chalk": "^4.1.0", + "char-spinner": "^1.0.1", + "cli-table3": "^0.6.0", + "color-support": "^1.1.1", + "cross-argv": "^2.0.0", + "form-data": "^4.0.0", + "has-async-hooks": "^1.0.0", + "hdr-histogram-js": "^3.0.0", + "hdr-histogram-percentiles-obj": "^3.0.0", + "http-parser-js": "^0.5.2", + "hyperid": "^3.0.0", + "lodash.chunk": "^4.2.0", + "lodash.clonedeep": "^4.5.0", + "lodash.flatten": "^4.4.0", + "manage-path": "^2.0.0", + "on-net-listen": "^1.1.1", + "pretty-bytes": "^5.4.1", + "progress": "^2.0.3", + "reinterval": "^1.1.0", + "retimer": "^3.0.0", + "semver": "^7.3.2", + "timestring": "^6.0.0" + }, + "bin": { + "autocannon": "autocannon.js" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -196,7 +280,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -290,7 +373,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -390,6 +472,28 @@ "node": ">=4" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/char-spinner/-/char-spinner-1.0.1.tgz", + "integrity": "sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==", + "license": "ISC" + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -409,6 +513,48 @@ "dev": true, "license": "ISC" }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/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==", + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -461,6 +607,12 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-argv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cross-argv/-/cross-argv-2.0.0.tgz", + "integrity": "sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==", + "license": "MIT" + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -570,6 +722,12 @@ "dev": true, "license": "MIT" }, + "node_modules/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==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -881,6 +1039,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-async-hooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-async-hooks/-/has-async-hooks-1.0.0.tgz", + "integrity": "sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==", + "license": "Apache-2.0" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -920,6 +1093,26 @@ "node": ">= 0.4" } }, + "node_modules/hdr-histogram-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz", + "integrity": "sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==", + "license": "BSD", + "dependencies": { + "@assemblyscript/loader": "^0.19.21", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -937,6 +1130,23 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "license": "MIT" + }, + "node_modules/hyperid": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.3.0.tgz", + "integrity": "sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "uuid": "^8.3.2", + "uuid-parse": "^1.1.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -954,7 +1164,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -995,6 +1204,33 @@ "node": ">= 0.10" } }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.chunk": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", + "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -1004,6 +1240,12 @@ "get-func-name": "^2.0.1" } }, + "node_modules/manage-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/manage-path/-/manage-path-2.0.0.tgz", + "integrity": "sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1094,7 +1336,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1170,6 +1411,15 @@ "node": ">= 0.8" } }, + "node_modules/on-net-listen": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/on-net-listen/-/on-net-listen-1.1.2.tgz", + "integrity": "sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==", + "license": "MIT", + "engines": { + "node": ">=9.4.0 || ^8.9.4" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1180,6 +1430,12 @@ "wrappy": "1" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1233,6 +1489,27 @@ "node": ">=10" } }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1337,6 +1614,18 @@ "node": ">= 6" } }, + "node_modules/reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==", + "license": "MIT" + }, + "node_modules/retimer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", + "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1369,7 +1658,6 @@ "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1586,6 +1874,32 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -1596,6 +1910,18 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar-fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", @@ -1626,6 +1952,15 @@ "node": ">=6" } }, + "node_modules/timestring": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/timestring/-/timestring-6.0.0.tgz", + "integrity": "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1699,6 +2034,21 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",