From 9f4056eecf04165ae6f5a2cc96c95228f9855581 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 18 Aug 2020 19:09:45 -0500 Subject: [PATCH 1/6] Support Idempotency --- integration/cloud/main.js | 5 + integration/server.js | 9 +- integration/test/IdempotencyTest.js | 49 ++ integration/test/clear.js | 10 +- package-lock.json | 670 +++++++++++++++------------ src/CoreManager.js | 3 +- src/Parse.js | 14 + src/RESTController.js | 12 +- src/__tests__/Parse-test.js | 9 + src/__tests__/RESTController-test.js | 28 ++ 10 files changed, 496 insertions(+), 313 deletions(-) create mode 100644 integration/test/IdempotencyTest.js diff --git a/integration/cloud/main.js b/integration/cloud/main.js index 4691a289f..6b632de2f 100644 --- a/integration/cloud/main.js +++ b/integration/cloud/main.js @@ -21,6 +21,11 @@ Parse.Cloud.define('UpdateUser', function (request) { return user.save(null, { useMasterKey: true }); }); +Parse.Cloud.define('CloudFunctionIdempotency', function () { + const object = new Parse.Object('IdempotencyItem'); + return object.save(null, { useMasterKey: true }); +}); + Parse.Cloud.define('CloudFunctionUndefined', function() { return undefined; }); diff --git a/integration/server.js b/integration/server.js index 3a2bcea09..3bf21c838 100644 --- a/integration/server.js +++ b/integration/server.js @@ -29,14 +29,19 @@ const api = new ParseServer({ }, verbose: false, silent: true, + idempotencyOptions: { + paths: ['functions/CloudFunctionIdempotency'], + ttl: 120 + } }); app.use('/parse', api); const TestUtils = require('parse-server').TestUtils; -app.get('/clear', (req, res) => { - TestUtils.destroyAllDataPermanently().then(() => { +app.get('/clear/:fast', (req, res) => { + const { fast } = req.params; + TestUtils.destroyAllDataPermanently(fast).then(() => { res.send('{}'); }); }); diff --git a/integration/test/IdempotencyTest.js b/integration/test/IdempotencyTest.js new file mode 100644 index 000000000..94db27600 --- /dev/null +++ b/integration/test/IdempotencyTest.js @@ -0,0 +1,49 @@ +'use strict'; + +const assert = require('assert'); +const clear = require('./clear'); +const Parse = require('../../node'); + +const Item = Parse.Object.extend('IdempotencyItem'); + +describe('Idempotency', () => { + beforeEach((done) => { + Parse.initialize('integration', null, 'notsosecret'); + Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); + Parse.Storage._clear(); + clear(true).then(() => { + done(); + }); + }); + + it('duplicate cloud code function request', async () => { + const restController = Parse.CoreManager.getRESTController(); + const XHR = restController._getXHR(); + function DuplicateXHR() { + const xhr = new XHR(); + const send = xhr.send; + xhr.send = function () { + this.setRequestHeader('X-Parse-Request-Id', '1234'); + send.apply(this, arguments); + } + return xhr; + } + restController._setXHR(DuplicateXHR); + await Parse.Cloud.run('CloudFunctionIdempotency'); + try { + await Parse.Cloud.run('CloudFunctionIdempotency'); + } catch (e) { + assert.equal(e.code, Parse.Error.DUPLICATE_REQUEST); + } + try { + await Parse.Cloud.run('CloudFunctionIdempotency'); + } catch (e) { + assert.equal(e.code, Parse.Error.DUPLICATE_REQUEST); + } + const query = new Parse.Query(Item); + const results = await query.find(); + assert.equal(results.length, 1); + + restController._setXHR(XHR); + }); +}); diff --git a/integration/test/clear.js b/integration/test/clear.js index 544078d13..8855196d8 100644 --- a/integration/test/clear.js +++ b/integration/test/clear.js @@ -1,5 +1,11 @@ const Parse = require('../../node'); -module.exports = function() { - return Parse._ajax('GET', 'http://localhost:1337/clear', ''); +/** + * Destroys all data in the database + * Calls /clear route in integration/test/server.js + * + * @param {boolean} fast set to true if it's ok to just drop objects and not indexes. + */ +module.exports = function(fast = false) { + return Parse._ajax('GET', `http://localhost:1337/clear/${fast}`, ''); }; diff --git a/package-lock.json b/package-lock.json index bd74648f2..480167d73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@apollo/protobufjs": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz", - "integrity": "sha512-EE3zx+/D/wur/JiLp6VCiw1iYdyy1lCJMf8CGPkLeDt5QJrN4N8tKFx33Ah4V30AUQzMk7Uz4IXKZ1LOj124gA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.5.tgz", + "integrity": "sha512-ZtyaBH1icCgqwIGb3zrtopV2D5Q8yxibkJzlaViM08eOhTQc7rACdYu0pfORFfhllvdMZ3aq69vifYHszY4gNA==", "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -26,9 +26,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz", - "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==", + "version": "10.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz", + "integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ==", "dev": true } } @@ -51,6 +51,12 @@ "xss": "^1.0.6" } }, + "@ardatan/aggregate-error": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.1.tgz", + "integrity": "sha512-UQ9BequOTIavs0pTHLMwQwKQF8tTV1oezY/H2O9chA+JNPFZSua55xpU5dPSjAU9/jLJ1VwU+HJuTVN8u7S6Fg==", + "dev": true + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -61,9 +67,9 @@ } }, "@babel/compat-data": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", - "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", "dev": true, "requires": { "browserslist": "^4.12.0", @@ -96,12 +102,12 @@ } }, "@babel/generator": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", - "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", + "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", "dev": true, "requires": { - "@babel/types": "^7.10.5", + "@babel/types": "^7.11.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -235,12 +241,12 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", - "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", "dev": true, "requires": { - "@babel/types": "^7.10.5" + "@babel/types": "^7.11.0" } }, "@babel/helper-module-imports": { @@ -253,17 +259,17 @@ } }, "@babel/helper-module-transforms": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", - "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", "@babel/helper-replace-supers": "^7.10.4", "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", "@babel/template": "^7.10.4", - "@babel/types": "^7.10.5", + "@babel/types": "^7.11.0", "lodash": "^4.17.19" } }, @@ -326,13 +332,22 @@ "@babel/types": "^7.10.4" } }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, "@babel/helper-split-export-declaration": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", - "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.11.0" } }, "@babel/helper-validator-identifier": { @@ -376,9 +391,9 @@ } }, "@babel/parser": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", - "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "version": "7.11.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz", + "integrity": "sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -453,9 +468,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", - "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -474,12 +489,13 @@ } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", - "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, @@ -659,9 +675,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", - "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -991,12 +1007,13 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", - "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, "@babel/plugin-transform-sticky-regex": { @@ -1029,9 +1046,9 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.5.tgz", - "integrity": "sha512-YCyYsFrrRMZ3qR7wRwtSSJovPG5vGyG4ZdcSAivGwTfoasMp3VOB/AKhohu3dFtmB4cCDcsndCSxGtrdliCsZQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.11.0.tgz", + "integrity": "sha512-edJsNzTtvb3MaXQwj8403B7mZoGu9ElDJQZOKjGUnvilquxBA3IQoEIOvkX/1O8xfAsnHS/oQhe2w/IXrr+w0w==", "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.10.5", @@ -1164,6 +1181,13 @@ "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", "requires": { "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } } }, "@babel/runtime-corejs3": { @@ -1173,6 +1197,13 @@ "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } } }, "@babel/template": { @@ -1187,26 +1218,26 @@ } }, "@babel/traverse": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", - "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", + "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.5", + "@babel/generator": "^7.11.0", "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.5", - "@babel/types": "^7.10.5", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.0", + "@babel/types": "^7.11.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", - "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", @@ -1225,111 +1256,111 @@ } }, "@graphql-tools/delegate": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.10.tgz", - "integrity": "sha512-FBHrmpSI9QpNbvqc5D4wdQW0WrNVUA2ylFhzsNRk9yvlKzcVKqiTrOpb++j7TLB+tG06dpSkfAssPcgZvU60fw==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.12.tgz", + "integrity": "sha512-52bac1Ct1s0c8aSTVCbnc5FI2LC+NqUFSs+5/mP1k5hIEW2GROGBeZdbRs2GQaHir1vKUYIyHzlZIIBMzOZ/gA==", "dev": true, "requires": { - "@graphql-tools/schema": "6.0.10", - "@graphql-tools/utils": "6.0.10", - "aggregate-error": "3.0.1", + "@ardatan/aggregate-error": "0.0.1", + "@graphql-tools/schema": "6.0.12", + "@graphql-tools/utils": "6.0.12", "tslib": "~2.0.0" }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } }, "@graphql-tools/merge": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.10.tgz", - "integrity": "sha512-fnz9h5vdA8LXc9TvmhnRXykwFZWZ4FdBeo4g3R1KqcQCp65ByCMcBuCJtYf4VxPrcgTLGlWtVOHrItCi0kdioA==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.12.tgz", + "integrity": "sha512-GGvdIoTad6PJk/d1omPlGQ25pCFWmjuGkARYZ71qWI/c4FEA8EdGoOoPz3shhaKXyLdRiu84S758z4ZtDQiYVw==", "dev": true, "requires": { - "@graphql-tools/schema": "6.0.10", - "@graphql-tools/utils": "6.0.10", + "@graphql-tools/schema": "6.0.12", + "@graphql-tools/utils": "6.0.12", "tslib": "~2.0.0" }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } }, "@graphql-tools/schema": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.10.tgz", - "integrity": "sha512-g8iy36dgf/Cpyz7bHSE2axkE8PdM5VYdS2tntmytLvPaN3Krb8IxBpZBJhmiICwyAAkruQE7OjDfYr8vP8jY4A==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.12.tgz", + "integrity": "sha512-XUmKJ+ipENaxuXIX4GapsLAUl1dFQBUg+S4ZbgtKVlwrPhZJ9bkjIqnUHk3wg4S4VXqzLX97ol1e4g9N6XLkYg==", "dev": true, "requires": { - "@graphql-tools/utils": "6.0.10", + "@graphql-tools/utils": "6.0.12", "tslib": "~2.0.0" }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } }, "@graphql-tools/stitch": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.10.tgz", - "integrity": "sha512-45xgk/ggXEkj6Ys4Hf1sV0ngzzvPhcGvA23/NG6E5LSkt4GM0TjtRpqwWMMoKJps9+1JX9/RSbHBAchC+zZj3w==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.12.tgz", + "integrity": "sha512-I+9l5Ws30Fn3nx0CIDUDMGP0nhexMEJyzfQn1t9DuOTy2QHPQ5YpaZ8hxv6y5+X23EJBU9AebqvNSvWNEO6XJQ==", "dev": true, "requires": { - "@graphql-tools/delegate": "6.0.10", - "@graphql-tools/merge": "6.0.10", - "@graphql-tools/schema": "6.0.10", - "@graphql-tools/utils": "6.0.10", - "@graphql-tools/wrap": "6.0.10", + "@graphql-tools/delegate": "6.0.12", + "@graphql-tools/merge": "6.0.12", + "@graphql-tools/schema": "6.0.12", + "@graphql-tools/utils": "6.0.12", + "@graphql-tools/wrap": "6.0.12", "tslib": "~2.0.0" }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } }, "@graphql-tools/utils": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.10.tgz", - "integrity": "sha512-1s3vBnYUIDLBGEaV1VF3lv1Xq54lT8Oz7tNNypv7K7cv3auKX7idRtjP8RM6hKpGod46JNZgu3NNOshMUEyEyA==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", + "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", "dev": true, "requires": { - "aggregate-error": "3.0.1", + "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" } }, "@graphql-tools/wrap": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.10.tgz", - "integrity": "sha512-260f+eks3pSltokwueFJXQSwf7QdsjccphXINBIa0hwPyF8mPanyJlqd5GxkkG+C2K/oOXm8qaxc6pp7lpaomQ==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.12.tgz", + "integrity": "sha512-x/t6004aNLzTbOFzZiau15fY2+TBy0wbFqP2du+I+yh8j6KmAU1YkPolBJ4bAI04WD3qcLNh7Rai+VhOxidOkw==", "dev": true, "requires": { - "@graphql-tools/delegate": "6.0.10", - "@graphql-tools/schema": "6.0.10", - "@graphql-tools/utils": "6.0.10", + "@graphql-tools/delegate": "6.0.12", + "@graphql-tools/schema": "6.0.12", + "@graphql-tools/utils": "6.0.12", "aggregate-error": "3.0.1", "tslib": "~2.0.0" }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } @@ -1555,9 +1586,9 @@ }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true, "optional": true } @@ -1958,9 +1989,9 @@ } }, "@types/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.7.tgz", + "integrity": "sha512-sOdDRU3oRS7LBNTIqwDkPJyq0lpHYcbMTt0TrjzsXbk/e37hcLTH6eZX7CdbDeN0yJJvzw9hFBZkbtCSbk/jAQ==", "dev": true, "requires": { "@types/express": "*" @@ -1989,9 +2020,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", - "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz", + "integrity": "sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA==", "dev": true, "requires": { "@types/node": "*", @@ -2046,6 +2077,12 @@ "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==", "dev": true }, + "@types/http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -2078,15 +2115,16 @@ "dev": true }, "@types/koa": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.3.tgz", - "integrity": "sha512-ABxVkrNWa4O/Jp24EYI/hRNqEVRlhB9g09p48neQp4m3xL1TJtdWk2NyNQSMCU45ejeELMQZBYyfstyVvO2H3Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.4.tgz", + "integrity": "sha512-Etqs0kdqbuAsNr5k6mlZQelpZKVwMu9WPRHVVTLnceZlhr0pYmblRNJbCgoCMzKWWePldydU0AYEOX4Q9fnGUQ==", "dev": true, "requires": { "@types/accepts": "*", "@types/content-disposition": "*", "@types/cookies": "*", "@types/http-assert": "*", + "@types/http-errors": "*", "@types/keygrip": "*", "@types/koa-compose": "*", "@types/node": "*" @@ -2108,15 +2146,15 @@ "dev": true }, "@types/mime": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", - "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", "dev": true }, "@types/node": { - "version": "14.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", - "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==", + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==", "dev": true }, "@types/node-fetch": { @@ -2143,9 +2181,9 @@ } }, "@types/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==", "dev": true }, "@types/range-parser": { @@ -2155,9 +2193,9 @@ "dev": true }, "@types/serve-static": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", - "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", + "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", "dev": true, "requires": { "@types/express-serve-static-core": "*", @@ -2180,9 +2218,9 @@ } }, "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", + "version": "13.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz", + "integrity": "sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2214,9 +2252,9 @@ } }, "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", + "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==", "dev": true }, "abstract-logging": { @@ -2236,9 +2274,9 @@ } }, "acorn": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", - "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true }, "acorn-globals": { @@ -2308,9 +2346,9 @@ } }, "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -2423,9 +2461,9 @@ }, "dependencies": { "uuid": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", - "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==", "dev": true } } @@ -2483,9 +2521,9 @@ } }, "apollo-server-core": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.0.tgz", - "integrity": "sha512-mnvg2cPvsQtjFXIqIhEAbPqGyiSXDSbiBgNQ8rY8g7r2eRMhHKZePqGF03gP1/w87yVaSDRAZBDk6o+jiBXjVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.1.tgz", + "integrity": "sha512-nuwn5ZBbmzPwDetb3FgiFFJlNK7ZBFg8kis/raymrjd3eBGdNcOyMTJDl6J9673X9Xqp+dXQmFYDW/G3G8S1YA==", "dev": true, "requires": { "@apollographql/apollo-tools": "^0.4.3", @@ -2571,9 +2609,9 @@ "dev": true }, "apollo-server-express": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.15.0.tgz", - "integrity": "sha512-ECptVIrOVW2cmMWvqtpkZfyZrQL8yTSgbVvP4M8qcPV/3XxDJa6444zy7vxqN7lyYl8IJAsg/IwC0vodoXe//A==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.15.1.tgz", + "integrity": "sha512-anNb9HJo+KTpgvUqiPOjEl4wPq8y8NmWaIUz/QqPZlhIEDdf7wd/kQo3Sdbov++7J9JNJx6Ownnvw+wxfogUgA==", "dev": true, "requires": { "@apollographql/graphql-playground-html": "1.6.26", @@ -2582,8 +2620,8 @@ "@types/cors": "^2.8.4", "@types/express": "4.17.4", "accepts": "^1.3.5", - "apollo-server-core": "^2.15.0", - "apollo-server-types": "^0.5.0", + "apollo-server-core": "^2.15.1", + "apollo-server-types": "^0.5.1", "body-parser": "^1.18.3", "cors": "^2.8.4", "express": "^4.17.1", @@ -2812,14 +2850,15 @@ } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "bn.js": { @@ -3020,9 +3059,9 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", "dev": true }, "axios": { @@ -3318,9 +3357,9 @@ "dev": true }, "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "body-parser": { @@ -3552,16 +3591,16 @@ } }, "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", - "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "dev": true, "requires": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.3", "inherits": "^2.0.4", "parse-asn1": "^5.1.5", "readable-stream": "^3.6.0", @@ -3597,15 +3636,15 @@ } }, "browserslist": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", - "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", + "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001093", - "electron-to-chromium": "^1.3.488", - "escalade": "^3.0.1", - "node-releases": "^1.1.58" + "caniuse-lite": "^1.0.30001111", + "electron-to-chromium": "^1.3.523", + "escalade": "^3.0.2", + "node-releases": "^1.1.60" } }, "bser": { @@ -3618,9 +3657,9 @@ } }, "bson": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", - "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", "dev": true }, "buffer": { @@ -3752,9 +3791,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001100", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz", - "integrity": "sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA==", + "version": "1.0.30001115", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001115.tgz", + "integrity": "sha512-NZrG0439ePYna44lJX8evHX2L7Z3/z3qjVLnHgbBb/duNEnGo348u+BQS5o4HTWcrb++100dHFrU36IesIrC1Q==", "dev": true }, "capture-exit": { @@ -4281,13 +4320,13 @@ } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" }, "dependencies": { "bn.js": { @@ -4989,9 +5028,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.498", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz", - "integrity": "sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w==", + "version": "1.3.535", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.535.tgz", + "integrity": "sha512-5k7WGdl1ZnbcU97acUnY/UXu6bCMDnKCAnEc1N0xNToPvMCp99PEvh5K3xNr4ZUVCf2FuratM++NgOxCtbtXzA==", "dev": true }, "elliptic": { @@ -5372,9 +5411,9 @@ }, "dependencies": { "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } @@ -6356,9 +6395,9 @@ } }, "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -6366,7 +6405,16 @@ "chokidar": "^2.0.0", "is-negated-glob": "^1.0.0", "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", "object.defaults": "^1.1.0" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } } }, "global-modules": { @@ -6415,9 +6463,9 @@ "dev": true }, "graphql": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.1.0.tgz", - "integrity": "sha512-0TVyfOlCGhv/DBczQkJmwXOK6fjWkjzY3Pt7wY8i0gcYXq8aogG3weCsg48m72lywKSeOqedEHvVPOvZvSD51Q==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz", + "integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==", "dev": true }, "graphql-extensions": { @@ -6456,9 +6504,9 @@ } }, "graphql-tag": { - "version": "2.10.4", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz", - "integrity": "sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz", + "integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==", "dev": true }, "graphql-tools": { @@ -6843,12 +6891,12 @@ "dev": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, @@ -7174,9 +7222,9 @@ } }, "inquirer": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.2.tgz", - "integrity": "sha512-DF4osh1FM6l0RJc5YWYhSDB6TawiBRlbV9Cox8MWlidU218Tb7fm3lQTULyUJDfJ0tjbzl0W4q651mrCCEM55w==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -7185,7 +7233,7 @@ "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.16", + "lodash": "^4.17.19", "mute-stream": "0.0.8", "run-async": "^2.4.0", "rxjs": "^6.6.0", @@ -7519,9 +7567,9 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" @@ -8838,9 +8886,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.clonedeep": { @@ -9381,13 +9429,13 @@ "dev": true }, "module-deps": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.2.tgz", - "integrity": "sha512-a9y6yDv5u5I4A+IPHTnqFxcaKr4p50/zxTjcQJaX2ws9tN/W6J6YXnEKhqRyPhl494dkcxx951onSKVezmI+3w==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", + "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", "dev": true, "requires": { "JSONStream": "^1.0.3", - "browser-resolve": "^1.7.0", + "browser-resolve": "^2.0.0", "cached-path-relative": "^1.0.2", "concat-stream": "~1.6.0", "defined": "^1.0.0", @@ -9401,6 +9449,17 @@ "subarg": "^1.0.0", "through2": "^2.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "requires": { + "resolve": "^1.17.0" + } + } } }, "moment": { @@ -9545,9 +9604,9 @@ } }, "node-releases": { - "version": "1.1.59", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", - "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==", + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", "dev": true }, "normalize-package-data": { @@ -9798,9 +9857,9 @@ "dev": true }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" @@ -10020,45 +10079,39 @@ } }, "parse": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-2.14.0.tgz", - "integrity": "sha512-S4bbF80Aom/xDk4YNkzZG1xBHYbiFQGueJWyO4DpYlajfkEs3gp0oszFDnGadTARyCgoQGxNE4Qkege/QqNETA==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-2.15.0.tgz", + "integrity": "sha512-Aupg+qd6I4X5uTacpsxROg5GlhkVn2+qOHtyOhlGj/Woi75c5cPD8kn7qhhLKcVVpe2L+HoJ+yGkMdI8IjKBKA==", "dev": true, "requires": { - "@babel/runtime": "7.10.2", - "@babel/runtime-corejs3": "7.10.2", + "@babel/runtime": "7.10.3", + "@babel/runtime-corejs3": "7.10.3", "crypto-js": "4.0.0", "react-native-crypto-js": "1.0.0", - "uuid": "3.3.3", + "uuid": "3.4.0", "ws": "7.3.0", "xmlhttprequest": "1.8.0" }, "dependencies": { "@babel/runtime": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", - "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", + "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz", - "integrity": "sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg==", + "version": "7.10.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz", + "integrity": "sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw==", "dev": true, "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" } }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true - }, "ws": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", @@ -10068,14 +10121,13 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" @@ -10144,19 +10196,19 @@ "dev": true }, "parse-server": { - "version": "github:parse-community/parse-server#3bd5684f67a16ec96907b50ab5fc9daa9e4fa8e0", + "version": "github:parse-community/parse-server#4cec333cf96eb10f836a83932797daba57a07144", "from": "github:parse-community/parse-server#master", "dev": true, "requires": { "@apollographql/graphql-playground-html": "1.6.26", - "@graphql-tools/stitch": "6.0.10", - "@graphql-tools/utils": "6.0.10", + "@graphql-tools/stitch": "6.0.12", + "@graphql-tools/utils": "6.0.12", "@node-rs/bcrypt": "0.3.0", "@parse/fs-files-adapter": "1.0.1", "@parse/push-adapter": "3.2.0", "@parse/s3-files-adapter": "1.4.0", "@parse/simple-mailgun-adapter": "1.1.0", - "apollo-server-express": "2.15.0", + "apollo-server-express": "2.15.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "commander": "5.1.0", @@ -10164,7 +10216,7 @@ "deepcopy": "2.0.0", "express": "4.17.1", "follow-redirects": "1.12.1", - "graphql": "15.1.0", + "graphql": "15.3.0", "graphql-list-fields": "2.0.2", "graphql-relay": "0.6.0", "graphql-upload": "11.0.0", @@ -10172,27 +10224,27 @@ "jsonwebtoken": "8.5.1", "jwks-rsa": "1.8.1", "ldapjs": "2.0.0", - "lodash": "4.17.16", + "lodash": "4.17.19", "lru-cache": "5.1.1", "mime": "2.4.6", "mongodb": "3.5.9", - "parse": "2.14.0", - "pg-promise": "10.5.7", + "parse": "2.15.0", + "pg-promise": "10.5.8", "pluralize": "8.0.0", "redis": "3.0.2", "semver": "7.3.2", - "subscriptions-transport-ws": "0.9.16", + "subscriptions-transport-ws": "0.9.17", "tv4": "1.3.0", "uuid": "8.2.0", "winston": "3.2.1", "winston-daily-rotate-file": "4.5.0", - "ws": "7.3.0" + "ws": "7.3.1" }, "dependencies": { "lodash": { - "version": "4.17.16", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.16.tgz", - "integrity": "sha512-mzxOTaU4AsJhnIujhngm+OnA6JX4fTI8D5H26wwGd+BJ57bW70oyRwTqo6EFJm1jTZ7hCo7yVzH1vB8TMFd2ww==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "mime": { @@ -10212,12 +10264,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==", "dev": true - }, - "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", - "dev": true } } }, @@ -10358,16 +10404,16 @@ "dev": true }, "pg": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.2.1.tgz", - "integrity": "sha512-DKzffhpkWRr9jx7vKxA+ur79KG+SKw+PdjMb1IRhMiKI9zqYUGczwFprqy+5Veh/DCcFs1Y6V8lRLN5I1DlleQ==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.2.2.tgz", + "integrity": "sha512-Uni50U0W2CNPM68+zfC/1WWjSO3q/uBSF/Nl7D+1npZGsPSM4/EZt0xSMW2jox1Bn0EfDlnTWnTsM/TrSOtBEA==", "dev": true, "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "^2.2.3", "pg-pool": "^3.2.1", - "pg-protocol": "^1.2.4", + "pg-protocol": "^1.2.5", "pg-types": "^2.1.0", "pgpass": "1.x", "semver": "4.3.2" @@ -10406,15 +10452,15 @@ "dev": true }, "pg-promise": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.5.7.tgz", - "integrity": "sha512-feCpn4J4MsNnR5Ve3fpbIlmbohwRirvZEI1Dcy72zwKvIKKRHPk7TJZFQHP4YQhaZ3sT3VGgg0o1/I+uhht/1g==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.5.8.tgz", + "integrity": "sha512-EdLyPd/XlmNsfA2uRKHuCnyLhk5DHPdKGPZmjzpcKfdx6dDZB+nEfSuaNSjReRrM7BmPaV/hSGppt9kG/W2Umw==", "dev": true, "requires": { "assert-options": "0.6.2", - "pg": "8.2.1", + "pg": "8.2.2", "pg-minify": "1.6.1", - "spex": "3.0.1" + "spex": "3.0.2" } }, "pg-protocol": { @@ -10527,9 +10573,9 @@ "dev": true }, "postgres-date": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz", - "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.6.tgz", + "integrity": "sha512-o2a4gxeFcox+CgB3Ig/kNHBP23PiEXHCXx7pcIIsvzoNz4qv+lKTyiSkjOXIMNUl12MO/mOYl2K6wR9X5K6Plg==", "dev": true }, "postgres-interval": { @@ -10989,9 +11035,9 @@ } }, "redis-commands": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", - "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", + "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==", "dev": true }, "redis-errors": { @@ -11027,7 +11073,8 @@ "regenerator-runtime": { "version": "0.13.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true }, "regenerator-transform": { "version": "0.14.5", @@ -11193,21 +11240,21 @@ } }, "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.17.15" + "lodash": "^4.17.19" } }, "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "dev": true, "requires": { - "request-promise-core": "1.1.3", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } @@ -11362,9 +11409,9 @@ "dev": true }, "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -11595,9 +11642,9 @@ "dev": true }, "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", "dev": true }, "simple-swizzle": { @@ -11899,9 +11946,9 @@ "dev": true }, "spex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spex/-/spex-3.0.1.tgz", - "integrity": "sha512-priWZUrXBmVPHTOmtUeS7gZzCOUwRK87OHJw5K8bTC6MLOq93mQocx+vWccNyKPT2EY+goZvKGguGn2lx8TBDA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spex/-/spex-3.0.2.tgz", + "integrity": "sha512-ZNCrOso+oNv5P01HCO4wuxV9Og5rS6ms7gGAqugfBPjx1QwfNXJI3T02ldfaap1O0dlT1sB0Rk+mhDqxt3Z27w==", "dev": true }, "split": { @@ -12271,9 +12318,9 @@ } }, "subscriptions-transport-ws": { - "version": "0.9.16", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz", - "integrity": "sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz", + "integrity": "sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA==", "dev": true, "requires": { "backo2": "^1.0.2", @@ -12694,9 +12741,9 @@ "dev": true }, "uglify-js": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", - "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz", + "integrity": "sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q==", "dev": true }, "umd": { @@ -12731,9 +12778,9 @@ "dev": true }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", "dev": true, "requires": { "arr-flatten": "^1.0.1", @@ -12741,10 +12788,19 @@ "bach": "^1.0.0", "collection-map": "^1.0.0", "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", "last-run": "^1.1.0", "object.defaults": "^1.0.0", "object.reduce": "^1.0.0", "undertaker-registry": "^1.0.0" + }, + "dependencies": { + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + } } }, "undertaker-registry": { @@ -13448,9 +13504,9 @@ "dev": true }, "xss": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz", - "integrity": "sha512-A9v7tblGvxu8TWXQC9rlpW96a+LN1lyw6wyhpTmmGW+FwRMactchBR3ROKSi33UPCUcUHSu8s9YP6F+K3Mw//w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.8.tgz", + "integrity": "sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw==", "dev": true, "requires": { "commander": "^2.20.3", diff --git a/src/CoreManager.js b/src/CoreManager.js index a96938153..7a9fed145 100644 --- a/src/CoreManager.js +++ b/src/CoreManager.js @@ -191,7 +191,8 @@ const config: Config & { [key: string]: mixed } = { USE_MASTER_KEY: false, PERFORM_USER_REWRITE: true, FORCE_REVOCABLE_SESSION: false, - ENCRYPTED_USER: false + ENCRYPTED_USER: false, + IDEMPOTENCY: false }; function requireMethods(name: string, methods: Array, controller: any) { diff --git a/src/Parse.js b/src/Parse.js index a3fc29da6..2c5adef3f 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -198,6 +198,20 @@ Object.defineProperty(Parse, 'secret', { CoreManager.set('ENCRYPTED_KEY', value); } }); + +/** + * @member Parse.idempotency + * @type boolean + * @static + */ +Object.defineProperty(Parse, 'idempotency', { + get() { + return CoreManager.get('IDEMPOTENCY'); + }, + set(value) { + CoreManager.set('IDEMPOTENCY', value); + } +}); /* End setters */ Parse.ACL = require('./ParseACL').default; diff --git a/src/RESTController.js b/src/RESTController.js index bc3ba1ff8..5d068c0cb 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -9,6 +9,8 @@ * @flow */ /* global XMLHttpRequest, XDomainRequest */ +const uuidv4 = require('uuid/v4'); + import CoreManager from './CoreManager'; import ParseError from './ParseError'; import { resolvingPromise } from './promiseUtils'; @@ -152,6 +154,9 @@ const RESTController = { headers['User-Agent'] = 'Parse/' + CoreManager.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; } + if (CoreManager.get('IDEMPOTENCY')) { + headers['X-Parse-Request-Id'] = uuidv4(); + } if (CoreManager.get('SERVER_AUTH_TYPE') && CoreManager.get('SERVER_AUTH_TOKEN')) { headers['Authorization'] = CoreManager.get('SERVER_AUTH_TYPE') + ' ' + CoreManager.get('SERVER_AUTH_TOKEN'); } @@ -314,9 +319,10 @@ const RESTController = { ); } } else { + const message = response.message ? response.message : response; error = new ParseError( ParseError.CONNECTION_FAILED, - 'XMLHttpRequest failed: ' + JSON.stringify(response) + 'XMLHttpRequest failed: ' + JSON.stringify(message) ); } return Promise.reject(error); @@ -324,6 +330,10 @@ const RESTController = { _setXHR(xhr: any) { XHR = xhr; + }, + + _getXHR() { + return XHR; } } diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index 318ae4b9a..4179f168b 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -58,6 +58,15 @@ describe('Parse module', () => { expect(Parse.serverAuthToken).toBe('some_token'); }); + it('can set idempotency', () => { + expect(Parse.idempotency).toBe(false); + Parse.idempotency = true; + expect(CoreManager.get('IDEMPOTENCY')).toBe(true); + expect(Parse.idempotency).toBe(true); + Parse.idempotency = false; + expect(Parse.idempotency).toBe(false); + }); + it('can set LocalDatastoreController', () => { const controller = { fromPinWithName: function() {}, diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 41879e474..d256d3ee8 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -9,6 +9,10 @@ jest.autoMockOff(); jest.useFakeTimers(); +jest.mock('uuid/v4', () => { + let value = 1000; + return () => (value++).toString(); +}); const CoreManager = require('../CoreManager'); const RESTController = require('../RESTController'); @@ -432,6 +436,30 @@ describe('RESTController', () => { CoreManager.set('SERVER_AUTH_TOKEN', null); }); + it('sends requestId header for idempotency', async () => { + CoreManager.set('IDEMPOTENCY', true); + const requestIdHeader = (header) => 'X-Parse-Request-Id' === header[0]; + const xhr = { + setRequestHeader: jest.fn(), + open: jest.fn(), + send: jest.fn() + }; + RESTController._setXHR(function() { return xhr; }); + RESTController.request('GET', 'classes/MyObject', {}, {}); + await flushPromises(); + expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( + [["X-Parse-Request-Id", '1000']] + ); + xhr.setRequestHeader.mockClear(); + + RESTController.request('GET', 'classes/MyObject', {}, {}); + await flushPromises(); + expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( + [["X-Parse-Request-Id", '1001']] + ); + CoreManager.set('IDEMPOTENCY', false); + }); + it('reports upload/download progress of the AJAX request when callback is provided', (done) => { const xhr = mockXHR([{ status: 200, response: { success: true } }], { progress: { From 0a53d85713ad3d606aa85e9f4a40ac57a954bfe0 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 18 Aug 2020 19:46:31 -0500 Subject: [PATCH 2/6] fix tests --- integration/test/ParseQueryTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js index 278501d19..5ccfb1997 100644 --- a/integration/test/ParseQueryTest.js +++ b/integration/test/ParseQueryTest.js @@ -1804,12 +1804,12 @@ describe('Parse Query', () => { ]; const objects = []; for (const i in subjects) { - const obj = new TestObject({ comment: subjects[i] }); + const obj = new TestObject({ subject: subjects[i] }); objects.push(obj); } return Parse.Object.saveAll(objects).then(() => { const q = new Parse.Query(TestObject); - q.fullText('comment', 'coffee'); + q.fullText('subject', 'coffee'); q.ascending('$score'); q.select('$score'); return q.find(); From 2fb95a32c4e0252ea11c5b34e8926fe2cafca69b Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 18 Aug 2020 20:23:12 -0500 Subject: [PATCH 3/6] Improve coverage --- integration/test/IdempotencyTest.js | 2 +- integration/test/clear.js | 2 +- src/__tests__/RESTController-test.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/integration/test/IdempotencyTest.js b/integration/test/IdempotencyTest.js index 94db27600..2f8814e55 100644 --- a/integration/test/IdempotencyTest.js +++ b/integration/test/IdempotencyTest.js @@ -11,7 +11,7 @@ describe('Idempotency', () => { Parse.initialize('integration', null, 'notsosecret'); Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); Parse.Storage._clear(); - clear(true).then(() => { + clear().then(() => { done(); }); }); diff --git a/integration/test/clear.js b/integration/test/clear.js index 8855196d8..1f5366a82 100644 --- a/integration/test/clear.js +++ b/integration/test/clear.js @@ -6,6 +6,6 @@ const Parse = require('../../node'); * * @param {boolean} fast set to true if it's ok to just drop objects and not indexes. */ -module.exports = function(fast = false) { +module.exports = function(fast = true) { return Parse._ajax('GET', `http://localhost:1337/clear/${fast}`, ''); }; diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index d256d3ee8..e26aebfeb 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -34,6 +34,7 @@ function flushPromises() { describe('RESTController', () => { it('throws if there is no XHR implementation', () => { RESTController._setXHR(null); + expect(RESTController._getXHR()).toBe(null); expect(RESTController.ajax.bind(null, 'GET', 'users/me', {})).toThrow( 'Cannot make a request: No definition of XMLHttpRequest was found.' ); From a0fa114b9869ce8998c628f379b1799219d8f676 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 19 Aug 2020 21:06:43 -0500 Subject: [PATCH 4/6] Handle network retry --- integration/test/IdempotencyTest.js | 16 ++++------------ src/RESTController.js | 6 ++++-- src/__tests__/RESTController-test.js | 26 +++++++++++++++++++++++--- src/__tests__/test_helpers/mockXHR.js | 8 +++++++- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/integration/test/IdempotencyTest.js b/integration/test/IdempotencyTest.js index 2f8814e55..634962b12 100644 --- a/integration/test/IdempotencyTest.js +++ b/integration/test/IdempotencyTest.js @@ -1,6 +1,5 @@ 'use strict'; -const assert = require('assert'); const clear = require('./clear'); const Parse = require('../../node'); @@ -30,19 +29,12 @@ describe('Idempotency', () => { } restController._setXHR(DuplicateXHR); await Parse.Cloud.run('CloudFunctionIdempotency'); - try { - await Parse.Cloud.run('CloudFunctionIdempotency'); - } catch (e) { - assert.equal(e.code, Parse.Error.DUPLICATE_REQUEST); - } - try { - await Parse.Cloud.run('CloudFunctionIdempotency'); - } catch (e) { - assert.equal(e.code, Parse.Error.DUPLICATE_REQUEST); - } + await expectAsync(Parse.Cloud.run('CloudFunctionIdempotency')).toBeRejectedWithError('Duplicate request'); + await expectAsync(Parse.Cloud.run('CloudFunctionIdempotency')).toBeRejectedWithError('Duplicate request'); + const query = new Parse.Query(Item); const results = await query.find(); - assert.equal(results.length, 1); + expect(results.length).toBe(1); restController._setXHR(XHR); }); diff --git a/src/RESTController.js b/src/RESTController.js index 5d068c0cb..e3d8a24e8 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -95,6 +95,8 @@ const RESTController = { return ajaxIE9(method, url, data, headers, options); } const promise = resolvingPromise(); + const isIdempotent = CoreManager.get('IDEMPOTENCY'); + const requestId = isIdempotent ? uuidv4() : ''; let attempts = 0; const dispatch = function() { @@ -154,8 +156,8 @@ const RESTController = { headers['User-Agent'] = 'Parse/' + CoreManager.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; } - if (CoreManager.get('IDEMPOTENCY')) { - headers['X-Parse-Request-Id'] = uuidv4(); + if (isIdempotent && ['POST', 'PUT'].includes(method)) { + headers['X-Parse-Request-Id'] = requestId; } if (CoreManager.get('SERVER_AUTH_TYPE') && CoreManager.get('SERVER_AUTH_TOKEN')) { headers['Authorization'] = CoreManager.get('SERVER_AUTH_TYPE') + ' ' + CoreManager.get('SERVER_AUTH_TOKEN'); diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index e26aebfeb..6b01fce0b 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -437,7 +437,7 @@ describe('RESTController', () => { CoreManager.set('SERVER_AUTH_TOKEN', null); }); - it('sends requestId header for idempotency', async () => { + it('idempotency - sends requestId header', async () => { CoreManager.set('IDEMPOTENCY', true); const requestIdHeader = (header) => 'X-Parse-Request-Id' === header[0]; const xhr = { @@ -446,14 +446,14 @@ describe('RESTController', () => { send: jest.fn() }; RESTController._setXHR(function() { return xhr; }); - RESTController.request('GET', 'classes/MyObject', {}, {}); + RESTController.request('POST', 'classes/MyObject', {}, {}); await flushPromises(); expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( [["X-Parse-Request-Id", '1000']] ); xhr.setRequestHeader.mockClear(); - RESTController.request('GET', 'classes/MyObject', {}, {}); + RESTController.request('PUT', 'classes/MyObject', {}, {}); await flushPromises(); expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( [["X-Parse-Request-Id", '1001']] @@ -461,6 +461,26 @@ describe('RESTController', () => { CoreManager.set('IDEMPOTENCY', false); }); + it('idempotency - handle requestId on network retries', (done) => { + CoreManager.set('IDEMPOTENCY', true); + RESTController._setXHR(mockXHR([ + { status: 500 }, + { status: 500 }, + { status: 200, response: { success: true }} + ])); + RESTController.ajax('POST', 'users', {}).then(({ response, status, xhr }) => { + // X-Parse-Request-Id should be the same for all retries + const requestIdHeaders = xhr.setRequestHeader.mock.calls.filter((header) => 'X-Parse-Request-Id' === header[0]) + expect(requestIdHeaders.every((header) => header[1] === '1000')).toBeTruthy(); + expect(requestIdHeaders.length).toBe(3); + expect(response).toEqual({ success: true }); + expect(status).toBe(200); + done(); + }); + jest.runAllTimers(); + CoreManager.set('IDEMPOTENCY', false); + }); + it('reports upload/download progress of the AJAX request when callback is provided', (done) => { const xhr = mockXHR([{ status: 200, response: { success: true } }], { progress: { diff --git a/src/__tests__/test_helpers/mockXHR.js b/src/__tests__/test_helpers/mockXHR.js index cd23a6c3f..529c762d7 100644 --- a/src/__tests__/test_helpers/mockXHR.js +++ b/src/__tests__/test_helpers/mockXHR.js @@ -19,9 +19,15 @@ function mockXHR(results, options = {}) { const XHR = function() { }; let attempts = 0; + const headers = {}; XHR.prototype = { open: function() { }, - setRequestHeader: function() { }, + setRequestHeader: jest.fn((key, value) => { + headers[key] = value; + }), + getRequestHeader: function(key) { + return headers[key]; + }, upload: function() { }, send: function() { this.status = results[attempts].status; From 917d5e72a3a77ba7b0843c2239ae7d105b9fa3cf Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 20 Aug 2020 08:41:23 -0500 Subject: [PATCH 5/6] More Tests --- integration/server.js | 6 +- integration/test/IdempotencyTest.js | 59 ++++++++++++---- src/__tests__/RESTController-test.js | 102 +++++++++++++++------------ 3 files changed, 108 insertions(+), 59 deletions(-) diff --git a/integration/server.js b/integration/server.js index 3bf21c838..158794177 100644 --- a/integration/server.js +++ b/integration/server.js @@ -30,7 +30,11 @@ const api = new ParseServer({ verbose: false, silent: true, idempotencyOptions: { - paths: ['functions/CloudFunctionIdempotency'], + paths: [ + 'functions/CloudFunctionIdempotency', + 'jobs/CloudJob1', + 'classes/IdempotentTest' + ], ttl: 120 } }); diff --git a/integration/test/IdempotencyTest.js b/integration/test/IdempotencyTest.js index 634962b12..73544f0e4 100644 --- a/integration/test/IdempotencyTest.js +++ b/integration/test/IdempotencyTest.js @@ -4,30 +4,35 @@ const clear = require('./clear'); const Parse = require('../../node'); const Item = Parse.Object.extend('IdempotencyItem'); +const RESTController = Parse.CoreManager.getRESTController(); + +const XHR = RESTController._getXHR(); +function DuplicateXHR(requestId) { + function XHRWrapper() { + const xhr = new XHR(); + const send = xhr.send; + xhr.send = function () { + this.setRequestHeader('X-Parse-Request-Id', requestId); + send.apply(this, arguments); + } + return xhr; + } + return XHRWrapper; +} describe('Idempotency', () => { beforeEach((done) => { Parse.initialize('integration', null, 'notsosecret'); Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse'); Parse.Storage._clear(); + RESTController._setXHR(XHR); clear().then(() => { done(); }); }); - it('duplicate cloud code function request', async () => { - const restController = Parse.CoreManager.getRESTController(); - const XHR = restController._getXHR(); - function DuplicateXHR() { - const xhr = new XHR(); - const send = xhr.send; - xhr.send = function () { - this.setRequestHeader('X-Parse-Request-Id', '1234'); - send.apply(this, arguments); - } - return xhr; - } - restController._setXHR(DuplicateXHR); + it('handle duplicate cloud code function request', async () => { + RESTController._setXHR(DuplicateXHR('1234')); await Parse.Cloud.run('CloudFunctionIdempotency'); await expectAsync(Parse.Cloud.run('CloudFunctionIdempotency')).toBeRejectedWithError('Duplicate request'); await expectAsync(Parse.Cloud.run('CloudFunctionIdempotency')).toBeRejectedWithError('Duplicate request'); @@ -35,7 +40,33 @@ describe('Idempotency', () => { const query = new Parse.Query(Item); const results = await query.find(); expect(results.length).toBe(1); + }); + + it('handle duplicate job request', async () => { + RESTController._setXHR(DuplicateXHR('1234')); + const params = { startedBy: 'Monty Python' }; + const jobStatusId = await Parse.Cloud.startJob('CloudJob1', params); + await expectAsync(Parse.Cloud.startJob('CloudJob1', params)).toBeRejectedWithError('Duplicate request'); - restController._setXHR(XHR); + const jobStatus = await Parse.Cloud.getJobStatus(jobStatusId); + expect(jobStatus.get('status')).toBe('succeeded'); + expect(jobStatus.get('params').startedBy).toBe('Monty Python'); + }); + + it('handle duplicate POST / PUT request', async () => { + RESTController._setXHR(DuplicateXHR('1234')); + const testObject = new Parse.Object('IdempotentTest'); + await testObject.save(); + await expectAsync(testObject.save()).toBeRejectedWithError('Duplicate request'); + + RESTController._setXHR(DuplicateXHR('5678')); + testObject.set('foo', 'bar'); + await testObject.save(); + await expectAsync(testObject.save()).toBeRejectedWithError('Duplicate request'); + + const query = new Parse.Query('IdempotentTest'); + const results = await query.find(); + expect(results.length).toBe(1); + expect(results[0].get('foo')).toBe('bar'); }); }); diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 6b01fce0b..73b58b60d 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -257,6 +257,64 @@ describe('RESTController', () => { expect(response.result).toBe('hello'); }); + it('idempotency - sends requestId header', async () => { + CoreManager.set('IDEMPOTENCY', true); + const requestIdHeader = (header) => 'X-Parse-Request-Id' === header[0]; + const xhr = { + setRequestHeader: jest.fn(), + open: jest.fn(), + send: jest.fn() + }; + RESTController._setXHR(function() { return xhr; }); + RESTController.request('POST', 'classes/MyObject', {}, {}); + await flushPromises(); + expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( + [["X-Parse-Request-Id", '1000']] + ); + xhr.setRequestHeader.mockClear(); + + RESTController.request('PUT', 'classes/MyObject', {}, {}); + await flushPromises(); + expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( + [["X-Parse-Request-Id", '1001']] + ); + CoreManager.set('IDEMPOTENCY', false); + }); + + it('idempotency - handle requestId on network retries', (done) => { + CoreManager.set('IDEMPOTENCY', true); + RESTController._setXHR(mockXHR([ + { status: 500 }, + { status: 500 }, + { status: 200, response: { success: true }} + ])); + RESTController.ajax('POST', 'users', {}).then(({ response, status, xhr }) => { + // X-Parse-Request-Id should be the same for all retries + const requestIdHeaders = xhr.setRequestHeader.mock.calls.filter((header) => 'X-Parse-Request-Id' === header[0]) + expect(requestIdHeaders.every((header) => header[1] === requestIdHeaders[0][1])).toBeTruthy(); + expect(requestIdHeaders.length).toBe(3); + expect(response).toEqual({ success: true }); + expect(status).toBe(200); + done(); + }); + jest.runAllTimers(); + CoreManager.set('IDEMPOTENCY', false); + }); + + it('idempotency - should properly handle url method not POST / PUT', () => { + CoreManager.set('IDEMPOTENCY', true); + const xhr = { + setRequestHeader: jest.fn(), + open: jest.fn(), + send: jest.fn() + }; + RESTController._setXHR(function() { return xhr; }); + RESTController.ajax('GET', 'users/me', {}, {}); + const requestIdHeaders = xhr.setRequestHeader.mock.calls.filter((header) => 'X-Parse-Request-Id' === header[0]); + expect(requestIdHeaders.length).toBe(0); + CoreManager.set('IDEMPOTENCY', false); + }); + it('handles aborted requests', (done) => { const XHR = function() { }; XHR.prototype = { @@ -437,50 +495,6 @@ describe('RESTController', () => { CoreManager.set('SERVER_AUTH_TOKEN', null); }); - it('idempotency - sends requestId header', async () => { - CoreManager.set('IDEMPOTENCY', true); - const requestIdHeader = (header) => 'X-Parse-Request-Id' === header[0]; - const xhr = { - setRequestHeader: jest.fn(), - open: jest.fn(), - send: jest.fn() - }; - RESTController._setXHR(function() { return xhr; }); - RESTController.request('POST', 'classes/MyObject', {}, {}); - await flushPromises(); - expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( - [["X-Parse-Request-Id", '1000']] - ); - xhr.setRequestHeader.mockClear(); - - RESTController.request('PUT', 'classes/MyObject', {}, {}); - await flushPromises(); - expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual( - [["X-Parse-Request-Id", '1001']] - ); - CoreManager.set('IDEMPOTENCY', false); - }); - - it('idempotency - handle requestId on network retries', (done) => { - CoreManager.set('IDEMPOTENCY', true); - RESTController._setXHR(mockXHR([ - { status: 500 }, - { status: 500 }, - { status: 200, response: { success: true }} - ])); - RESTController.ajax('POST', 'users', {}).then(({ response, status, xhr }) => { - // X-Parse-Request-Id should be the same for all retries - const requestIdHeaders = xhr.setRequestHeader.mock.calls.filter((header) => 'X-Parse-Request-Id' === header[0]) - expect(requestIdHeaders.every((header) => header[1] === '1000')).toBeTruthy(); - expect(requestIdHeaders.length).toBe(3); - expect(response).toEqual({ success: true }); - expect(status).toBe(200); - done(); - }); - jest.runAllTimers(); - CoreManager.set('IDEMPOTENCY', false); - }); - it('reports upload/download progress of the AJAX request when callback is provided', (done) => { const xhr = mockXHR([{ status: 200, response: { success: true } }], { progress: { From c41bc762d7f9ca1393697fe0e6d75bdcdbf0e4f3 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 20 Aug 2020 08:43:45 -0500 Subject: [PATCH 6/6] Clean up --- src/RESTController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RESTController.js b/src/RESTController.js index e3d8a24e8..123f64b21 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -95,7 +95,7 @@ const RESTController = { return ajaxIE9(method, url, data, headers, options); } const promise = resolvingPromise(); - const isIdempotent = CoreManager.get('IDEMPOTENCY'); + const isIdempotent = CoreManager.get('IDEMPOTENCY') && ['POST', 'PUT'].includes(method); const requestId = isIdempotent ? uuidv4() : ''; let attempts = 0; @@ -156,7 +156,7 @@ const RESTController = { headers['User-Agent'] = 'Parse/' + CoreManager.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; } - if (isIdempotent && ['POST', 'PUT'].includes(method)) { + if (isIdempotent) { headers['X-Parse-Request-Id'] = requestId; } if (CoreManager.get('SERVER_AUTH_TYPE') && CoreManager.get('SERVER_AUTH_TOKEN')) {