diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..1f8783d --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '39 13 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..da99d0c --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,14 @@ +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000..0a52527 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,23 @@ +name: Node.js CI + +on: + push: + branches: [master] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node: [18.x] + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - run: npm ci + - run: npm test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index df63076..0000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: node_js -node_js: - - "0.10" - - "0.8" diff --git a/README.md b/README.md index 8c64f61..a64a10b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ dynamo-table-extensions ======================= -[![Build Status](https://secure.travis-ci.org/xarvh/dynamo-table-extensions.png?branch=master)](http://travis-ci.org/xarvh/dynamo-table-extensions) +[![npm version](https://badge.fury.io/js/dynamo-table-extensions.svg)](https://badge.fury.io/js/dynamo-table-extensions) +![Build Status](https://github.com/Adslot/dynamo-table-extensions/actions/workflows/node.js.yml/badge.svg) Adds higher-level methods to [dynamo-table](https://github.com/mhart/dynamo-table). diff --git a/dynamo-adapter.js b/dynamo-adapter.js new file mode 100644 index 0000000..cab77a7 --- /dev/null +++ b/dynamo-adapter.js @@ -0,0 +1,145 @@ +const {promisify} = require('util'); +const {DynamoTable} = require('./slim'); + +const isCallback = (fn) => typeof fn === 'function'; + +const validateCall = (methodName, args, minLength) => { + if (args.length < minLength) + throw new Error(`Incorrect args supplied to method ${methodName}: expected: ${minLength} got: ${args.length}`); +} + +/** + Provides an adapter for the DynamoTable class to allow for promisified + api usage when omitting the cb parameter. +*/ +class DynamoAdapter { + constructor(name, options) { + this._delegate = new DynamoTable(name, options); + this.hooks = options.hooks || {}; + + // Delegate externally accessed properties + Object.keys(this._delegate).forEach(key => { + this[key] = this._delegate[key]; + }); + + // NOTE: Rebind all methods so they can be passed without the class instance + // Since we only instantiate once per table and maintain the instances, + // the performance should be a big concern + Object.getOwnPropertyNames(DynamoAdapter.prototype).forEach(key => { + if (typeof this[key] === 'function') this[key] = this[key].bind(this); + }); + + // NOTE: This function needs to be rebound to the delegate for its use case + this['mapToDb'] = this._delegate.mapToDb.bind(this._delegate); + } + + // Proxies execution to delegate + _call(methodName, minLength, ...args) { + validateCall(methodName, args, minLength); + + const cb = args[args.length - 1]; + + let method = this._delegate[methodName]; + + // TODO: No support for async 'hook' + this._runHook(methodName); + + if (!isCallback(cb)) method = promisify(method); + + return method.apply(this._delegate, args); + } + + _runHook(methodName) { + const hook = this.hooks[methodName]; + + return hook ? hook() : null; + } + + // Allows additional hook for execution BEFORE function + addHook(methodName, fn) { + this.hooks[methodName] = fn; + } + + // Provides instance copy functionality + copy(options) { + const overrides = Object.assign({}, this, options); + + return new DynamoAdapter(this.name, overrides); + } + + // Accepts minimum 1 parameter + addNew(...args) { + return this._call('addNew', 1, ...args); + } + + // Accepts minimum 0 parameters + count(...args) { + return this._call('count', 0, ...args); + } + + // Accepts minimum 1 parameter + get(...args) { + return this._call('get', 1, ...args); + } + + // Accepts minimum 1 parameter + put(...args) { + return this._call('put', 1, ...args); + } + + // Accept minimum 1 parameter + delete(...args) { + return this._call('delete', 1, ...args); + } + + update(...args) { + return this._call('update', 1, ...args); + } + + // Accept minimum 1 parameter + query(...args) { + return this._call('query', 1, ...args); + } + + // Accept minimum 0 parameters + scan(...args) { + return this._call('scan', 0, ...args); + } + + // Accept minimum 1 parameter + batchGet(...args) { + return this._call('batchGet', 1, ...args); + } + + // Accept minimum 1 parameter + batchWrite(...args) { + return this._call('batchWrite', 1, ...args); + } + + // Accept minimum 0 parameters + createTable(...args) { + return this._call('createTable', 0, ...args); + } + + // Accept minimum 0 parameters + describeTable(...args) { + return this._call('describeTable', 0, ...args); + } + + // Accept minimum 0 parameters + deleteTable(...args) { + return this._call('deleteTable', 0, ...args); + } + + // Accept minimum 2 parameters + throttledBatchWrite(...args) { + return this._call('throttledBatchWrite', 2, ...args); + } + + // Accept minimum 0 parameters + truncate(...args) { + return this._call('truncate', 0, ...args); + } +} + +module.exports = DynamoAdapter; diff --git a/index.js b/index.js index e74f59c..a639452 100644 --- a/index.js +++ b/index.js @@ -1,59 +1,9 @@ -var async = require('async') -var dynamoTable = require('./slim') +const {DynamoTable} = require('./slim'); +const DynamoAdapter = require('./dynamo-adapter'); -module.exports = dynamoTable +// Library is a factory function which also provides the DynamoTable class +const dynamo = (...args) => new DynamoAdapter(...args); -var proto = dynamoTable.DynamoTable.prototype +dynamo.DynamoTable = DynamoTable; -// Ensures that no more than capacityRatio * writeCapacity items are written per second -proto.throttledBatchWrite = function(capacityRatio, items, cb) { - if (!(capacityRatio > 0)) return cb(new Error('non-positive capacityRatio')) - - var self = this - self.describeTable(function(err, info) { - if (err) return cb(err) - - var itemsPerSecond = Math.ceil(info.ProvisionedThroughput.WriteCapacityUnits * capacityRatio); - var written = 0; - var ready = true; - - var waitAndWrite = function(cb) { - async.until(function() {return ready}, function(cb) {setTimeout(cb, 10)}, function(err) { - if (err) return cb(err) - ready = false - setTimeout(function() {ready = true}, 1000) - - var write = items.slice(written, written + itemsPerSecond) - self.batchWrite(write, function(err) { - if (err) return cb(err) - written += write.length; - cb(); - }) - }) - } - - async.whilst(function() {return written < items.length}, waitAndWrite, cb) - }) -} - - -proto.truncate = function(cb) { - async.series([this.deleteTable.bind(this), this.createTable.bind(this)], cb) -} - - -proto.addNew = function(record, cb) { - var key = Array.isArray(this.key) ? this.key[0] : this.key; - - if (record[key]) - return this.put(record, cb); - - var self = this; - - // this.nextId is added by `dynamo-table-id`, which must be loaded - this.nextId(function(err, id) { - if (err) return cb(err); - record[key] = id; - self.put(record, cb); - }); -} +module.exports = dynamo; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ce04f5d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1573 @@ +{ + "name": "dynamo-table-extensions", + "version": "1.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "dynamo-table-extensions", + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "async": "^3.2.4" + }, + "devDependencies": { + "mocha": "^10.2.0" + }, + "engines": { + "node": "^18" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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 + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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/chalk/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.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==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/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_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "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==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/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, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "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 + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.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 + }, + "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 + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "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 + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "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 + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "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.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": 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 + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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-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": "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 + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "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-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "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 + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "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 + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "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 + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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" + } + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 8bc60a6..488c4b4 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,20 @@ { "name": "dynamo-table-extensions", - "version": "0.1.1", + "version": "1.2.0", "description": "Adds higher-level methods to dynamo-table", "main": "index.js", "scripts": { "test": "mocha -t 100s", - "coverage": "istanbul cover ./node_modules/.bin/_mocha -- -t 10s" + "coverage": "istanbul cover ./node_modules/.bin/_mocha -- -t 10s", + "postversion": "git push -u origin $(git rev-parse --abbrev-ref HEAD) --follow-tags && npm publish && echo '…released.'", + "preversion": "echo 'Releasing…' && npm ci", + "release:major": "npm version major -m 'build: release major version %s'", + "release:minor": "npm version minor -m 'build: release minor version %s'", + "release:patch": "npm version patch -m 'build: release patch version %s'" }, "repository": { "type": "git", - "url": "https://github.com/xarvh/dynamo-table-extensions.git" + "url": "https://github.com/Adslot/dynamo-table-extensions.git" }, "keywords": [ "dynamo", @@ -21,13 +26,13 @@ "Dmitry Shirokov " ], "license": "MIT", + "dependencies": { + "async": "^3.2.4" + }, "devDependencies": { - "should": "~2.0.2", - "mocha": "~1.13.0", - "dynalite": "~0.3.5", - "istanbul": "~0.1.44" + "mocha": "^10.2.0" }, - "dependencies": { - "async": "~0.2.9" + "engines": { + "node": "^18" } } diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..f7f0485 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "extends": ["github>Adslot/renovate-config-adslot"] +} diff --git a/slim.js b/slim.js index ae17138..b0d7d2d 100644 --- a/slim.js +++ b/slim.js @@ -1,6 +1,5 @@ +const async = require('async'); var dynamo -// , once = require('once') - var once = function(func) { var ran = false, memo; @@ -13,15 +12,6 @@ var once = function(func) { }; }; -// try { -// dynamo = require('dynamo-client') -// } catch (e) { -// // Assume consumer will pass in client -// } - -module.exports = function(name, options) { - return new DynamoTable(name, options) -} module.exports.DynamoTable = DynamoTable function DynamoTable(name, options) { @@ -42,13 +32,9 @@ function DynamoTable(name, options) { this.writeCapacity = options.writeCapacity this.localIndexes = options.localIndexes || options.indexes this.globalIndexes = options.globalIndexes - // this.scanSegments = options.scanSegments - // !!!!!!!!!!! - // this.preMapFromDb = options.preMapFromDb this.postMapFromDb = options.postMapFromDb // needed this.preMapToDb = options.preMapToDb // needed - // this.postMapToDb = options.postMapToDb } DynamoTable.prototype.mapAttrToDb = function(val, key, jsObj) { @@ -56,87 +42,10 @@ DynamoTable.prototype.mapAttrToDb = function(val, key, jsObj) { return val.toISOString() return val - // var mapping = this.mappings[key], numToStr = this._numToStr.bind(this, key) - // if (mapping) { - // if (typeof mapping.to === 'function') return mapping.to(val, key, jsObj) - // if (typeof val === 'undefined' || typeof val === 'function') return - // if (mapping === 'json') return {S: JSON.stringify(val)} - // if (val == null || val === '') return - // switch (mapping) { - // case 'S': return {S: String(val)} - // case 'N': return {N: numToStr(val)} - // case 'B': return {B: val.toString('base64')} - // case 'SS': return {SS: typeof val[0] === 'string' ? val : val.map(String)} - // case 'NS': return {NS: val.map(numToStr)} - // case 'BS': return {BS: val.map(function(x) { return x.toString('base64') })} - // case 'bignum': return Array.isArray(val) ? {NS: val} : {N: val} - // case 'isodate': return {S: val.toISOString()} - // case 'timestamp': return {N: numToStr(val)} - // case 'mapS': return {SS: Object.keys(val)} - // case 'mapN': return {NS: Object.keys(val)} - // case 'mapB': return {BS: Object.keys(val)} - // } - // } - // if (val == null || val === '') return - // switch (typeof val) { - // case 'string': return {S: val} - // case 'boolean': return {S: String(val)} - // case 'number': return {N: numToStr(val)} - // case 'function': return - // } - // if (Buffer.isBuffer(val)) { - // if (!val.length) return - // return {B: val.toString('base64')} - // } - // if (Array.isArray(val)) { - // if (!val.length) return - // if (typeof val[0] === 'string') return {SS: val} - // if (typeof val[0] === 'number') return {NS: val.map(numToStr)} - // if (Buffer.isBuffer(val[0])) return {BS: val.map(function(x) { return x.toString('base64') })} - // } - // // Other types (inc dates) are mapped as they are in JSON - // val = typeof val.toJSON === 'function' ? val.toJSON() : JSON.stringify(val) - // if (val) return {S: val} } DynamoTable.prototype.mapAttrFromDb = function(val, key, dbItem) { return val - // var mapping = this.mappings[key] - // if (mapping) { - // if (typeof mapping.from === 'function') return mapping.from(val, key, dbItem) - // switch (mapping) { - // case 'S': return val.S - // case 'N': return +val.N - // case 'B': return new Buffer(val.B, 'base64') - // case 'SS': return val.SS - // case 'NS': return val.NS.map(Number) - // case 'BS': return val.BS.map(function(x) { return new Buffer(x, 'base64') }) - // case 'json': return JSON.parse(val.S) - // case 'bignum': return val.N != null ? val.N : val.NS - // case 'isodate': return new Date(val.S) - // case 'timestamp': return new Date(val.N) - // case 'mapS': - // case 'mapN': - // case 'mapB': - // return (val.SS || val.NS || val.BS).reduce(function(mapObj, val) { - // mapObj[val] = 1 - // return mapObj - // }, {}) - // } - // } - // if (val.S != null) { - // if (val.S === 'true') return true - // if (val.S === 'false') return false - // if (val.S[0] === '{' || val.S[0] === '[') - // try { return JSON.parse(val.S) } catch (e) {} - // return val.S - // } - // if (val.N != null) return +val.N - // if (val.B != null) return new Buffer(val.B, 'base64') - // if (val.SS != null) return val.SS - // if (val.NS != null) return val.NS.map(Number) - // if (val.BS != null) return val.BS.map(function(x) { return new Buffer(x, 'base64') }) - // throw new Error('Unknown DynamoDB type for "' + key + '": ' + JSON.stringify(val)) } DynamoTable.prototype.mapToDb = function(jsObj) { @@ -151,24 +60,11 @@ DynamoTable.prototype.mapToDb = function(jsObj) { dbItem[key] = dbAttr }) } - // if (this.postMapToDb) dbItem = this.postMapToDb(dbItem) + return dbItem } DynamoTable.prototype.mapFromDb = function(dbItem) { - // if (this.preMapFromDb) dbItem = this.preMapFromDb(dbItem) - - // var self = this, - // jsObj = dbItem != null ? {} : null - - // if (dbItem != null && jsObj != null) { - // Object.keys(dbItem).forEach(function(key) { - // var jsAttr = self.mapAttrFromDb(dbItem[key], key, dbItem) - // if (typeof jsAttr !== 'undefined') - // jsObj[key] = jsAttr - // }) - // } - // TODO: clone? var jsObj = dbItem; @@ -206,23 +102,6 @@ DynamoTable.prototype._isEmpty = function(attr) { attr.SS === '[]' || attr.NS === '[]' || attr.BS === '[]' } -// DynamoTable.prototype._getKeyType = function(attr) { -// var type = this.keyTypes[attr] || this.mappings[attr] || 'S' -// switch (type) { -// case 'N': -// case 'S': -// case 'B': -// return type -// case 'json': -// case 'isodate': -// return 'S' -// case 'bignum': -// case 'timestamp': -// return 'N' -// } -// throw new Error('Unsupported key type (' + type + ') for attr ' + attr) -// } - DynamoTable.prototype.get = function(key, options, cb) { if (!cb) { cb = options; options = {} } if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') @@ -232,7 +111,7 @@ DynamoTable.prototype.get = function(key, options, cb) { options.Key = options.Key || this.resolveKey(key) this.client.request('GetItem', options, function(err, data) { if (err) return cb(err) - // console.log('data.Item', data.Item) + cb(null, self.mapFromDb(data.Item)) }) } @@ -265,6 +144,7 @@ DynamoTable.prototype.update = function(key, actions, options, cb) { // If actions is a string or array, then it's a whitelist for attributes to update if (typeof actions === 'string') actions = [actions] if (Array.isArray(actions)) { pick = actions; actions = key; key = null } + if (!actions) { actions = key; key = null } // If key is null, assume actions has a full object to put so clone it (without keys) if (key == null) { @@ -327,24 +207,7 @@ DynamoTable.prototype.query = function(conditions, options, cb) { var self = this, nonKeys options.KeyConditions = options.KeyConditions || this.conditions(conditions) - // if (!options.IndexName) { - // nonKeys = Object.keys(options.KeyConditions).filter(function(attr) { return !~self.key.indexOf(attr) }) - // if (nonKeys.length) { - // // we have a non-key attribute, must find an IndexName - // this.resolveLocalIndexes(this.localIndexes).forEach(function(index) { - // if (index.key === nonKeys[0]) - // options.IndexName = index.name - // }) - // if (!options.IndexName) { - // nonKeys = Object.keys(options.KeyConditions) - // this.resolveGlobalIndexes(this.globalIndexes).forEach(function(index) { - // if (index.hashKey === nonKeys[0] && (!nonKeys[1] || index.rangeKey === nonKeys[1])) - // options.IndexName = index.name - // }) - // } - // options.IndexName = options.IndexName || nonKeys[0] - // } - // } + this._listRequest('Query', options, cb) } @@ -358,40 +221,13 @@ DynamoTable.prototype.scan = function(conditions, options, cb) { var totalSegments, segment, allItems if (conditions != null && !options.ScanFilter) options.ScanFilter = this.conditions(conditions) - // options.TotalSegments = options.TotalSegments || this.scanSegments - - // if (options.Segment == null && options.TotalSegments) { - // totalSegments = options.TotalSegments - // allItems = new Array(totalSegments) - // for (segment = 0; segment < totalSegments; segment++) - // this.scan(null, cloneWithSegment(options, segment), checkDone(segment)) - // } else { + this._listRequest('Scan', options, cb) - // } - - // function cloneWithSegment(options, segment) { - // return Object.keys(options).reduce(function(clone, key) { - // clone[key] = options[key] - // return clone - // }, {Segment: segment}) - // } - // function checkDone(segment) { - // return function (err, items) { - // if (err) return cb(err) - // allItems[segment] = items - // if (!--totalSegments) { - // if (options.Select === 'COUNT') - // return cb(null, allItems.reduce(function(sum, count) { return sum + count })) - // cb(null, [].concat.apply([], allItems)) - // } - // } - // } } // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html DynamoTable.MAX_GET = 250 DynamoTable.prototype.batchGet = function(keys, options, tables, cb) { - // console.log('batchGet', this.name, keys) if (!cb) { cb = tables; tables = [] } if (!cb) { cb = options; options = {} } if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') @@ -431,9 +267,6 @@ DynamoTable.prototype.batchGet = function(keys, options, tables, cb) { }, {})) } - if (allKeys.length > DynamoTable.MAX_GET) - console.log('Alarma!!', allKeys.length) - for (i = 0; i < allKeys.length; i += DynamoTable.MAX_GET) { requestItems = {} for (j = i; j < i + DynamoTable.MAX_GET && j < allKeys.length; j++) { @@ -451,7 +284,7 @@ DynamoTable.prototype.batchGet = function(keys, options, tables, cb) { function batchRequest(requestItems, results, cb) { k = Object.keys(requestItems)[0] - //console.log('batch', k, requestItems[k].Keys.length) + if (!cb) { cb = results; results = {} } self.client.request('BatchGetItem', {RequestItems: requestItems}, function(err, data) { if (err) return cb(err) @@ -483,7 +316,16 @@ DynamoTable.prototype.batchGet = function(keys, options, tables, cb) { // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html DynamoTable.MAX_WRITE = 25 -DynamoTable.prototype.batchWrite = function(operations, tables, cb) { +DynamoTable.prototype.batchWrite = function(operations, tables, options, cb) { + if (!cb) { + cb = options; + if (Object.prototype.toString.call(tables) === '[object Object]') { + options = tables; + tables = []; + } else { + options = {} + } + } if (!cb) { cb = tables; tables = [] } if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') cb = once(cb) @@ -521,7 +363,8 @@ DynamoTable.prototype.batchWrite = function(operations, tables, cb) { } function batchRequest(requestItems, cb) { - self.client.request('BatchWriteItem', {RequestItems: requestItems}, function(err, data) { + options.RequestItems = requestItems + self.client.request('BatchWriteItem', options, function(err, data) { if (err) return cb(err) if (Object.keys(data.UnprocessedItems || {}).length) return batchRequest(data.UnprocessedItems, cb) @@ -544,62 +387,7 @@ DynamoTable.prototype.createTable = function(readCapacity, writeCapacity, localI if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') options.TableName = options.TableName || this.name var self = this - // , attrMap = this.key.reduce(function(namesObj, attr) { - // namesObj[attr] = true - // return namesObj - // }, {}) - - // if (localIndexes && !options.LocalSecondaryIndexes) { - // options.LocalSecondaryIndexes = this.resolveLocalIndexes(localIndexes).map(function(index) { - // var lsi = { - // IndexName: index.name, - // KeySchema: [self.key[0], index.key].map(function(attr, ix) { - // return { AttributeName: attr, KeyType: ix === 0 ? 'HASH' : 'RANGE' } - // }), - // } - // if (typeof index.projection === 'string') - // lsi.Projection = {ProjectionType: index.projection} - // else if (Array.isArray(index.projection)) - // lsi.Projection = {ProjectionType: 'INCLUDE', NonKeyAttributes: index.projection} - - // attrMap[index.key] = true - - // return lsi - // }) - // } - // if (this.globalIndexes && !options.GlobalSecondaryIndexes) { - // options.GlobalSecondaryIndexes = this.resolveGlobalIndexes(this.globalIndexes).map(function(index) { - // var gsi = { - // IndexName: index.name, - // KeySchema: [index.hashKey, index.rangeKey].filter(function(attr) { return attr }).map(function(attr, ix) { - // return { AttributeName: attr, KeyType: ix === 0 ? 'HASH' : 'RANGE' } - // }), - // ProvisionedThroughput: { - // ReadCapacityUnits: index.readCapacity, - // WriteCapacityUnits: index.writeCapacity, - // } - // } - // if (typeof index.projection === 'string') - // gsi.Projection = {ProjectionType: index.projection} - // else if (Array.isArray(index.projection)) - // gsi.Projection = {ProjectionType: 'INCLUDE', NonKeyAttributes: index.projection} - - // attrMap[index.hashKey] = true - // if (index.rangeKey) attrMap[index.rangeKey] = true - - // return gsi - // }) - // } - // options.KeySchema = options.KeySchema || this.key.map(function(attr, ix) { - // return {AttributeName: attr, KeyType: ix === 0 ? 'HASH' : 'RANGE'} - // }) - // options.AttributeDefinitions = options.AttributeDefinitions || Object.keys(attrMap).map(function(attr) { - // return {AttributeName: attr, AttributeType: self._getKeyType(attr)} - // }) - // options.ProvisionedThroughput = options.ProvisionedThroughput || { - // ReadCapacityUnits: readCapacity, - // WriteCapacityUnits: writeCapacity, - // } + this.client.request('CreateTable', options, function(err, data) { if (err) return cb(err) cb(null, data.TableDescription) @@ -617,23 +405,6 @@ DynamoTable.prototype.describeTable = function(options, cb) { }) } -// DynamoTable.prototype.updateTable = function(readCapacity, writeCapacity, options, cb) { -// if (!cb) { cb = options; options = {} } -// if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') -// options.TableName = options.TableName || this.name - -// if (readCapacity && writeCapacity) { -// options.ProvisionedThroughput = options.ProvisionedThroughput || { -// ReadCapacityUnits: readCapacity, -// WriteCapacityUnits: writeCapacity, -// } -// } -// this.client.request('UpdateTable', options, function(err, data) { -// if (err) return cb(err) -// cb(null, data.TableDescription) -// }) -// } - DynamoTable.prototype.deleteTable = function(options, cb) { if (!cb) { cb = options; options = {} } if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') @@ -645,140 +416,29 @@ DynamoTable.prototype.deleteTable = function(options, cb) { }) } -// TODO: Support ExclusiveStartTableName/LastEvaluatedTableName -// DynamoTable.prototype.listTables = function(options, cb) { -// if (!cb) { cb = options; options = {} } -// if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') - -// this.client.request('ListTables', options, function(err, data) { -// if (err) return cb(err) -// cb(null, data.TableNames) -// }) -// } - -// DynamoTable.prototype.deleteTableAndWait = function(options, cb) { -// if (!cb) { cb = options; options = {} } -// if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') -// var self = this -// this.describeTable(function(err, data) { -// if (err && err.name === 'ResourceNotFoundException') return cb() -// if (err) return cb(err) - -// if (data.TableStatus !== 'ACTIVE') return setTimeout(self.deleteTableAndWait.bind(self, options, cb), 1000) - -// self.deleteTable(options, function(err) { -// if (err) return cb(err) -// self.deleteTableAndWait(options, cb) -// }) -// }) -// } - -// DynamoTable.prototype.createTableAndWait = function(readCapacity, writeCapacity, localIndexes, options, cb) { -// if (!cb) { cb = options; options = {} } -// if (!cb) { cb = localIndexes; localIndexes = this.localIndexes } -// if (!cb) { cb = writeCapacity; writeCapacity = this.writeCapacity || 1 } -// if (!cb) { cb = readCapacity; readCapacity = this.readCapacity || 1 } -// if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') -// var self = this -// this.describeTable(function(err, data) { -// if (err && err.name === 'ResourceNotFoundException') { -// return self.createTable(readCapacity, writeCapacity, localIndexes, options, function(err) { -// if (err) return cb(err) -// self.createTableAndWait(readCapacity, writeCapacity, localIndexes, options, cb) -// }) -// } -// if (err) return cb(err) - -// if (data.TableStatus === 'ACTIVE') return cb() -// if (data.TableStatus !== 'CREATING') return cb(new Error(data.TableStatus)) - -// setTimeout(self.createTableAndWait.bind(self, readCapacity, writeCapacity, localIndexes, options, cb), 1000) -// }) -// } - -// DynamoTable.prototype.updateTableAndWait = function(readCapacity, writeCapacity, options, cb) { -// if (!cb) { cb = options; options = {} } -// if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') -// options.TableName = options.TableName || this.name -// var self = this - -// self.updateTable(readCapacity, writeCapacity, options, function(err) { -// if (err) return cb(err) - -// // check whether the update has in fact been applied -// function checkTable(count) { -// // Make sure we don't go into an infinite loop -// if (++count > 1000) return cb(new Error('Wait limit exceeded')) - -// self.describeTable(function(err, data) { -// if (err) return cb(err) -// if (data.TableStatus !== 'ACTIVE' || (data.GlobalSecondaryIndexes && -// !data.GlobalSecondaryIndexes.every(function (idx) {return idx.IndexStatus === 'ACTIVE'}))) { -// return setTimeout(checkTable, 1000, count) -// } - -// // If the table is ACTIVE then return -// cb(null, data) -// }) -// } -// checkTable(1) -// }) -// } - - -// DynamoTable.prototype.increment = function(key, attr, incrAmt, options, cb) { -// var self = this, actions -// if (!cb) { cb = options; options = {} } -// if (!cb) { cb = incrAmt; incrAmt = 1 } -// if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') - -// options.ReturnValues = options.ReturnValues || 'UPDATED_NEW' -// actions = {add: {}} -// actions.add[attr] = incrAmt -// this.update(key, actions, options, function(err, data) { -// if (err) return cb(err) -// var newVal = (data.Attributes != null ? data.Attributes[attr] : null) -// if (newVal == null) return cb() -// cb(null, self.mapAttrFromDb(newVal, attr)) -// }) -// } - DynamoTable.prototype._listRequest = function(operation, items, options, cb) { if (!cb) { cb = options; options = items; items = [] } if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') var self = this - // console.log('list opts', options) this.client.request(operation, options, function(err, data) { if (err) return cb(err) if (options.Select === 'COUNT') return cb(null, data.Count) // When we fetch only few fields it doesn't require // mapping as it's not longer a valid model, right? - // console.log(options.AttributesToGet) if (options.AttributesToGet && options.AttributesToGet.length) return cb(null, data.Items) for (var i = data.Items.length - 1; i >= 0; i--) { data.Items[i] = self.mapFromDb(data.Items[i]) }; - // = items.concat(data.Items.map(function(item) { return self.mapFromDb(item) })) - // items = items.concat(data.Items.map(function(item) { return self.mapFromDb(item) })) - // if (data.LastEvaluatedKey != null && (!options.Limit || options.Limit !== (data.ScannedCount || data.Count))) { - // options.ExclusiveStartKey = data.LastEvaluatedKey - // return self._listRequest(operation, items, options, cb) - // } + cb(null, data.Items) }) } DynamoTable.prototype.conditions = function(conditionExprObj) { - // var self = this - // return Object.keys(conditionExprObj).reduce(function(condObj, attr) { - // condObj[attr] = self.condition(attr, conditionExprObj[attr]) - // return condObj - // }, {}) - // var condObj = {} for (var k in conditionExprObj) condObj[k] = this.condition(k, conditionExprObj[k]) @@ -842,60 +502,6 @@ DynamoTable.prototype.comparison = function(comparison) { return comparison.toUpperCase() } -// local indexes: -// [attr1/name1, attr2/name2] -// {name: attr1} -// {name: {key: attr1, projection: 'KEYS_ONLY'}} -// {name: {key: attr1, projection: [attr1, attr2]}} -// [{name: name1, key: attr1}, {name: name2, key: attr2}] -// DynamoTable.prototype.resolveLocalIndexes = function(indexes) { -// if (!indexes) return [] -// if (!Array.isArray(indexes)) { -// indexes = Object.keys(indexes).map(function(name) { -// var index = indexes[name] -// if (typeof index === 'string') index = {key: index} -// return {name: name, key: index.key, projection: index.projection} -// }) -// } -// return indexes.map(function(index) { -// if (typeof index === 'string') index = {name: index, key: index} -// index.projection = index.projection || 'ALL' -// return index -// }) -// } - -// global indexes: -// [attr1/name1, attr2/name2] -// {name: attr1} -// {name: {key: attr1, projection: 'KEYS_ONLY'}} -// {name: {key: attr1, projection: [attr1, attr2]}} -// [{name: name1, key: attr1}, {name: name2, key: attr2}] -// DynamoTable.prototype.resolveGlobalIndexes = function(indexes) { -// if (!indexes) return [] -// if (!Array.isArray(indexes)) { -// indexes = Object.keys(indexes).map(function(name) { -// var index = indexes[name] -// if (typeof index === 'string') index = {hashKey: index} -// return { -// name: name, -// hashKey: index.hashKey, -// rangeKey: index.rangeKey, -// projection: index.projection, -// readCapacity: index.readCapacity, -// writeCapacity: index.writeCapacity, -// } -// }) -// } -// return indexes.map(function(index) { -// if (typeof index === 'string') index = {name: index} -// index.hashKey = index.hashKey || index.name -// index.projection = index.projection || 'ALL' -// index.readCapacity = index.readCapacity || 1 -// index.writeCapacity = index.writeCapacity || 1 -// return index -// }) -// } - DynamoTable.prototype._getDefaultOptions = function(options) { if (options == null) options = {} @@ -907,9 +513,53 @@ DynamoTable.prototype._getDefaultOptions = function(options) { return options } -// DynamoTable.prototype._numToStr = function(attr, num) { -// var numStr = String(+num) -// if (numStr === 'NaN' || numStr === 'Infinity' || numStr === '-Infinity') -// throw new Error('Cannot convert attribute "' + attr + '" to DynamoDB number: ' + num) -// return numStr -// } +// Ensures that no more than capacityRatio * writeCapacity items are written per second +DynamoTable.prototype.throttledBatchWrite = function(capacityRatio, items, cb) { + if (!(capacityRatio > 0)) return cb(new Error('non-positive capacityRatio')) + + var self = this + self.describeTable(function(err, info) { + if (err) return cb(err) + + var itemsPerSecond = Math.ceil(info.ProvisionedThroughput.WriteCapacityUnits * capacityRatio); + var written = 0; + var ready = true; + + var waitAndWrite = function(cb) { + async.until(function() {return ready}, function(cb) {setTimeout(cb, 10)}, function(err) { + if (err) return cb(err) + ready = false + setTimeout(function() {ready = true}, 1000) + + var write = items.slice(written, written + itemsPerSecond) + self.batchWrite(write, function(err) { + if (err) return cb(err) + written += write.length; + cb(); + }) + }) + } + + async.whilst(function() {return written < items.length}, waitAndWrite, cb) + }) +} + +DynamoTable.prototype.truncate = function(cb) { + async.series([this.deleteTable.bind(this), this.createTable.bind(this)], cb) +} + +DynamoTable.prototype.addNew = function(record, cb) { + var key = Array.isArray(this.key) ? this.key[0] : this.key; + + if (record[key]) + return this.put(record, cb); + + var self = this; + + // this.nextId is added by `dynamo-table-id`, which must be loaded + this.nextId(function(err, id) { + if (err) return cb(err); + record[key] = id; + self.put(record, cb); + }); +} diff --git a/test/slow.js b/test/slow.js index 2e4034f..e12c673 100644 --- a/test/slow.js +++ b/test/slow.js @@ -1,166 +1,2 @@ -var should = require('should') -var async = require('async') -var dynamoTableExtended = require('..') -var dynaliteServer = require('dynalite')() -var useLive = process.env.USE_LIVE_DYNAMO // set this (and AWS credentials) if you want to test on a live instance -var region = process.env.AWS_REGION // will just default to us-east-1 if not specified - -describe('extensions', function() { - - var table - - before(function(done) { - var port, setup = function(cb) { cb() } - - if (!useLive) { - port = 10000 + Math.round(Math.random() * 10000) - region = {host: 'localhost', port: port, credentials: {accessKeyId: 'a', secretAccessKey: 'a'}} - setup = dynaliteServer.listen.bind(dynaliteServer, port) - } - - table = dynamoTableExtended('dynamo-table-extension-test', { - region: region, - key: ['id'], - mappings: {id: 'N'}, - writeCapacity: 2 - }) - - setup(function(err) { - if (err) return done(err) - async.series([table.deleteTableAndWait.bind(table), table.createTableAndWait.bind(table)], done) - }) - }) - - after(function (done) { - table.deleteTableAndWait(done) - }) - - - describe('throttledBatchWrite', function() { - var originalBatchWrite - var batchWriteCount - var batchWriteError - var writtenItems - - beforeEach(function() { - originalBatchWrite = table.batchWrite - table.batchWrite = function(items, cb) { - ++batchWriteCount - writtenItems = writtenItems.concat(items) - cb(batchWriteError) - } - batchWriteCount = 0 - batchWriteError = null - writtenItems = [] - }) - - afterEach(function() { - table.batchWrite = originalBatchWrite - }) - - it('should reject non-positive ratios', function(done) { - table.throttledBatchWrite(NaN, [], function(err) { - err.should.match(/positive/) - should(batchWriteCount).equal(0) - done() - }) - }) - - it('should write at half capacity without delay', function(done) { - start = Date.now() - table.throttledBatchWrite(0.5, [1], function(err) { - should(Date.now() - start).be.below(1000) - should(batchWriteCount).equal(1) - writtenItems.should.eql([1]) - done() - }) - }) - - it('should write at full capacity without delay', function(done) { - start = Date.now() - table.throttledBatchWrite(1.0, [1, 2], function(err) { - should(Date.now() - start).be.below(1000) - should(batchWriteCount).equal(1) - writtenItems.should.eql([1, 2]) - done() - }) - }) - - it('should write without exceeding capacity', function(done) { - start = Date.now() - table.throttledBatchWrite(1.0, [1, 2, 3, 4, 5], function(err) { - should(Date.now() - start).be.within(2000, 2900) - should(batchWriteCount).equal(3) - writtenItems.should.eql([1, 2, 3, 4, 5]) - done() - }) - }) - - it('should properly handle batchWrite errors', function(done) { - batchWriteError = new Error('test') - table.throttledBatchWrite(1.0, [1, 2, 3, 4, 5], function(err) { - err.should.match(/test/) - done() - }) - }) - }) - - - describe('truncate()', function() { - - before(function(done) { - table.batchWrite([{id: 1, forumName: 'a', subject: 'b'}], done) - }) - - it('should remove everything from the table', function(done) { - table.truncate(function(err) { - if (err) return done(err) - table.scan(function(err, info) { - if (err) return done(err) - info.should.eql([]) - done() - }) - }) - }) - }) - - - describe('addNew()', function() { - - before(function(done) { - table.batchWrite([{id: 1, forumName: 'a', subject: 'b'}], done) - // `dynamo-table-id` mock - table.nextId = function(cb) { cb(null, 2) } - }) - - it('should update existing record if `id` is defined', function(done) { - var record = {id: 1, forumName: 'c', subject: 'd'} - table.addNew(record, function(err) { - if (err) return done(err) - table.scan(function(err, records) { - if (err) return done(err) - records.should.eql([{id: 1, forumName: 'c', subject: 'd'}]) - done() - }) - }) - }) - - it('should create new record if `id` is undefined', function(done) { - var record = {forumName: 'e', subject: 'f'} - table.addNew(record, function(err) { - if (err) return done(err) - table.scan(function(err, records) { - if (err) return done(err) - // dynamo doesn't guarantee any ordering - records.sort(function(a, b) { return a.id - b.id }) - records.should.eql([ - {id: 1, forumName: 'c', subject: 'd'}, - {id: 2, forumName: 'e', subject: 'f'} - ]) - done() - }) - }) - }) - }) - -}) +// TODO: Needs valid tests. Currently there are external tests but +// nothing actually testing this package.