diff --git a/.github/workflows/betaRelease.yml b/.github/workflows/betaRelease.yml new file mode 100644 index 0000000..9134abd --- /dev/null +++ b/.github/workflows/betaRelease.yml @@ -0,0 +1,51 @@ +name: Next Release + +on: + push: + branches: [ beta ] + +env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + +jobs: + build-test-release: + name: Build, test and release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v2-beta + with: + node-version: '12' + + - name: Create ".npmrc" file in the project root + run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc + + - name: Install dependencies + run: yarn --frozen-lockfile + + - name: Check dependencies + run: yarn adio + + - name: Build packages + run: yarn build + + # - name: Run tests + # run: yarn test:dist + + - name: Prepare packages + run: yarn prepack + + - name: Set git email + run: git config --global user.email "info@webiny.com" + + - name: Set git username + run: git config --global user.name "webiny" + + - name: Create a release on GitHub + run: yarn lerna version --conventional-prerelease --yes + + - name: Release packages to NPM + run: yarn lerna publish from-package --dist-tag beta --yes + diff --git a/jest-dynamodb-config.js b/jest-dynamodb-config.js new file mode 100644 index 0000000..9607496 --- /dev/null +++ b/jest-dynamodb-config.js @@ -0,0 +1,50 @@ +module.exports = { + tables: [ + { + TableName: `pk-sk`, + KeySchema: [ + { AttributeName: "pk", KeyType: "HASH" }, + { AttributeName: "sk", KeyType: "RANGE" } + ], + AttributeDefinitions: [ + { AttributeName: "pk", AttributeType: "S" }, + { AttributeName: "sk", AttributeType: "S" }, + { AttributeName: "gsi1pk", AttributeType: "S" }, + { AttributeName: "gsi1sk", AttributeType: "S" }, + { AttributeName: "gsi2pk", AttributeType: "S" }, + { AttributeName: "gsi2sk", AttributeType: "S" } + ], + ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 }, + GlobalSecondaryIndexes: [ + { + IndexName: "gsi1pk_gsi1sk", + KeySchema: [ + { AttributeName: "gsi1pk", KeyType: "HASH" }, + { AttributeName: "gsi1sk", KeyType: "RANGE" } + ], + Projection: { + ProjectionType: "ALL" + }, + ProvisionedThroughput: { + ReadCapacityUnits: 1, + WriteCapacityUnits: 1 + } + }, + { + IndexName: "gsi2pk_gsi2sk", + KeySchema: [ + { AttributeName: "gsi2pk", KeyType: "HASH" }, + { AttributeName: "gsi2sk", KeyType: "RANGE" } + ], + Projection: { + ProjectionType: "ALL" + }, + ProvisionedThroughput: { + ReadCapacityUnits: 1, + WriteCapacityUnits: 1 + } + } + ] + } + ] +}; diff --git a/package.json b/package.json index 7362a82..c977233 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "@babel/preset-env": "^7.4.3", "@babel/preset-flow": "^7.0.0", "adio": "^1.1.1", - "all-contributors-cli": "^5.6.0", "babel-eslint": "^10.0.3", "babel-jest": "^24.7.1", "camelcase": "^5.2.0", @@ -45,8 +44,6 @@ "test:coverage": "yarn test:src --coverage", "test:coverage:coveralls": "npm run test:coverage && cat ./coverage/lcov.info | coveralls", "lint-staged": "lint-staged", - "contributors:add": "all-contributors add", - "contributors:generate": "all-contributors generate", "lerna:version": "lerna version --yes", "lerna:publish": "lerna publish --yes", "lerna:rm-dist": "lerna exec -- rm -rf dist", diff --git a/packages/fields-storage-dynamodb/.babelrc b/packages/fields-storage-dynamodb/.babelrc new file mode 100644 index 0000000..4767347 --- /dev/null +++ b/packages/fields-storage-dynamodb/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + ["@babel/preset-env", { + "targets": { + "node": "8.10" + } + }], + "@babel/preset-flow" + ], + "plugins": [ + ["@babel/plugin-proposal-object-rest-spread", {"useBuiltIns": true}], + ["@babel/plugin-transform-runtime"] + ] +} \ No newline at end of file diff --git a/packages/fields-storage-dynamodb/CHANGELOG.md b/packages/fields-storage-dynamodb/CHANGELOG.md new file mode 100644 index 0000000..3012ba7 --- /dev/null +++ b/packages/fields-storage-dynamodb/CHANGELOG.md @@ -0,0 +1,642 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.0.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@2.0.1-next.0...@commodo/fields-storage-dynamoDb@2.0.1) (2020-07-29) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## 2.0.1-next.0 (2020-07-28) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0 (2020-06-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.28 (2020-06-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.27 (2020-06-04) + + +### Bug Fixes + +* use withStorageName, fallback to withName ([12f7ebe](https://github.com/webiny/commodo/commit/12f7ebe19f0c0301cf792295228be47896b1efae)) + + + + + +# 2.0.0-next.26 (2020-06-03) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.25 (2020-06-02) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.24 (2020-06-02) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.23 (2020-05-31) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.22 (2020-05-31) + + +### Bug Fixes + +* remove unused dependencies ([2baac91](https://github.com/webiny/commodo/commit/2baac9175a21cd05dc071efc54593cf6ca0e0648)) + + + + + +# 2.0.0-next.21 (2020-05-31) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.20 (2020-05-31) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.19 (2020-05-31) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.18 (2020-05-31) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.17 (2020-05-31) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.16 (2020-05-25) + + +### Bug Fixes + +* add missing "nedb-promises" dependency ([36a3172](https://github.com/webiny/commodo/commit/36a317222d49b860f09512fbc48f56a977a0245e)) + + + + + +# 2.0.0-next.15 (2020-05-25) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.14 (2020-05-22) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.13 (2020-05-22) + + +### Features + +* add support for custom id structure ([2a8aa49](https://github.com/webiny/commodo/commit/2a8aa49a6ddc8ff830d1771b662da3dd48bcfd1c)) + + + + + +# 2.0.0-next.12 (2020-05-22) + + +### Bug Fixes + +* add missing dependency ([d9fdb96](https://github.com/webiny/commodo/commit/d9fdb96d96358f50c280566d5da71fd5079cf212)) + + + + + +# 2.0.0-next.11 (2020-05-20) + + +### Bug Fixes + +* improve "isId" regex and fix tests ([7ddaaf5](https://github.com/webiny/commodo/commit/7ddaaf5e6062a1306f2c574bdd34f7cb073441f0)) + + + + + +# 2.0.0-next.10 (2020-05-19) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.9 (2020-05-14) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-next.8 (2020-05-14) + + +### Bug Fixes + +* update to work with the new driver interface ([588beb1](https://github.com/webiny/commodo/commit/588beb1b5c01e14b5eb49ed3cbb5aaa020c29724)) + + + + + +# 2.0.0-next.7 (2020-05-11) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.6 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.5 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.4 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.3 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.2 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.1 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 2.0.0-canary.0 (2020-05-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# [2.0.0-next.0](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@1.0.2...@commodo/fields-storage-dynamoDb@2.0.0-next.0) (2020-05-05) + + +### Bug Fixes + +* do not pass sort/match into aggregation pipeline if not needed ([e619fc9](https://github.com/webiny/commodo/commit/e619fc9282845ea2d0f98779891b8703c853b7b9)) + + +### Features + +* remove page/perPage handling ([b845316](https://github.com/webiny/commodo/commit/b845316ef0c5670b32fba857cbf318dfe730cc5c)) +* use raw data instead of model instances in storage drivers ([7b9e15b](https://github.com/webiny/commodo/commit/7b9e15b6a4883c8d5f28269a3daf97aa2563098d)) + + +### BREAKING CHANGES + +* Storage drivers no longer accept a model instance. They now work with raw data passed from fields-storage layer. +* Handling of page/perPage was removed to make driver more generic. Developers who need this, need to handle parameters before calling the driver, and calculate proper limit/offset themselves. + + + + + +## [1.0.3](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@1.0.2...@commodo/fields-storage-dynamoDb@1.0.3) (2020-01-21) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [1.0.2](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@1.0.3...@commodo/fields-storage-dynamoDb@1.0.2) (2020-01-19) + + +### Bug Fixes + +* update package versions ([aa1831e](https://github.com/webiny/commodo/commit/aa1831e)) + + + + + +## [1.0.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@1.0.3...@commodo/fields-storage-dynamoDb@1.0.1) (2020-01-19) + + +### Bug Fixes + +* update package versions ([aa1831e](https://github.com/webiny/commodo/commit/aa1831e)) + + + + + +## [1.0.3](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@1.0.1...@commodo/fields-storage-dynamoDb@1.0.3) (2020-01-17) + + +### Bug Fixes + +* update versions ([e5b4c61](https://github.com/webiny/commodo/commit/e5b4c61)) +* update versions ([95852d7](https://github.com/webiny/commodo/commit/95852d7)) + + + + + +## [1.0.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@1.0.1...@commodo/fields-storage-dynamoDb@1.0.1) (2020-01-17) + + +### Bug Fixes + +* update versions ([95852d7](https://github.com/webiny/commodo/commit/95852d7)) + + + + + +## [1.0.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.4.0...@commodo/fields-storage-dynamoDb@1.0.1) (2020-01-17) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# [0.4.0](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.4.0-next.0...@commodo/fields-storage-dynamoDb@0.4.0) (2020-01-10) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# [0.4.0-next.0](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.3.0...@commodo/fields-storage-dynamoDb@0.4.0-next.0) (2020-01-07) + + +### Bug Fixes + +* check results received from aggregate ([d1a5313](https://github.com/webiny/commodo/commit/d1a5313)) + + +### Features + +* use $facet to count totalCount, allow old approach too (optional) ([2e81f19](https://github.com/webiny/commodo/commit/2e81f19)) + + + + + +# [0.3.0](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.15...@commodo/fields-storage-dynamoDb@0.3.0) (2019-12-17) + + +### Features + +* add option to skip totalCount query ([e257dab](https://github.com/webiny/commodo/commit/e257dab)) +* remove findByIds ([4c3e7df](https://github.com/webiny/commodo/commit/4c3e7df)) + + + + + +## [0.2.15](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.14...@commodo/fields-storage-dynamoDb@0.2.15) (2019-10-26) + + +### Bug Fixes + +* replace getAttribute with getField ([b0c3aec](https://github.com/webiny/commodo/commit/b0c3aec)) + + + + + +## [0.2.14](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.13...@commodo/fields-storage-dynamoDb@0.2.14) (2019-10-20) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.13](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.12...@commodo/fields-storage-dynamoDb@0.2.13) (2019-10-14) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.12](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.11...@commodo/fields-storage-dynamoDb@0.2.12) (2019-09-26) + + +### Bug Fixes + +* update dependency versions ([2a30863](https://github.com/webiny/commodo/commit/2a30863)) + + + + + +## [0.2.11](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.10...@commodo/fields-storage-dynamoDb@0.2.11) (2019-09-25) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.10](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.9...@commodo/fields-storage-dynamoDb@0.2.10) (2019-09-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.9](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.8...@commodo/fields-storage-dynamoDb@0.2.9) (2019-09-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.8](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.7...@commodo/fields-storage-dynamoDb@0.2.8) (2019-09-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.7](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.5...@commodo/fields-storage-dynamoDb@0.2.7) (2019-09-11) + + +### Bug Fixes + +* upgrade to work with new version of repropose ([0c4c983](https://github.com/webiny/commodo/commit/0c4c983)) + + + + + +## [0.2.5](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.4...@commodo/fields-storage-dynamoDb@0.2.5) (2019-05-24) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.4](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.3...@commodo/fields-storage-dynamoDb@0.2.4) (2019-05-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.3](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.2...@commodo/fields-storage-dynamoDb@0.2.3) (2019-05-09) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.2](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.1...@commodo/fields-storage-dynamoDb@0.2.2) (2019-05-07) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.2.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.2.0...@commodo/fields-storage-dynamoDb@0.2.1) (2019-05-05) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# [0.2.0](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.1.2...@commodo/fields-storage-dynamoDb@0.2.0) (2019-04-28) + + +### Features + +* rename "object" field to "fields" ([3f17ceb](https://github.com/webiny/commodo/commit/3f17ceb)) + + + + + +## [0.1.2](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.1.1...@commodo/fields-storage-dynamoDb@0.1.2) (2019-04-28) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.1.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.1.0...@commodo/fields-storage-dynamoDb@0.1.1) (2019-04-28) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# [0.1.0](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.9...@commodo/fields-storage-dynamoDb@0.1.0) (2019-04-27) + + +### Features + +* add "aggregate" function (specific to DynamoDb) ([466f4fa](https://github.com/webiny/commodo/commit/466f4fa)) + + + + + +## [0.0.9](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.8...@commodo/fields-storage-dynamoDb@0.0.9) (2019-04-24) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.0.8](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.7...@commodo/fields-storage-dynamoDb@0.0.8) (2019-04-24) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.0.7](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.6...@commodo/fields-storage-dynamoDb@0.0.7) (2019-04-24) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.0.6](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.5...@commodo/fields-storage-dynamoDb@0.0.6) (2019-04-24) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.0.5](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.4...@commodo/fields-storage-dynamoDb@0.0.5) (2019-04-24) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.0.4](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.3...@commodo/fields-storage-dynamoDb@0.0.4) (2019-04-23) + + +### Bug Fixes + +* add missing README.md files ([7228d9c](https://github.com/webiny/commodo/commit/7228d9c)) + + + + + +## [0.0.3](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.2...@commodo/fields-storage-dynamoDb@0.0.3) (2019-04-23) + + +### Bug Fixes + +* update keywords in package.json ([cc544ea](https://github.com/webiny/commodo/commit/cc544ea)) + + + + + +## [0.0.2](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.1...@commodo/fields-storage-dynamoDb@0.0.2) (2019-04-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +## [0.0.1](https://github.com/webiny/commodo/compare/@commodo/fields-storage-dynamoDb@0.0.0...@commodo/fields-storage-dynamoDb@0.0.1) (2019-04-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb + + + + + +# 0.0.0 (2019-04-23) + +**Note:** Version bump only for package @commodo/fields-storage-dynamoDb diff --git a/packages/fields-storage-dynamodb/LICENSE b/packages/fields-storage-dynamodb/LICENSE new file mode 100644 index 0000000..b32afdb --- /dev/null +++ b/packages/fields-storage-dynamodb/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Adrian Smijulj + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/fields-storage-dynamodb/README.md b/packages/fields-storage-dynamodb/README.md new file mode 100644 index 0000000..94a3e4d --- /dev/null +++ b/packages/fields-storage-dynamodb/README.md @@ -0,0 +1,16 @@ +# @commodo/fields-storage-dynamoDb + +We're working hard to get all the docs in order. New articles will be added daily. + +In the meantime, take a look at our [Github repo](https://github.com/webiny/webiny-js), it contains tons of examples to get you on track. + +For API examples, take a look at the packages that have an `api-` prefix. Some good packages to study: + +- [@webiny/api-i18n](https://github.com/webiny/webiny-js/tree/master/packages/api-i18n) +- [@webiny/api-page-builder](https://github.com/webiny/webiny-js/tree/master/packages/api-page-builder) +- [@webiny/api-security](https://github.com/webiny/webiny-js/tree/master/packages/api-security) + +If you still can't find what you're looking for, please open an issue and we'll point you in the right direction. + +Thank you for your patience! + diff --git a/packages/fields-storage-dynamodb/__tests__/delete.test.js b/packages/fields-storage-dynamodb/__tests__/delete.test.js new file mode 100644 index 0000000..54923e9 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/delete.test.js @@ -0,0 +1,51 @@ +import { useModels } from "./models"; + +describe("delete test", () => { + const { models, getDocumentClient, id: pk } = useModels(); + + it("should be able to perform delete operation", async () => { + const { SimpleModel } = models; + + const simpleModel = new SimpleModel(); + simpleModel.populate({ + pk, + sk: "something-1", + name: "Something-1", + enabled: true, + tags: ["one", "two", "three"], + age: 55 + }); + + await simpleModel.save(); + + let item = await getDocumentClient() + .get({ + TableName: "pk-sk", + Key: { pk, sk: "something-1" } + }) + .promise(); + + expect(item).toEqual({ + Item: { + sk: "something-1", + name: "Something-1", + pk, + slug: "something1", + enabled: true, + age: 55, + tags: ["one", "two", "three"] + } + }); + + await simpleModel.delete(); + + item = await getDocumentClient() + .get({ + TableName: "pk-sk", + Key: { pk, sk: "something-1" } + }) + .promise(); + + expect(item).toEqual({}); + }); +}); diff --git a/packages/fields-storage-dynamodb/__tests__/find.test.js b/packages/fields-storage-dynamodb/__tests__/find.test.js new file mode 100644 index 0000000..34f3cc2 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/find.test.js @@ -0,0 +1,128 @@ +import { useModels } from "./models"; + +describe("find test", function() { + const { models, getDocumentClient, id: pk } = useModels(); + + beforeEach(async () => { + for (let i = 0; i < 10; i++) { + await getDocumentClient() + .put({ + TableName: "pk-sk", + Item: { + pk, + sk: `something-${i}`, + gsi1pk: `gsi1-${pk}`, + gsi1sk: `gsi1-something-${i}`, + gsi2pk: `gsi2-${pk}`, + gsi2sk: `gsi2-something-${i}`, + name: `Something-${i}`, + enabled: true, + tags: [i], + age: i * 10 + } + }) + .promise(); + } + }); + + it("should be able to perform basic find queries", async () => { + const { SimpleModel } = models; + + const [something0Entry] = await SimpleModel.find({ + query: { pk, sk: "something-0" } + }); + + expect(something0Entry.pk).toBe(pk); + expect(something0Entry.sk).toBe("something-0"); + expect(something0Entry.name).toBe("Something-0"); + expect(something0Entry.enabled).toBe(true); + expect(something0Entry.tags).toEqual([0]); + expect(something0Entry.age).toEqual(0); + + const [something4Entry] = await SimpleModel.find({ + query: { pk: pk, sk: "something-4" } + }); + + expect(something4Entry.pk).toBe(pk); + expect(something4Entry.sk).toBe("something-4"); + expect(something4Entry.name).toBe("Something-4"); + expect(something4Entry.enabled).toBe(true); + expect(something4Entry.tags).toEqual([4]); + expect(something4Entry.age).toEqual(40); + }); + + it("should be able to use basic comparison query operators", async () => { + const { SimpleModel } = models; + + // Should return two instances using the $gte operator. + let results = await SimpleModel.find({ + query: { pk: pk, sk: { $gte: "something-8" } } + }); + + expect(results[0].pk).toBe(pk); + expect(results[0].sk).toBe("something-8"); + expect(results[0].name).toBe("Something-8"); + + expect(results[1].pk).toBe(pk); + expect(results[1].sk).toBe("something-9"); + expect(results[1].name).toBe("Something-9"); + + // Should return 9 instances (zero-indexed item not included in the result set). + results = await SimpleModel.find({ + query: { pk: pk, sk: { $gt: "something-0" } } + }); + + expect(results.length).toBe(9); + + // Should return 10 instances (all items included in the result set). + results = await SimpleModel.find({ + query: { pk: pk, sk: { $gte: "something-0" } } + }); + + expect(results.length).toBe(10); + }); + + it("should be able to use both ascending and descending ordering", async () => { + // TODO + }); + + it("should be able to apply limits", async () => { + const { SimpleModel } = models; + + let results = await SimpleModel.find({ + limit: 3, + query: { pk: pk, sk: { $beginsWith: "something" } } + }); + + expect(results[0].sk).toBe("something-0"); + expect(results[2].sk).toBe("something-2"); + }); + + it("should be able to paginate results", async () => { + // TODO: + }); + + it("should be able query GSIs", async () => { + const { SimpleModel } = models; + + let results = await SimpleModel.find({ + query: { gsi1pk: "gsi1-SimpleModel", gsi1sk: { $beginsWith: "gsi1-something" } } + }); + + expect(results.length).toBe(10); + + expect(results[0].sk).toBe("something-0"); + expect(results[2].sk).toBe("something-2"); + expect(results[9].sk).toBe("something-9"); + + results = await SimpleModel.find({ + query: { gsi2pk: "gsi2-SimpleModel", gsi2sk: { $lte: "gsi2-something-3" } } + }); + + expect(results.length).toBe(4); + + expect(results[0].sk).toBe("something-0"); + expect(results[2].sk).toBe("something-2"); + expect(results[3].sk).toBe("something-3"); + }); +}); diff --git a/packages/fields-storage-dynamodb/__tests__/findOne.test.js b/packages/fields-storage-dynamodb/__tests__/findOne.test.js new file mode 100644 index 0000000..6bf6d05 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/findOne.test.js @@ -0,0 +1,98 @@ +import { useModels } from "./models"; + +describe("find test", function() { + const { models, getDocumentClient, id: pk } = useModels(); + + beforeAll(async () => { + for (let i = 0; i < 10; i++) { + await getDocumentClient() + .put({ + TableName: "pk-sk", + Item: { + pk: pk, + sk: `something-${i}`, + gsi1pk: "gsi1-SimpleModel", + gsi1sk: `gsi1-something-${i}`, + gsi2pk: "gsi2-SimpleModel", + gsi2sk: `gsi2-something-${i}`, + name: `Something-${i}`, + enabled: true, + tags: [i], + age: i * 10 + } + }) + .promise(); + } + }); + + it("should be able to perform basic findOne queries", async () => { + const { SimpleModel } = models; + + const something0Entry = await SimpleModel.findOne({ + query: { pk: pk, sk: "something-0" } + }); + + expect(something0Entry.pk).toBe(pk); + expect(something0Entry.sk).toBe("something-0"); + expect(something0Entry.name).toBe("Something-0"); + expect(something0Entry.enabled).toBe(true); + expect(something0Entry.tags).toEqual([0]); + expect(something0Entry.age).toEqual(0); + + const something4Entry = await SimpleModel.findOne({ + query: { pk: pk, sk: "something-4" } + }); + + expect(something4Entry.pk).toBe(pk); + expect(something4Entry.sk).toBe("something-4"); + expect(something4Entry.name).toBe("Something-4"); + expect(something4Entry.enabled).toBe(true); + expect(something4Entry.tags).toEqual([4]); + expect(something4Entry.age).toEqual(40); + }); + + it("should be able to use basic comparison query operators", async () => { + const { SimpleModel } = models; + + // Should return index 8 instance. + let somethingEntry = await SimpleModel.findOne({ + query: { pk: pk, sk: { $gte: "something-8" } } + }); + + expect(somethingEntry.pk).toBe(pk); + expect(somethingEntry.sk).toBe("something-8"); + expect(somethingEntry.name).toBe("Something-8"); + + // Should return index 9 instance. + + somethingEntry = await SimpleModel.findOne({ + query: { pk: pk, sk: { $gt: "something-8" } } + }); + + expect(somethingEntry.pk).toBe(pk); + expect(somethingEntry.sk).toBe("something-9"); + expect(somethingEntry.name).toBe("Something-9"); + }); + + it("should be able to use both ascending and descending ordering", async () => { + // TODO + }); + + it("should be able query GSIs", async () => { + const { SimpleModel } = models; + + let result = await SimpleModel.findOne({ + query: { gsi1pk: "gsi1-SimpleModel", gsi1sk: "gsi1-something-3" } + }); + + result.sk = 'something-3'; + result.gsi1sk = 'gsi1-something-3'; + + result = await SimpleModel.findOne({ + query: { gsi2pk: "gsi2-SimpleModel", gsi2sk: { $lt: "gsi2-something-3" } } + }); + + result.sk = 'something-2'; + result.gsi1sk = 'gsi1-something-2'; + }); +}); diff --git a/packages/fields-storage-dynamodb/__tests__/models/SimpleModel.js b/packages/fields-storage-dynamodb/__tests__/models/SimpleModel.js new file mode 100644 index 0000000..8d67557 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/models/SimpleModel.js @@ -0,0 +1,34 @@ +import { compose } from "ramda"; +import camelcase from "camelcase"; +import { withName } from "@commodo/name"; +import { withHooks } from "@commodo/hooks"; +import { withFields, string, boolean, number } from "@commodo/fields"; +import { withPrimaryKey, withUniqueKey } from "@commodo/fields-storage"; + +export default base => + compose( + withName("SimpleModel"), + withHooks({ + beforeSave() { + if (this.name) { + this.slug = camelcase(this.name); + } + } + }), + withPrimaryKey("pk", "sk"), + withUniqueKey("gsi1pk", "gsi1sk"), + withUniqueKey("gsi2pk", "gsi2sk"), + withFields({ + pk: string(), + sk: string(), + gsi1pk: string(), + gsi1sk: string(), + gsi2pk: string(), + gsi2sk: string(), + name: string(), + slug: string(), + enabled: boolean({ value: true }), + tags: string({ list: true }), + age: number() + }) + )(base()); diff --git a/packages/fields-storage-dynamodb/__tests__/models/index.js b/packages/fields-storage-dynamodb/__tests__/models/index.js new file mode 100644 index 0000000..6dd1ab6 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/models/index.js @@ -0,0 +1 @@ +export {default as useModels} from "./useModels"; diff --git a/packages/fields-storage-dynamodb/__tests__/models/useModels.js b/packages/fields-storage-dynamodb/__tests__/models/useModels.js new file mode 100644 index 0000000..24bf908 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/models/useModels.js @@ -0,0 +1,45 @@ +import { DocumentClient } from "aws-sdk/clients/dynamodb"; +import { withStorage } from "@commodo/fields-storage"; +import { DynamoDbDriver } from "@commodo/fields-storage-dynamodb"; +import { compose } from "ramda"; +import uniqid from "uniqid"; + +// Models. +import simpleModel from "./SimpleModel"; + +export default () => { + const self = { + models: {}, + documentClient: null, + id: uniqid(), + beforeAll: () => { + self.documentClient = new DocumentClient({ + convertEmptyValues: true, + endpoint: "localhost:8000", + sslEnabled: false, + region: "local-env" + }); + + const base = () => + compose( + withStorage({ + driver: new DynamoDbDriver({ + documentClient: self.documentClient, + tableName: "pk-sk" + }) + }) + )(); + + Object.assign(self.models, { + SimpleModel: simpleModel(base) + }); + }, + getDocumentClient() { + return self.documentClient; + } + }; + + beforeAll(self.beforeAll); + + return self; +}; diff --git a/packages/fields-storage-dynamodb/__tests__/queue.batch.delete.test.js b/packages/fields-storage-dynamodb/__tests__/queue.batch.delete.test.js new file mode 100644 index 0000000..3004589 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/queue.batch.delete.test.js @@ -0,0 +1,99 @@ +import { useModels } from "./models"; +import { Batch } from "@commodo/fields-storage"; + +describe("batch save test", function() { + const { models, getDocumentClient, id: pk } = useModels(); + + beforeAll(async () => { + for (let i = 0; i < 6; i++) { + await getDocumentClient() + .put({ + TableName: "pk-sk", + Item: { + pk: pk, + sk: `something-${i}` + } + }) + .promise(); + } + }); + + it("should be able to batch save, create, and update calls", async () => { + const { SimpleModel } = models; + + const a = new SimpleModel().populate({ pk: pk, sk: "something-0" }); + const b = new SimpleModel().populate({ pk: pk, sk: "something-1" }); + const c = new SimpleModel().populate({ pk: pk, sk: "something-2" }); + + const batchWriteSpy = jest.spyOn(SimpleModel.getStorageDriver().getClient(), "batchWrite"); + const deleteSpy = jest.spyOn(SimpleModel.getStorageDriver().getClient(), "delete"); + + let batch = new Batch([a, "delete"], [b, "delete"], [c, "delete"]); + await batch.execute(); + + expect(batchWriteSpy).toHaveBeenCalledTimes(1); + + let items = await getDocumentClient() + .query({ + TableName: "pk-sk", + KeyConditionExpression: "pk = :pk and sk >= :sk", + ExpressionAttributeValues: { + ":pk": pk, + ":sk": "a" + } + }) + .promise(); + + expect(items).toEqual({ + "Count": 3, + "Items": [ + { + "pk": pk, + "sk": "something-3" + }, + { + "pk": pk, + "sk": "something-4" + }, + { + "pk": pk, + "sk": "something-5" + } + ], + "ScannedCount": 3 + }); + + // Now, try to insert an item via the static "create" method. + batch = new Batch( + [SimpleModel, "delete", { query: { pk: pk, sk: "something-3" } }], + [SimpleModel, "delete", { query: { pk: pk, sk: "something-4" } }], + [SimpleModel, "delete", { query: { pk: pk, sk: "something-5" } }] + ); + + await batch.execute(); + + expect(batchWriteSpy).toHaveBeenCalledTimes(2); + + items = await getDocumentClient() + .query({ + TableName: "pk-sk", + KeyConditionExpression: "pk = :pk and sk >= :sk", + ExpressionAttributeValues: { + ":pk": pk, + ":sk": "a" + } + }) + .promise(); + + expect(items).toEqual({ + "Count": 0, + "Items": [], + "ScannedCount": 0 + }); + + expect(deleteSpy).toHaveBeenCalledTimes(0); + + batchWriteSpy.mockRestore(); + deleteSpy.mockRestore(); + }); +}); diff --git a/packages/fields-storage-dynamodb/__tests__/queue.batch.save.test.js b/packages/fields-storage-dynamodb/__tests__/queue.batch.save.test.js new file mode 100644 index 0000000..bf351b8 --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/queue.batch.save.test.js @@ -0,0 +1,265 @@ +import { useModels } from "./models"; +import { Batch } from "@commodo/fields-storage"; + +describe("batch save test", function() { + const { models, getDocumentClient, id: pk } = useModels(); + + it("should be able to batch save, create, and update calls", async () => { + const { SimpleModel } = models; + + const a = new SimpleModel().populate({ pk: pk, sk: "a" }); + const b = new SimpleModel().populate({ pk: pk, sk: "b" }); + const c = new SimpleModel().populate({ pk: pk, sk: "c" }); + + const batchWriteSpy = jest.spyOn(SimpleModel.getStorageDriver().getClient(), "batchWrite"); + const putSpy = jest.spyOn(SimpleModel.getStorageDriver().getClient(), "put"); + const updateSpy = jest.spyOn(SimpleModel.getStorageDriver().getClient(), "update"); + + let batch = new Batch([a, "save"], [b, "save"], [c, "save"]); + await batch.execute(); + + expect(batchWriteSpy).toHaveBeenCalledTimes(1); + + let items = await getDocumentClient() + .query({ + TableName: "pk-sk", + KeyConditionExpression: "pk = :pk and sk >= :sk", + ExpressionAttributeValues: { + ":pk": pk, + ":sk": "a" + } + }) + .promise(); + + expect(items).toEqual({ + Items: [ + { sk: "a", enabled: true, pk: pk }, + { sk: "b", enabled: true, pk: pk }, + { sk: "c", enabled: true, pk: pk } + ], + Count: 3, + ScannedCount: 3 + }); + + // Now, try to insert an item via the static "create" method. + batch = new Batch( + [SimpleModel, "create", { data: { pk: pk, sk: "d" } }], + [SimpleModel, "create", { data: { pk: pk, sk: "e" } }], + [SimpleModel, "create", { data: { pk: pk, sk: "f" } }] + ); + + await batch.execute(); + + expect(batchWriteSpy).toHaveBeenCalledTimes(2); + + items = await getDocumentClient() + .query({ + TableName: "pk-sk", + KeyConditionExpression: "pk = :pk and sk >= :sk", + ExpressionAttributeValues: { + ":pk": pk, + ":sk": "a" + } + }) + .promise(); + + expect(items).toEqual({ + Count: 6, + Items: [ + { + enabled: true, + pk: pk, + sk: "a" + }, + { + enabled: true, + pk: pk, + sk: "b" + }, + { + enabled: true, + pk: pk, + sk: "c" + }, + { + pk: pk, + sk: "d" + }, + { + pk: pk, + sk: "e" + }, + { + pk: pk, + sk: "f" + } + ], + ScannedCount: 6 + }); + + // Let's do updates now. First, update a, b, and c instances. + a.enabled = false; + b.enabled = false; + c.enabled = false; + + batch = new Batch([a, "save"], [b, "save"], [c, "save"]); + await batch.execute(); + expect(batchWriteSpy).toHaveBeenCalledTimes(3); + + items = await getDocumentClient() + .query({ + TableName: "pk-sk", + KeyConditionExpression: "pk = :pk and sk >= :sk", + ExpressionAttributeValues: { + ":pk": pk, + ":sk": "a" + } + }) + .promise(); + + expect(items).toEqual({ + Count: 6, + Items: [ + { + age: null, + enabled: false, + name: null, + pk: pk, + sk: "a", + slug: null, + tags: null + }, + { + age: null, + enabled: false, + name: null, + pk: pk, + sk: "b", + slug: null, + tags: null + }, + { + age: null, + enabled: false, + name: null, + pk: pk, + sk: "c", + slug: null, + tags: null + }, + { + pk: pk, + sk: "d" + }, + { + pk: pk, + sk: "e" + }, + { + pk: pk, + sk: "f" + } + ], + ScannedCount: 6 + }); + + // Once we have that working, let's update d, e, and f items, which were inserted statically. + batch = new Batch( + [ + SimpleModel, + "update", + { + query: { pk: pk, sk: "d" }, + data: { pk: pk, sk: "d", enabled: false } + } + ], + [ + SimpleModel, + "update", + { + query: { pk: pk, sk: "e" }, + data: { pk: pk, sk: "e", enabled: false } + } + ], + [ + SimpleModel, + "update", + { + query: { pk: pk, sk: "f" }, + data: { pk: pk, sk: "f", enabled: false } + } + ] + ); + + await batch.execute(); + + expect(batchWriteSpy).toHaveBeenCalledTimes(4); + + items = await getDocumentClient() + .query({ + TableName: "pk-sk", + KeyConditionExpression: "pk = :pk and sk >= :sk", + ExpressionAttributeValues: { + ":pk": pk, + ":sk": "a" + } + }) + .promise(); + + expect(items).toEqual({ + Count: 6, + Items: [ + { + age: null, + enabled: false, + name: null, + pk: pk, + sk: "a", + slug: null, + tags: null + }, + { + age: null, + enabled: false, + name: null, + pk: pk, + sk: "b", + slug: null, + tags: null + }, + { + age: null, + enabled: false, + name: null, + pk: pk, + sk: "c", + slug: null, + tags: null + }, + { + pk: pk, + sk: "d", + enabled: false + }, + { + pk: pk, + sk: "e", + enabled: false + }, + { + pk: pk, + sk: "f", + enabled: false + } + ], + ScannedCount: 6 + }); + + expect(putSpy).toHaveBeenCalledTimes(0); + expect(updateSpy).toHaveBeenCalledTimes(0); + + batchWriteSpy.mockRestore(); + putSpy.mockRestore(); + updateSpy.mockRestore(); + }); +}); diff --git a/packages/fields-storage-dynamodb/__tests__/save.test.js b/packages/fields-storage-dynamodb/__tests__/save.test.js new file mode 100644 index 0000000..06165cb --- /dev/null +++ b/packages/fields-storage-dynamodb/__tests__/save.test.js @@ -0,0 +1,62 @@ +import { useModels } from "./models"; + +describe("save test", function() { + const { models, getDocumentClient, id: pk } = useModels(); + + it("should be able to perform create & update operations", async () => { + const { SimpleModel } = models; + const simpleModel = new SimpleModel(); + simpleModel.populate({ + pk, + sk: "something-1", + name: "Something-1", + enabled: true, + tags: ["one", "two", "three"], + age: 55 + }); + + await simpleModel.save(); + + let item = await getDocumentClient() + .get({ + TableName: "pk-sk", + Key: { pk, sk: "something-1" } + }) + .promise(); + + expect(item).toEqual({ + Item: { + sk: "something-1", + name: "Something-1", + pk: pk, + slug: "something1", + enabled: true, + age: 55, + tags: ["one", "two", "three"] + } + }); + + simpleModel.name = "Something-1-edited"; + await simpleModel.save(); + + item = await getDocumentClient() + .get({ + TableName: "pk-sk", + Key: { pk, sk: "something-1" } + }) + .promise(); + + expect(item).toEqual({ + Item: { + sk: "something-1", + name: "Something-1-edited", + pk: pk, + slug: "something1Edited", + enabled: true, + age: 55, + tags: ["one", "two", "three"] + } + }); + + }); +}); diff --git a/packages/fields-storage-dynamodb/jest.config.js b/packages/fields-storage-dynamodb/jest.config.js new file mode 100644 index 0000000..c1cff1c --- /dev/null +++ b/packages/fields-storage-dynamodb/jest.config.js @@ -0,0 +1,6 @@ +const dynamoDbPreset = require("@shelf/jest-dynamodb/jest-preset"); +const base = require("../../jest.config.base"); + +module.exports = { + ...base({ path: __dirname }, [dynamoDbPreset]) +}; diff --git a/packages/fields-storage-dynamodb/package.json b/packages/fields-storage-dynamodb/package.json new file mode 100644 index 0000000..559dffc --- /dev/null +++ b/packages/fields-storage-dynamodb/package.json @@ -0,0 +1,34 @@ +{ + "name": "@commodo/fields-storage-dynamodb", + "version": "2.0.1", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/webiny/commodo.git" + }, + "contributors": [ + "Adrian Smijulj " + ], + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.729.0" + }, + "devDependencies": { + "@shelf/jest-dynamodb": "^1.7.0", + "uniqid": "^5.2.0" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "commodo", + "composeable", + "models", + "storage", + "dynamoDb" + ], + "scripts": { + "build": "babel src --ignore src/__tests__ --out-dir dist --source-maps", + "watch": "yarn build --watch" + } +} diff --git a/packages/fields-storage-dynamodb/src/BatchProcess.js b/packages/fields-storage-dynamodb/src/BatchProcess.js new file mode 100644 index 0000000..b5e0772 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/BatchProcess.js @@ -0,0 +1,64 @@ +class BatchProcess { + constructor(batch, documentClient) { + this.documentClient = documentClient; + this.batchProcess = batch; + + this.queryBuildResolveFunction = null; + this.queryBuilding = new Promise(resolve => { + this.queryBuildResolveFunction = resolve; + }); + + this.queryExecutionResolveFunction = null; + this.queryExecution = new Promise(resolve => { + this.queryExecutionResolveFunction = resolve; + }); + + this.operations = []; + this.results = []; + } + + addOperation(type, operation) { + this.operations.push([type, operation]); + return this.operations.length - 1; + } + + allOperationsAdded() { + return this.operations.length === this.batchProcess.operations.length; + } + + markAsReady() { + this.queryBuildResolveFunction(); + } + + async execute() { + const batchWriteParams = { + RequestItems: {} + }; + + for (let i = 0; i < this.operations.length; i++) { + let [type, { TableName, ...rest }] = this.operations[i]; + + if (!batchWriteParams.RequestItems[TableName]) { + batchWriteParams.RequestItems[TableName] = []; + } + + batchWriteParams.RequestItems[TableName].push({ + [type]: rest + }); + } + + this.results = await this.documentClient.batchWrite(batchWriteParams).promise(); + this.queryExecutionResolveFunction(); + return []; + } + + waitForOperationsAdded() { + return this.queryBuilding; + } + + waitForQueryExecution() { + return this.queryExecution; + } +} + +export default BatchProcess; diff --git a/packages/fields-storage-dynamodb/src/DynamoDbDriver.js b/packages/fields-storage-dynamodb/src/DynamoDbDriver.js new file mode 100644 index 0000000..a10f479 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/DynamoDbDriver.js @@ -0,0 +1,203 @@ +import { DocumentClient } from "aws-sdk/clients/dynamodb"; +import BatchProcess from "./BatchProcess"; +import QueryGenerator from "./QueryGenerator"; + +const propertyIsPartOfUniqueKey = (property, keys) => { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!key.unique) { + continue; + } + + let fields = keys[i].fields; + if (!Array.isArray(fields)) { + continue; + } + + for (let j = 0; j < fields.length; j++) { + let field = fields[j]; + if (field.name === property) { + return true; + } + } + } + return false; +}; + +class DynamoDbDriver { + constructor({ documentClient, tableName } = {}) { + this.batchProcesses = {}; + this.documentClient = documentClient || new DocumentClient(); + this.tableName = tableName; + } + + getClient() { + return this.documentClient; + } + + async create({ name, data, batch }) { + if (!batch) { + return await this.documentClient + .put({ TableName: this.tableName || name, Item: data }) + .promise(); + } + + const batchProcess = this.getBatchProcess(batch); + batchProcess.addOperation("PutRequest", { TableName: this.tableName || name, Item: data }); + + if (batchProcess.allOperationsAdded()) { + batchProcess.execute(); + batchProcess.markAsReady(); + } else { + await batchProcess.waitForOperationsAdded(); + } + + await batchProcess.waitForQueryExecution(); + + return true; + } + + async update({ query, data, name, batch, instance, keys }) { + if (!batch) { + const update = { + UpdateExpression: "SET ", + ExpressionAttributeNames: {}, + ExpressionAttributeValues: {} + }; + + const updateExpression = []; + for (const key in data) { + updateExpression.push(`#${key} = :${key}`); + update.ExpressionAttributeNames[`#${key}`] = key; + update.ExpressionAttributeValues[`:${key}`] = data[key]; + } + + update.UpdateExpression += updateExpression.join(", "); + + return await this.documentClient + .update({ + TableName: this.tableName || name, + Key: query, + ...update + }) + .promise(); + } + + const batchProcess = this.getBatchProcess(batch); + + // It would be nice if we could rely on the received data all the time, but that's not possible. Because + // "PutRequest" operations only insert or overwrite existing data (meaning => classic updates are NOT allowed), + // we must get complete model data, and use that in the operation. This is possible only if the update + // call is part of an model instance update (not part of SomeModel.save() call), where we can access the + // toStorage function. So, if that's the case, we'll call it with the skipDifferenceCheck flag enabled. + // Normally we wouldn't have to do all of this dancing, but it's how DynamoDB works, there's no way around it. + const storageData = instance + ? await instance.toStorage({ skipDifferenceCheck: true }) + : data; + + // The only problem with the above approach is that it may insert null values into GSI columns, + // which immediately gets rejected by DynamoDB. Let's remove those here. + const Item = {}; + for (let property in storageData) { + // Check if key is a part of a unique index. If so, and is null, remove it from data. + if (!propertyIsPartOfUniqueKey(property, keys)) { + Item[property] = storageData[property]; + continue; + } + + if (storageData[property] !== null) { + Item[property] = storageData[property]; + } + } + + batchProcess.addOperation("PutRequest", { + TableName: this.tableName || name, + Item + }); + + if (batchProcess.allOperationsAdded()) { + batchProcess.execute(); + batchProcess.markAsReady(); + } else { + await batchProcess.waitForOperationsAdded(); + } + + await batchProcess.waitForQueryExecution(); + + return true; + } + + async delete({ query, name, batch }) { + if (!batch) { + return await this.documentClient + .delete({ + TableName: this.tableName || name, + Key: query + }) + .promise(); + } + + const batchProcess = this.getBatchProcess(batch); + batchProcess.addOperation("DeleteRequest", { + TableName: this.tableName || name, + Key: query + }); + + if (batchProcess.allOperationsAdded()) { + batchProcess.execute(); + batchProcess.markAsReady(); + } else { + await batchProcess.waitForOperationsAdded(); + } + + await batchProcess.waitForQueryExecution(); + + return true; + } + + async find({ name, query, sort, limit, batch, keys }) { + if (!batch) { + const queryGenerator = new QueryGenerator(); + const queryParams = queryGenerator.generate({ + query, + keys, + sort, + limit, + table: this.tableName || name + }); + + const { Items } = await this.documentClient.query(queryParams).promise(); + return [Items]; + } + + const batchProcess = this.getBatchProcess(batch); + batchProcess.addOperation("DeleteRequest", { + Key: query + }); + + if (batchProcess.allOperationsAdded()) { + batchProcess.execute(); + batchProcess.markAsReady(); + } else { + await batchProcess.waitForOperationsAdded(); + } + + await batchProcess.waitForQueryExecution(); + + return true; + } + + async count() { + throw new Error(`Cannot run "count" operation - not supported.`); + } + + getBatchProcess(batch) { + if (!this.batchProcesses[batch.id]) { + this.batchProcesses[batch.id] = new BatchProcess(batch, this.documentClient); + } + + return this.batchProcesses[batch.id]; + } +} + +export default DynamoDbDriver; diff --git a/packages/fields-storage-dynamodb/src/QueryGenerator.js b/packages/fields-storage-dynamodb/src/QueryGenerator.js new file mode 100644 index 0000000..2176658 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/QueryGenerator.js @@ -0,0 +1,51 @@ +import createKeyConditionExpressionArgs from "./statements/createKeyConditionExpressionArgs"; + +class QueryGenerator { + generate({ query, keys, sort, limit, table }) { + // 1. Which key can we use in this query operation? + const key = this.findQueryKey(query, keys); + + if (!key) { + throw new Error("Cannot perform query - key not found."); + } + + // 2. Now that we know the key, let's separate the key attributes from the rest. + const keyAttributesValues = {}, + nonKeyAttributesValues = {}; + for (let queryKey in query) { + if (key.fields.find(item => item.name === queryKey)) { + keyAttributesValues[queryKey] = query[queryKey]; + } else { + nonKeyAttributesValues[queryKey] = query[queryKey]; + } + } + + const keyConditionExpression = createKeyConditionExpressionArgs({ + query: keyAttributesValues, + sort, + key + }); + + return { ...keyConditionExpression, TableName: table, Limit: limit }; + } + + findQueryKey(query = {}, keys = []) { + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let hasAllFields = true; + for (let j = 0; j < key.fields.length; j++) { + let field = key.fields[j]; + if (!query[field.name]) { + hasAllFields = false; + break; + } + } + + if (hasAllFields) { + return key; + } + } + } +} + +export default QueryGenerator; diff --git a/packages/fields-storage-dynamodb/src/index.js b/packages/fields-storage-dynamodb/src/index.js new file mode 100644 index 0000000..15d78cf --- /dev/null +++ b/packages/fields-storage-dynamodb/src/index.js @@ -0,0 +1,4 @@ +// @flow +import { default as DynamoDbDriver } from "./DynamoDbDriver"; + +export { DynamoDbDriver }; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/beginsWith.js b/packages/fields-storage-dynamodb/src/operators/comparison/beginsWith.js new file mode 100644 index 0000000..a846dec --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/beginsWith.js @@ -0,0 +1,12 @@ +const beginsWith = { + canProcess: ({ value }) => { + return value && typeof value["$beginsWith"] !== "undefined"; + }, + process: ({ key, value, args }) => { + args.expression += `begins_with (#${key}, :${key})`; + args.attributeNames[`#${key}`] = key; + args.attributeValues[`:${key}`] = value["$beginsWith"]; + } +}; + +export default beginsWith; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/between.js b/packages/fields-storage-dynamodb/src/operators/comparison/between.js new file mode 100644 index 0000000..0ed7f75 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/between.js @@ -0,0 +1,19 @@ +const between = { + canProcess: ({ value }) => { + return value && typeof value["$between"] !== "undefined"; + }, + process: ({ key, value }) => { + return { + statement: `#${key} BETWEEN :${key}Gte AND :${key}Lte`, + attributeNames: { + [`#${key}`]: key + }, + attributeValues: { + [`:${key}Gte`]: value[0], + [`:${key}Lte`]: value[1] + } + }; + } +}; + +export default between; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/eq.js b/packages/fields-storage-dynamodb/src/operators/comparison/eq.js new file mode 100644 index 0000000..29f2168 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/eq.js @@ -0,0 +1,22 @@ +const validTypes = ["string", "boolean", "number"]; + +const eq = { + canProcess: ({ key, value }) => { + if (key && key.charAt(0) === "$") { + return false; + } + + if (value && typeof value["$eq"] !== "undefined") { + return true; + } + + return validTypes.includes(typeof value); + }, + process: ({ key, value, args }) => { + args.expression += `#${key} = :${key}`; + args.attributeNames[`#${key}`] = key; + args.attributeValues[`:${key}`] = value; + } +}; + +export default eq; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/gt.js b/packages/fields-storage-dynamodb/src/operators/comparison/gt.js new file mode 100644 index 0000000..29ab05c --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/gt.js @@ -0,0 +1,12 @@ +const gt = { + canProcess: ({ value }) => { + return value && typeof value["$gt"] !== "undefined"; + }, + process: ({ key, value, args }) => { + args.expression += `#${key} > :${key}`; + args.attributeNames[`#${key}`] = key; + args.attributeValues[`:${key}`] = value["$gt"]; + } +}; + +export default gt; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/gte.js b/packages/fields-storage-dynamodb/src/operators/comparison/gte.js new file mode 100644 index 0000000..6fc0384 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/gte.js @@ -0,0 +1,12 @@ +const gte = { + canProcess: ({ value }) => { + return value && typeof value["$gte"] !== "undefined"; + }, + process: ({ key, value, args }) => { + args.expression += `#${key} >= :${key}`; + args.attributeNames[`#${key}`] = key; + args.attributeValues[`:${key}`] = value["$gte"] + } +}; + +export default gte; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/lt.js b/packages/fields-storage-dynamodb/src/operators/comparison/lt.js new file mode 100644 index 0000000..db91ce3 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/lt.js @@ -0,0 +1,12 @@ +const lt = { + canProcess: ({ value }) => { + return value && typeof value["$lt"] !== "undefined"; + }, + process: ({ key, value, args }) => { + args.expression += `#${key} < :${key}`; + args.attributeNames[`#${key}`] = key; + args.attributeValues[`:${key}`] = value["$lt"] + } +}; + +export default lt; diff --git a/packages/fields-storage-dynamodb/src/operators/comparison/lte.js b/packages/fields-storage-dynamodb/src/operators/comparison/lte.js new file mode 100644 index 0000000..711b01a --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/comparison/lte.js @@ -0,0 +1,12 @@ +const lte = { + canProcess: ({ value }) => { + return value && typeof value["$lte"] !== "undefined"; + }, + process: ({ key, value, args }) => { + args.expression += `#${key} <= :${key}`; + args.attributeNames[`#${key}`] = key; + args.attributeValues[`:${key}`] = value["$lte"] + } +}; + +export default lte; diff --git a/packages/fields-storage-dynamodb/src/operators/index.js b/packages/fields-storage-dynamodb/src/operators/index.js new file mode 100644 index 0000000..12f1305 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/index.js @@ -0,0 +1,20 @@ +// Comparison operators (A-Z) +import $and from "./logical/and"; +import $beginsWith from "./comparison/beginsWith"; +import $between from "./comparison/between"; +import $gt from "./comparison/gt"; +import $gte from "./comparison/gte"; +import $lt from "./comparison/lt"; +import $lte from "./comparison/lte"; +import $eq from "./comparison/eq"; + +module.exports = { + $and, + $beginsWith, + $between, + $eq, + $gt, + $gte, + $lt, + $lte +}; diff --git a/packages/fields-storage-dynamodb/src/operators/logical/and.js b/packages/fields-storage-dynamodb/src/operators/logical/and.js new file mode 100644 index 0000000..bc005a0 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/logical/and.js @@ -0,0 +1,62 @@ +const isObject = value => value && typeof value === "object"; + +const and = { + canProcess: ({ key }) => { + return key === "$and"; + }, + process: ({ value, args, processStatement }) => { + const andArgs = { + expression: "", + attributeNames: {}, + attributeValues: {} + }; + + switch (true) { + /* case Array.isArray(value): + value.forEach(object => { + for (const [andKey, andValue] of Object.entries(object)) { + if (andArgs.expression === "") { + processStatement({ + args: andArgs, + query: { [andKey]: andValue } + }); + } else { + andExpression += + " AND " + processStatement({ args: andArgs, query: { [andKey]: andValue } }); + + } + } + }); + break;*/ + case isObject(value): { + for (const [andKey, andValue] of Object.entries(value)) { + const currentArgs = { + expression: "", + attributeNames: {}, + attributeValues: {} + }; + + processStatement({ args: currentArgs, query: { [andKey]: andValue } }); + + Object.assign(andArgs.attributeNames, currentArgs.attributeNames); + Object.assign(andArgs.attributeValues, currentArgs.attributeValues); + + if (andArgs.expression === "") { + andArgs.expression = currentArgs.expression; + } else { + andArgs.expression += " and " + currentArgs.expression; + } + } + break; + } + default: + throw Error("$and operator must receive an object or an array."); + } + + args.expression += "(" + andArgs.expression + ")"; + Object.assign(args.attributeNames, andArgs.attributeNames); + Object.assign(args.attributeValues, andArgs.attributeValues); + } +}; + +export default and; diff --git a/packages/fields-storage-dynamodb/src/operators/logical/or.js b/packages/fields-storage-dynamodb/src/operators/logical/or.js new file mode 100644 index 0000000..501ccb4 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/operators/logical/or.js @@ -0,0 +1,40 @@ +// @flow +import _ from "lodash"; +import type { Operator } from "../../../types"; + +const or: Operator = { + canProcess: ({ key }) => { + return key === "$or"; + }, + process: ({ value, statement }) => { + let output = ""; + switch (true) { + case _.isArray(value): + value.forEach(object => { + for (const [orKey, orValue] of Object.entries(object)) { + if (output === "") { + output = statement.process({ [orKey]: orValue }); + } else { + output += " OR " + statement.process({ [orKey]: orValue }); + } + } + }); + break; + case _.isPlainObject(value): + for (const [orKey, orValue] of Object.entries(value)) { + if (output === "") { + output = statement.process({ [orKey]: orValue }); + } else { + output += " OR " + statement.process({ [orKey]: orValue }); + } + } + break; + default: + throw Error("$or operator must receive an object or an array."); + } + + return "(" + output + ")"; + } +}; + +export default or; diff --git a/packages/fields-storage-dynamodb/src/statements/createKeyConditionExpressionArgs.js b/packages/fields-storage-dynamodb/src/statements/createKeyConditionExpressionArgs.js new file mode 100644 index 0000000..8d64f01 --- /dev/null +++ b/packages/fields-storage-dynamodb/src/statements/createKeyConditionExpressionArgs.js @@ -0,0 +1,28 @@ +import processStatement from "./processStatement"; + +export default ({ query, sort, key }) => { + const args = { + expression: "", + attributeNames: {}, + attributeValues: {} + }; + + processStatement({ args, query: { $and: query } }); + + const output = { + KeyConditionExpression: args.expression, + ExpressionAttributeNames: args.attributeNames, + ExpressionAttributeValues: args.attributeValues + }; + + const sortKey = key.fields && key.fields[1]; + if (sort && sort[sortKey.name] === -1) { + output.ScanIndexForward = true; + } + + if (!key.primary) { + output.IndexName = key.name; + } + + return output; +}; diff --git a/packages/fields-storage-dynamodb/src/statements/processStatement.js b/packages/fields-storage-dynamodb/src/statements/processStatement.js new file mode 100644 index 0000000..10ffc4c --- /dev/null +++ b/packages/fields-storage-dynamodb/src/statements/processStatement.js @@ -0,0 +1,15 @@ +import allOperators from "./../operators"; + +export default function processStatement({ args, query }) { + outerLoop: for (const [key, value] of Object.entries(query)) { + const operators = Object.values(allOperators); + for (let i = 0; i < operators.length; i++) { + const operator = operators[i]; + if (operator.canProcess({ key, value, args })) { + operator.process({ key, value, args, processStatement }); + continue outerLoop; + } + } + throw new Error(`Invalid operator {${key} : ${(value: any)}}.`); + } +} diff --git a/packages/fields-storage/__tests__/maxPerPage.test.js b/packages/fields-storage/__tests__/maxPerPage.test.js index 4c61746..5e7f298 100644 --- a/packages/fields-storage/__tests__/maxPerPage.test.js +++ b/packages/fields-storage/__tests__/maxPerPage.test.js @@ -4,69 +4,69 @@ import { withName } from "@commodo/name"; import { compose } from "ramda"; import { CustomDriver } from "./resources/CustomDriver"; -const EntityWithoutMaxPerPage = compose( +const EntityWithoutMaxLimit = compose( withFields({ name: string() }), - withName("EntityWithoutMaxPerPage"), + withName("EntityWithoutMaxLimit"), withStorage({ driver: new CustomDriver() }) )(); -const EntityWithMaxPerPage = compose( +const EntityWithMaxLimit = compose( withFields({ name: string() }), - withName("EntityWithMaxPerPage"), + withName("EntityWithMaxLimit"), withStorage({ driver: new CustomDriver(), - maxPerPage: 500 + maxLimit: 500 }) )(); -const EntityWithMaxPerPageSetToNull = compose( +const EntityWithMaxLimitSetToNull = compose( withFields({ name: string() }), - withName("EntityWithMaxPerPage"), + withName("EntityWithMaxLimit"), withStorage({ driver: new CustomDriver(), - maxPerPage: null + maxLimit: null }) )(); -describe("maxPerPage test", () => { - test("must throw errors if maxPerPage config parameter was exceeded", async () => { - await EntityWithoutMaxPerPage.find({ limit: 99 }); - await EntityWithoutMaxPerPage.find({ limit: 100 }); +describe("maxLimit test", () => { + test("must throw errors if maxLimit config parameter was exceeded", async () => { + await EntityWithoutMaxLimit.find({ limit: 99 }); + await EntityWithoutMaxLimit.find({ limit: 100 }); let error = null; try { - await EntityWithoutMaxPerPage.find({ limit: 101 }); + await EntityWithoutMaxLimit.find({ limit: 101 }); } catch (e) { error = e; } expect(error.message).toBe("Cannot query for more than 100 models per page."); - await EntityWithMaxPerPage.find({ limit: 499 }); - await EntityWithMaxPerPage.find({ limit: 500 }); + await EntityWithMaxLimit.find({ limit: 499 }); + await EntityWithMaxLimit.find({ limit: 500 }); error = null; try { - await EntityWithMaxPerPage.find({ limit: 501 }); + await EntityWithMaxLimit.find({ limit: 501 }); } catch (e) { error = e; } expect(error.message).toBe("Cannot query for more than 500 models per page."); - await EntityWithMaxPerPageSetToNull.find({ limit: 100 }); + await EntityWithMaxLimitSetToNull.find({ limit: 100 }); error = null; try { - await EntityWithoutMaxPerPage.find({ limit: 101 }); + await EntityWithoutMaxLimit.find({ limit: 101 }); } catch (e) { error = e; } diff --git a/packages/fields-storage/package.json b/packages/fields-storage/package.json index af7aa3e..5f0d218 100644 --- a/packages/fields-storage/package.json +++ b/packages/fields-storage/package.json @@ -35,6 +35,7 @@ "storage" ], "scripts": { - "build": "babel src --ignore src/__tests__ --out-dir dist --source-maps" + "build": "babel src --ignore src/__tests__ --out-dir dist --source-maps", + "watch": "yarn build --watch" } } diff --git a/packages/fields-storage/src/Batch.js b/packages/fields-storage/src/Batch.js new file mode 100644 index 0000000..cc891c8 --- /dev/null +++ b/packages/fields-storage/src/Batch.js @@ -0,0 +1,27 @@ +class Batch { + constructor(...operations) { + this.operations = operations; + this.type = "batch"; + this.id = Math.random() + .toString(36) + .slice(-6); // e.g. tfz58m + } + + push(...items) { + this.operations.push(...items); + } + + async execute() { + const promises = []; + for (let i = 0; i < this.operations.length; i++) { + const [model, operation, args] = this.operations[i]; + promises.push(model[operation]({ ...args, batch: this })); + } + + await Promise.all(promises); + + return this; + } +} + +export default Batch; diff --git a/packages/fields-storage/src/StoragePool.js b/packages/fields-storage/src/StoragePool.js index 45cbddd..1b82feb 100644 --- a/packages/fields-storage/src/StoragePool.js +++ b/packages/fields-storage/src/StoragePool.js @@ -1,6 +1,20 @@ // @flow import { getName } from "@commodo/name"; import StoragePoolEntry from "./StoragePoolEntry"; +import getPrimaryKey from "./getPrimaryKey"; + +function getPoolItemId(model, data) { + const primaryKey = getPrimaryKey(model); + const output = { namespace: model.getStorageName(), id: [] }; + + for (let i = 0; i < primaryKey.fields.length; i++) { + let field = primaryKey.fields[i]; + output.id.push(data ? data[field.name] : model[field.name]); + } + + output.id.join(":"); + return output; +} class StoragePool { pool: {}; @@ -13,43 +27,32 @@ class StoragePool { } add(model: $Subtype): this { - const namespace = getName(model); + const { namespace, id } = getPoolItemId(model); if (!this.getPool()[namespace]) { this.getPool()[namespace] = {}; } - this.getPool()[namespace][model.id] = new StoragePoolEntry(model); + this.getPool()[namespace][id] = new StoragePoolEntry(model); return this; } - has(model, id): boolean { - const namespace = getName(model); - if (!this.getPool()[namespace]) { - return false; - } - - const modelId = id || model.id; - return typeof this.getPool()[namespace][modelId] !== "undefined"; - } - remove(model): this { - const namespace = getName(model); + const { namespace, id } = getPoolItemId(model); if (!this.getPool()[namespace]) { return this; } - delete this.getPool()[namespace][model.id]; + delete this.getPool()[namespace][id]; return this; } - get(model: Class<$Subtype> | CreateModel, id: ?mixed): ?CreateModel { - const namespace = getName(model); + get(model, data: any = null) { + const { namespace, id } = getPoolItemId(model, data); if (!this.getPool()[namespace]) { return undefined; } - const modelId = id || model.id; - const poolEntry: StoragePoolEntry = this.getPool()[namespace][modelId]; + const poolEntry: StoragePoolEntry = this.getPool()[namespace][id]; if (poolEntry) { return poolEntry.getModel(); } diff --git a/packages/fields-storage/src/Transaction.js b/packages/fields-storage/src/Transaction.js new file mode 100644 index 0000000..0f80459 --- /dev/null +++ b/packages/fields-storage/src/Transaction.js @@ -0,0 +1,23 @@ +// @flow +import Batch from "./Batch"; + +class Transaction extends Batch { + constructor(...operations) { + super(...operations); + this.type = "transaction"; + } + + async execute() { + for (let i = 0; i < this.operations.length; i++) { + const item = this.operations[i]; + if (Array.isArray(item)) { + await this.operations[i][0]({ ...this.operations[i][1], batch: this }); + } else { + await this.operations[i]({ batch: this }); + } + } + return this; + } +} + +export default Transaction; diff --git a/packages/fields-storage/src/getKeys.js b/packages/fields-storage/src/getKeys.js new file mode 100644 index 0000000..45433ad --- /dev/null +++ b/packages/fields-storage/src/getKeys.js @@ -0,0 +1,17 @@ +const getPrimaryKey = model => { + if (!model) { + return []; + } + + if (Array.isArray(model.__storageKeys)) { + return model.__storageKeys; + } + + if (model.constructor && Array.isArray(model.constructor.__storageKeys)) { + return model.constructor.__storageKeys; + } + + return []; +}; + +export default getPrimaryKey; diff --git a/packages/fields-storage/src/getPrimaryKey.js b/packages/fields-storage/src/getPrimaryKey.js new file mode 100644 index 0000000..d34b15b --- /dev/null +++ b/packages/fields-storage/src/getPrimaryKey.js @@ -0,0 +1,7 @@ +import getKeys from "./getKeys"; + +const getPrimaryKey = model => { + return getKeys(model).find(item => item.primary); +}; + +export default getPrimaryKey; diff --git a/packages/fields-storage/src/idGenerator.js b/packages/fields-storage/src/idGenerator.js deleted file mode 100644 index a789aaf..0000000 --- a/packages/fields-storage/src/idGenerator.js +++ /dev/null @@ -1,5 +0,0 @@ -import mdbid from "mdbid"; - -export default { - generate: mdbid -} diff --git a/packages/fields-storage/src/index.js b/packages/fields-storage/src/index.js index cca2b51..459bd26 100644 --- a/packages/fields-storage/src/index.js +++ b/packages/fields-storage/src/index.js @@ -8,3 +8,8 @@ export { default as hasWithStorage } from "./hasWithStorage"; export { default as withStorageName } from "./withStorageName"; export { default as getStorageName } from "./getStorageName"; export { default as hasStorageName } from "./hasStorageName"; +export { default as withPrimaryKey } from "./withPrimaryKey"; +export { default as withKey } from "./withKey"; +export { default as Batch } from "./Batch"; +export { default as withUniqueKey } from "./withUniqueKey"; +export { default as getKeys } from "./getKeys"; diff --git a/packages/fields-storage/src/withKey.js b/packages/fields-storage/src/withKey.js new file mode 100644 index 0000000..36b6d18 --- /dev/null +++ b/packages/fields-storage/src/withKey.js @@ -0,0 +1,28 @@ +import { withStaticProps } from "repropose"; + +const withKey = (...params) => { + let newKey; + if (typeof params[0] === "string") { + newKey = { + fields: params.map(item => ({ name: item })) + }; + } else { + newKey = params[0]; + } + + if (!newKey.name) { + newKey.name = newKey.fields.map(item => item.name).join("_"); + } + + return function(fn) { + if (!fn.__storageKeys) { + withStaticProps({ __storageKeys: [newKey] })(fn); + } else { + fn.__storageKeys.push(newKey); + } + + return fn; + }; +}; + +export default withKey; diff --git a/packages/fields-storage/src/withPrimaryKey.js b/packages/fields-storage/src/withPrimaryKey.js new file mode 100644 index 0000000..0e6bfb7 --- /dev/null +++ b/packages/fields-storage/src/withPrimaryKey.js @@ -0,0 +1,19 @@ +import withKey from "./withKey"; + +const withPrimaryKey = (...params) => { + let key; + if (typeof params[0] === "string") { + key = { + fields: params.map(item => ({ name: item })) + }; + } else { + key = params[0]; + } + + key.primary = true; + key.unique = true; + + return withKey(key); +}; + +export default withPrimaryKey; diff --git a/packages/fields-storage/src/withStorage.js b/packages/fields-storage/src/withStorage.js index c044ade..51158c7 100644 --- a/packages/fields-storage/src/withStorage.js +++ b/packages/fields-storage/src/withStorage.js @@ -1,8 +1,9 @@ // @flow import { getName as defaultGetName } from "@commodo/name"; import getStorageName from "./getStorageName"; +import getPrimaryKey from "./getPrimaryKey"; +import getKeys from "./getKeys"; import { withStaticProps, withProps } from "repropose"; -import cloneDeep from "lodash.clonedeep"; import { withHooks } from "@commodo/hooks"; import type { SaveParams } from "@commodo/fields-storage/types"; import WithStorageError from "./WithStorageError"; @@ -10,13 +11,16 @@ import Collection from "./Collection"; import StoragePool from "./StoragePool"; import FieldsStorageAdapter from "./FieldsStorageAdapter"; import { decodeCursor, encodeCursor } from "./cursor"; -import idGenerator from "./idGenerator"; + +// TODO: check faster alternative. +import cloneDeep from "lodash.clonedeep"; + interface IStorageDriver {} type Configuration = { storagePool?: StoragePool, driver?: IStorageDriver, - maxPerPage: ?number + maxLimit: ?number }; const defaults = { @@ -28,8 +32,6 @@ const defaults = { } }; -const generateId = () => idGenerator.generate(); - const hook = async (name, { options, model }) => { if (options.hooks[name] === false) { return; @@ -37,7 +39,7 @@ const hook = async (name, { options, model }) => { await model.hook(name, { model, options }); }; -const registerSaveUpdateCreateHooks = async (prefix, { existing, model, options }) => { +const triggerSaveUpdateCreateHooks = async (prefix, { existing, model, options }) => { await hook(prefix + "Save", { model, options }); if (existing) { await hook(prefix + "Update", { model, options }); @@ -46,7 +48,7 @@ const registerSaveUpdateCreateHooks = async (prefix, { existing, model, options } }; -const getName = (instance) => { +const getName = instance => { return getStorageName(instance) || defaultGetName(instance); }; @@ -78,27 +80,12 @@ function cursorFrom(data, keys) { const withStorage = (configuration: Configuration) => { return baseFn => { - let fn = withHooks({ - delete() { - if (!this.id) { - throw new WithStorageError( - "Cannot delete before saving to storage.", - WithStorageError.CANNOT_DELETE_NO_ID - ); - } - } - })(baseFn); - - fn = withProps(props => ({ + let fn = withProps(props => ({ __withStorage: { existing: false, processing: false, fieldsStorageAdapter: new FieldsStorageAdapter() }, - generateId, - isId(value) { - return typeof value === "string" && !!value.match(/^[a-zA-Z0-9]*$/); - }, isExisting() { return this.__withStorage.existing; }, @@ -106,8 +93,15 @@ const withStorage = (configuration: Configuration) => { this.__withStorage.existing = existing; return this; }, - async save(options: ?SaveParams): Promise { - options = { ...options, ...defaults.save }; + async save(rawArgs: ?SaveParams): Promise { + const primaryKey = getPrimaryKey(this); + if (!primaryKey) { + throw Error( + `Cannot save "${this.getStorageName()}" model, no primary key defined.` + ); + } + + const args = { ...defaults.save, ...cloneDeep(rawArgs) }; if (this.__withStorage.processing) { return; @@ -117,104 +111,113 @@ const withStorage = (configuration: Configuration) => { const existing = this.isExisting(); - await registerSaveUpdateCreateHooks("before", { existing, model: this, options }); + await triggerSaveUpdateCreateHooks("before", { + existing, + model: this, + options: args + }); try { - await hook("__save", { model: this, options }); + await hook("__save", { model: this, options: args }); if (existing) { - await hook("__update", { model: this, options }); + await hook("__update", { model: this, options: args }); } else { - await hook("__create", { model: this, options }); + await hook("__create", { model: this, options: args }); } - options.validation !== false && (await this.validate()); + args.validation !== false && (await this.validate()); - await registerSaveUpdateCreateHooks("__before", { + await triggerSaveUpdateCreateHooks("__before", { existing, model: this, - options + options: args }); if (this.isDirty()) { - if (!this.id) { - this.id = this.constructor.generateId(); - } + const data = await this.toStorage(); if (existing) { - const { getId } = options; - await this.getStorageDriver().update([ - { - name: getName(this), - query: - typeof getId === "function" ? getId(this) : { id: this.id }, - data: await this.toStorage() - } - ]); + const query = {}; + for (let i = 0; i < primaryKey.fields.length; i++) { + let field = primaryKey.fields[i]; + query[field.name] = this[field.name]; + } + + await this.constructor.update({ + batch: args.batch, + instance: this, + data, + query + }); } else { - await this.getStorageDriver().create([ - { - name: getName(this), - data: await this.toStorage() - } - ]); + await this.constructor.create({ + batch: args.batch, + instance: this, + data + }); } } - await registerSaveUpdateCreateHooks("__after", { + await triggerSaveUpdateCreateHooks("__after", { existing, model: this, - options + options: args }); this.setExisting(); this.clean(); this.constructor.getStoragePool().add(this); - } catch (e) { - if (!existing) { - this.getField("id").reset(); - } - throw e; } finally { this.__withStorage.processing = null; } - await registerSaveUpdateCreateHooks("after", { existing, model: this, options }); + await triggerSaveUpdateCreateHooks("after", { + existing, + model: this, + options: args + }); }, /** * Deletes current and all linked models (if autoDelete on the attribute was enabled). - * @param options */ - async delete(options: ?Object) { + async delete(rawArgs) { + const primaryKey = getPrimaryKey(this); + if (!primaryKey) { + throw Error( + `Cannot delete "${this.getStorageName()}" model, no primary key defined.` + ); + } + if (this.__withStorage.processing) { return; } this.__withStorage.processing = "delete"; - options = { ...options, ...defaults.delete }; - - let getId; - - ({ getId, ...options } = options); + const args = { ...defaults.delete, ...cloneDeep(rawArgs) }; try { - await this.hook("delete", { options, model: this }); + await this.hook("delete", { options: args, model: this }); - options.validation !== false && (await this.validate()); + args.validation !== false && (await this.validate()); - await this.hook("beforeDelete", { options, model: this }); + await this.hook("beforeDelete", { options: args, model: this }); - await this.getStorageDriver().delete({ - name: getName(this), - options: { - query: typeof getId === "function" ? getId(this) : { id: this.id }, - ...options - } + const query = {}; + for (let i = 0; i < primaryKey.fields.length; i++) { + let field = primaryKey.fields[i]; + query[field.name] = this[field.name]; + } + + await this.constructor.delete({ + batch: args.batch, + instance: this, + query }); - await this.hook("afterDelete", { options, model: this }); + await this.hook("afterDelete", { options: args, model: this }); this.constructor.getStoragePool().remove(this); } finally { @@ -226,6 +229,10 @@ const withStorage = (configuration: Configuration) => { return this.constructor.__withStorage.driver; }, + getStorageName() { + return getName(this); + }, + async populateFromStorage(data: Object) { await this.__withStorage.fieldsStorageAdapter.fromStorage({ data, @@ -240,7 +247,7 @@ const withStorage = (configuration: Configuration) => { skipDifferenceCheck }); } - }))(fn); + }))(baseFn); fn = withStaticProps(() => { const __withStorage = { @@ -276,18 +283,85 @@ const withStorage = (configuration: Configuration) => { getStorageDriver() { return this.__withStorage.driver; }, - isId(value) { - return typeof value === "string" && !!value.match(/^[0-9a-fA-F]{24}$/); + getStorageName() { + return getName(this); }, - generateId, - async find(options: ?FindParams) { - if (!options) { - options = {}; + + /** + * Inserts an entry into the database. + */ + async create(args) { + const { data, batch, ...rest } = cloneDeep(args); + + return this.getStorageDriver().create({ + ...rest, + model: this, + name: getName(this), + keys: getKeys(this), + primaryKey: getPrimaryKey(this), + data, + batch + }); + }, + + /** + * Updates an existing entry in the database. + */ + async update(args = {}) { + const { data, query, batch, ...rest } = cloneDeep(args); + + return this.getStorageDriver().update({ + ...rest, + model: this, + name: getName(this), + keys: getKeys(this), + primaryKey: getPrimaryKey(this), + data, + query, + batch + }); + }, + + /** + * Deletes an existing entry in the database. + */ + async delete(args = {}) { + const { data, query, batch, ...rest } = cloneDeep(args); + + return this.getStorageDriver().delete({ + ...rest, + model: this, + name: getName(this), + keys: getKeys(this), + primaryKey: getPrimaryKey(this), + data, + query, + batch + }); + }, + + /** + * Finds one model matched by given query parameters. + * @param rawArgs + */ + async findOne(rawArgs) { + // TODO: don't load if not necessary, check storage poll first. + if (!rawArgs) { + rawArgs = {}; } - const maxPerPage = this.__withStorage.maxPerPage || 100; + const args = cloneDeep(rawArgs); + args.limit = 1; + + const [result] = await this.find(args); + return result; + }, + + async find(args = {}) { + const maxLimit = this.__withStorage.maxLimit || 100; let { + batch, query = {}, sort, limit, @@ -296,17 +370,17 @@ const withStorage = (configuration: Configuration) => { totalCount: countTotal = false, defaultSortField = "id", ...other - } = options; + } = args; if (!sort) { sort = {}; } - limit = Number.isInteger(limit) && limit > 0 ? limit : maxPerPage; + limit = Number.isInteger(limit) && limit > 0 ? limit : maxLimit; - if (limit > maxPerPage) { + if (limit > maxLimit) { throw new WithStorageError( - `Cannot query for more than ${maxPerPage} models per page.`, + `Cannot query for more than ${maxLimit} models per page.`, WithStorageError.MAX_PER_PAGE_EXCEEDED ); } @@ -361,9 +435,14 @@ const withStorage = (configuration: Configuration) => { } const params = { query, sort, limit: limit + 1, ...other }; - let [results, meta] = await this.getStorageDriver().find({ + let [results, meta = {}] = await this.getStorageDriver().find({ + ...params, + model: this, name: getName(this), - options: params + keys: getKeys(this), + primaryKey: getPrimaryKey(this), + query, + batch }); // Have we reached the last record? @@ -379,11 +458,9 @@ const withStorage = (configuration: Configuration) => { let totalCount = null; if (countTotal) { totalCount = await this.getStorageDriver().count({ - name: getName(this), - options: { - query: originalQuery, - ...other - } + query: originalQuery, + ...other, + name: getName(this) }); } @@ -414,7 +491,7 @@ const withStorage = (configuration: Configuration) => { const result: ?Array = results; if (result instanceof Array) { for (let i = 0; i < result.length; i++) { - const pooled = this.getStoragePool().get(this, result[i].id); + const pooled = this.getStoragePool().get(this, result[i]); if (pooled) { collection.push(pooled); } else { @@ -429,60 +506,6 @@ const withStorage = (configuration: Configuration) => { return collection; }, - /** - * Finds a single model matched by given ID. - * @param id - * @param options - */ - async findById(id: mixed, options: ?Object): Promise { - if (!id || !this.isId(id)) { - return null; - } - - const pooled = this.getStoragePool().get(this, id); - if (pooled) { - return pooled; - } - - if (!options) { - options = {}; - } - - const newParams = { ...options, query: { id } }; - return await this.findOne(newParams); - }, - - /** - * Finds one model matched by given query parameters. - * @param options - */ - async findOne(options: ?Object): Promise> { - if (!options) { - options = {}; - } - - const prepared = { ...options }; - - const result = await this.getStorageDriver().findOne({ - name: getName(this), - options: prepared - }); - - if (result) { - const pooled = this.getStoragePool().get(this, result.id); - if (pooled) { - return pooled; - } - - const model: $Subtype = new this(); - model.setExisting(); - await model.populateFromStorage(((result: any): Object)); - this.getStoragePool().add(model); - return model; - } - return null; - }, - /** * Counts total number of models matched by given query parameters. * @param options diff --git a/packages/fields-storage/src/withUniqueKey.js b/packages/fields-storage/src/withUniqueKey.js new file mode 100644 index 0000000..b459eef --- /dev/null +++ b/packages/fields-storage/src/withUniqueKey.js @@ -0,0 +1,18 @@ +import withKey from "./withKey"; + +const withUniqueKey = (...params) => { + let key; + if (typeof params[0] === "string") { + key = { + fields: params.map(item => ({ name: item })) + }; + } else { + key = params[0]; + } + + key.unique = true; + + return withKey(key); +}; + +export default withUniqueKey; diff --git a/packages/fields/package.json b/packages/fields/package.json index d538431..bbbab10 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -27,6 +27,7 @@ "fields" ], "scripts": { - "build": "babel src --ignore src/__tests__ --out-dir dist --source-maps" + "build": "babel src --ignore src/__tests__ --out-dir dist --source-maps", + "watch": "yarn build --watch" } } diff --git a/yarn.lock b/yarn.lock index db39711..3e2ff51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -800,6 +800,49 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@commodo/fields-storage-ref@1.1.0-next.2": + version "1.1.0-next.2" + resolved "https://registry.npmjs.org/@commodo/fields-storage-ref/-/fields-storage-ref-1.1.0-next.2.tgz#c67a944336a0cb312799d91802e297b877fc77e6" + integrity sha512-qQ0tuGbwkNLLRQm22aDgAQ/RRV06tZtJpKnb+jDi7mnY7yW1Crrp0/D+Rowo3Z6rgImlMrmRBbkCpfFs7FCFnA== + dependencies: + "@commodo/fields" "^1.1.0-next.2" + "@commodo/fields-storage" "^2.0.0-next.15" + "@commodo/hooks" "^1.1.0-next.2" + "@commodo/name" "^1.2.0-next.2" + repropose "^1.0.2" + +"@commodo/fields-storage@^2.0.0-next.15": + version "2.0.1" + resolved "https://registry.npmjs.org/@commodo/fields-storage/-/fields-storage-2.0.1.tgz#e985efb0e3069a8ea5ec2136e0c61f09bc58cf25" + integrity sha512-0b4ZoW1oT03wVFsVvKsczIJLnE4KuW1dHAM4bHBbpCDcMlmLg5o+zPrvbYo25Ow6B+/rEW+0j6xWAC01LFnNKA== + dependencies: + "@commodo/hooks" "^1.1.1" + "@commodo/name" "^1.2.1" + mdbid "^1.0.0" + repropose "^1.0.2" + +"@commodo/fields@^1.1.0-next.2": + version "1.1.1" + resolved "https://registry.npmjs.org/@commodo/fields/-/fields-1.1.1.tgz#aec8419a2d6b3f5173f5a98cb718cd35bdd0d580" + integrity sha512-l/qpXKbvWjDStwMUsbirhbkymai4yYnhTbT4WH/GNBMtIEaTlt3xmAPyYFdbhGwHaZ913VoJKFOSFmCbj+yXUA== + dependencies: + "@commodo/name" "^1.2.1" + repropose "^1.0.2" + +"@commodo/hooks@^1.1.0-next.2", "@commodo/hooks@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@commodo/hooks/-/hooks-1.1.1.tgz#e0cb5c0ba617536b20494709514d6af35393ac34" + integrity sha512-GYOuldt/KZGvQmJKyo4tDirk3nPiVGvARwVNgjsNOcArll00sEQDsWrdBNQ+E5UCLb/YbFqEqj9s8+JXA4FxXA== + dependencies: + repropose "^1.0.2" + +"@commodo/name@^1.2.0-next.2", "@commodo/name@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@commodo/name/-/name-1.2.1.tgz#811f1a6359409f283687bdddfdeefc15d730c47a" + integrity sha512-xO8ST3b8OvbsaiAkWKUoAA5tSmF3Sm31E0ufsBNBGdPXwmYtXiq4i+1fM+EbSs9StPLqADYK794dk5jczyUXxA== + dependencies: + repropose "^1.0.2" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.npmjs.org/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -1829,6 +1872,15 @@ dependencies: any-observable "^0.3.0" +"@shelf/jest-dynamodb@^1.7.0": + version "1.7.0" + resolved "https://registry.npmjs.org/@shelf/jest-dynamodb/-/jest-dynamodb-1.7.0.tgz#94d6cc08984f2449db951a7ba8fcd1883f330c32" + integrity sha512-ASm/b43lXCgu8xmyIuth+iYp28JSVzn5GPuUq769AWveHMpuSUVESKSMpR0xrAlado4kxzqs0eupQOcuZamaeA== + dependencies: + cwd "0.10.0" + debug "4.1.1" + dynamodb-local "0.0.31" + "@shelf/jest-mongodb@^1.1.5": version "1.1.5" resolved "https://registry.npmjs.org/@shelf/jest-mongodb/-/jest-mongodb-1.1.5.tgz#50573c9cb8cf12f6ee93fb79ea7420a431138c9c" @@ -2218,20 +2270,6 @@ ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -all-contributors-cli@^5.6.0: - version "5.11.0" - resolved "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-5.11.0.tgz#5fc022b8064ee09e4370dc5e9d92d59c7ee0bbf3" - integrity sha512-+xL38RoYh4caJVlxqGsr7jzV5pXLquIEa5AsfUi6/8joOT1m18AKK0qwt3xgOG/P/zOdcs/PBLjmm8NqXh5T+g== - dependencies: - "@babel/runtime" "^7.2.0" - async "^2.0.0-rc.1" - chalk "^2.3.0" - inquirer "^6.2.1" - lodash "^4.11.2" - pify "^4.0.1" - request "^2.72.0" - yargs "^12.0.5" - ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -2436,13 +2474,6 @@ async@3.1.0: resolved "https://registry.npmjs.org/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772" integrity sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ== -async@^2.0.0-rc.1: - version "2.6.3" - resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2463,6 +2494,21 @@ atob@^2.1.2: resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +aws-sdk@^2.729.0: + version "2.738.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.738.0.tgz#37e4b75ab1acf9bc01bea5d4f83ebb937842de6a" + integrity sha512-oO1odRT4DGssivoP6lHO3yB6I+5sU0uqpj6UJ0kS5wrHQ9J9EGrictLVKA9y6XhN0sNW+XPNLD9jMMY/A+gXWA== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2721,6 +2767,15 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + buffer@^5.5.0: version "5.6.0" resolved "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" @@ -2887,7 +2942,7 @@ caseless@~0.12.0: resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2971,15 +3026,6 @@ cli-width@^2.0.0: resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3434,7 +3480,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -3654,6 +3700,16 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +dynamodb-local@0.0.31: + version "0.0.31" + resolved "https://registry.npmjs.org/dynamodb-local/-/dynamodb-local-0.0.31.tgz#ce6057aab0e1f8265c41658e5b12e679a0ce6ef6" + integrity sha512-5Ro47g989oLSC2JKFtPAWWSSicZOkqQrBGwHActqN6zdCZEy/eGvhq7Ki9Lgx/gNE+ksIF2wD6VNff3bjTI8ow== + dependencies: + debug "~4.1.0" + mkdirp "~0.5.0" + q "~1.4.1" + tar "~4.4.8" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3919,6 +3975,11 @@ eventemitter3@^3.1.0: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== +events@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + exec-sh@^0.3.2: version "0.3.4" resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" @@ -4460,11 +4521,6 @@ gensync@^1.0.0-beta.1: resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -4943,7 +4999,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@^1.1.4: +ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== @@ -5058,7 +5114,7 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" -inquirer@^6.2.0, inquirer@^6.2.1, inquirer@^6.2.2: +inquirer@^6.2.0, inquirer@^6.2.2: version "6.5.2" resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== @@ -5084,11 +5140,6 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -5372,7 +5423,7 @@ isarray@0.0.1: resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -5803,6 +5854,11 @@ jest@^24.7.1: import-local "^2.0.0" jest-cli "^24.9.0" +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= + js-base64@2.5.1: version "2.5.1" resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" @@ -6045,13 +6101,6 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - lcov-parse@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" @@ -6356,7 +6405,7 @@ lodash@4.17.11: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -lodash@4.17.15, lodash@^4.11.2, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.2.1: +lodash@4.17.15, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.2.1: version "4.17.15" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -6479,13 +6528,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -6547,15 +6589,6 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" @@ -6686,11 +6719,6 @@ mimic-fn@^1.0.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -6799,7 +6827,7 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -6870,6 +6898,19 @@ mongodb@^3.5.4: optionalDependencies: saslprep "^1.0.0" +mongodb@^3.5.5: + version "3.6.0" + resolved "https://registry.npmjs.org/mongodb/-/mongodb-3.6.0.tgz#babd7172ec717e2ed3f85e079b3f1aa29dce4724" + integrity sha512-/XWWub1mHZVoqEsUppE0GV7u9kanLvHxho6EvBxQbShXTKYF9trhZC2NzbulRGeG7xMJHD8IOWRcdKx5LPjAjQ== + dependencies: + bl "^2.2.0" + bson "^1.1.4" + denque "^1.4.1" + require_optional "^1.0.1" + safe-buffer "^5.1.2" + optionalDependencies: + saslprep "^1.0.0" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -7322,15 +7363,6 @@ os-homedir@^1.0.0, os-homedir@^1.0.1: resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-name@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -7352,11 +7384,6 @@ osenv@^0.1.4, osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-each-series@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" @@ -7369,11 +7396,6 @@ p-finally@^1.0.0: resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -7839,6 +7861,11 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + punycode@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -7854,6 +7881,11 @@ q@^1.5.1: resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= +q@~1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" + integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= + qs@6.5.2, qs@~6.5.1, qs@~6.5.2: version "6.5.2" resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -7864,6 +7896,11 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -8255,7 +8292,7 @@ request@2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@^2.72.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -8286,11 +8323,6 @@ require-directory@^2.1.1: resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -8374,6 +8406,11 @@ retry@^0.10.0: resolved "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +rfdc@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" + integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -8480,7 +8517,12 @@ saslprep@^1.0.0: dependencies: sparse-bitfield "^3.0.3" -sax@^1.2.4: +sax@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -8648,7 +8690,7 @@ simple-git@^1.85.0: dependencies: debug "^4.0.1" -sinon@^7.2.7: +sinon@^7.2.7, sinon@^7.3.2: version "7.5.0" resolved "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz#e9488ea466070ea908fd44a3d6478fd4923c67ec" integrity sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q== @@ -8931,7 +8973,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -9131,7 +9173,7 @@ tar-stream@^2.1.1: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: +tar@^4.4.10, tar@^4.4.12, tar@^4.4.8, tar@~4.4.8: version "4.4.13" resolved "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== @@ -9455,6 +9497,11 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +uniqid@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/uniqid/-/uniqid-5.2.0.tgz#0d0589a7e9ce07116848126764fbff0b68e74329" + integrity sha512-LH8zsvwJ/GL6YtNfSOmMCrI9piraAUjBfw2MCvleNE6a4pVKJwXjG2+HWhkVeFcSg+nmaPKbMrMOoxwQluZ1Mg== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -9528,6 +9575,14 @@ urix@^0.1.0: resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url@0.10.3: + version "0.10.3" + resolved "https://registry.npmjs.org/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + use@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -9560,6 +9615,11 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -9770,14 +9830,6 @@ wordwrap@~0.0.2: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -9874,6 +9926,19 @@ xml-name-validator@^3.0.0: resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + xmlchars@^2.1.1: version "2.2.0" resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" @@ -9884,7 +9949,7 @@ xtend@~4.0.1: resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== @@ -9894,14 +9959,6 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -9926,24 +9983,6 @@ yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^12.0.5: - version "12.0.5" - resolved "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - yargs@^13.3.0: version "13.3.2" resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"