diff --git a/package-lock.json b/package-lock.json index a9220cf3ae..1e5243010f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,9 +25,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==" } } }, @@ -2719,21 +2719,48 @@ "to-fast-properties": "^2.0.0" } }, + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@graphql-tools/batch-delegate": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-delegate/-/batch-delegate-6.0.13.tgz", + "integrity": "sha512-My2voosSQLjNDOKs4RXev4v9kmqHK6LxzXGg2fdOO59UQJ00cSbY9VNZnIIRWcOi6+JdOCAcbeuVsmocLUd7Jg==", + "requires": { + "@graphql-tools/delegate": "6.0.13", + "dataloader": "2.0.0", + "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==" + } + } + }, "@graphql-tools/delegate": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.12.tgz", - "integrity": "sha512-52bac1Ct1s0c8aSTVCbnc5FI2LC+NqUFSs+5/mP1k5hIEW2GROGBeZdbRs2GQaHir1vKUYIyHzlZIIBMzOZ/gA==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.13.tgz", + "integrity": "sha512-DoHzhQFQLSS0dw1afLz7h290INNSw3jmIU9CP+CTZ3Ikgfy+51abWjw5VDjgSvFOgnP0YbTR2r1Ett6JwUsryQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2747,19 +2774,19 @@ } }, "@graphql-tools/merge": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.12.tgz", - "integrity": "sha512-GGvdIoTad6PJk/d1omPlGQ25pCFWmjuGkARYZ71qWI/c4FEA8EdGoOoPz3shhaKXyLdRiu84S758z4ZtDQiYVw==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.13.tgz", + "integrity": "sha512-t8M1ytadf8JAKBSappzWqDzpCoufYPRPimadqxFxgeGZ/LrwzsJ7lCw8EIT1eZZYT7DbqYfAUalIQ124GazuEQ==", "requires": { - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2773,18 +2800,18 @@ } }, "@graphql-tools/schema": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.12.tgz", - "integrity": "sha512-XUmKJ+ipENaxuXIX4GapsLAUl1dFQBUg+S4ZbgtKVlwrPhZJ9bkjIqnUHk3wg4S4VXqzLX97ol1e4g9N6XLkYg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.13.tgz", + "integrity": "sha512-Uy2J7L3rr8QeIr+SwWltcAlvAu3q0EKuq41qMhL23dgqpoTAg1BIWlvYSoOdoGaRZP95bvLCEiyf/X5Q1VMEFw==", "requires": { - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/utils": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2798,22 +2825,23 @@ } }, "@graphql-tools/stitch": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.12.tgz", - "integrity": "sha512-I+9l5Ws30Fn3nx0CIDUDMGP0nhexMEJyzfQn1t9DuOTy2QHPQ5YpaZ8hxv6y5+X23EJBU9AebqvNSvWNEO6XJQ==", - "requires": { - "@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", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-6.0.13.tgz", + "integrity": "sha512-bbtGujHZTBZECUR7JlMp5a5tAm0PDeZIWnAGAcGrnOpHSdBq0kYP3NxrFwuXRP53EkV+C5eYT7zy5uQum1S34Q==", + "requires": { + "@graphql-tools/batch-delegate": "6.0.13", + "@graphql-tools/delegate": "6.0.13", + "@graphql-tools/merge": "6.0.13", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", + "@graphql-tools/wrap": "6.0.13", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -2827,30 +2855,30 @@ } }, "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" } }, "@graphql-tools/wrap": { - "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==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.13.tgz", + "integrity": "sha512-QJAHUpZN8fEupYNLDXtahlPH4/yC/AOXxOoaBUOpQTEFr7A3eMLGk9FgINWlGIEgWpJYH8G6bo9N0bE64LIsGw==", "requires": { - "@graphql-tools/delegate": "6.0.12", - "@graphql-tools/schema": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/delegate": "6.0.13", + "@graphql-tools/schema": "6.0.13", + "@graphql-tools/utils": "6.0.13", "aggregate-error": "3.0.1", "tslib": "~2.0.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.12.tgz", - "integrity": "sha512-MuFSkxXCe2QoD5QJPJ/1WIm0YnBzzXpkq9d/XznVAWptHFRwtwIbZ1xcREjYquFvoZ7ddsjZfyvUN/5ulmHhhg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.13.tgz", + "integrity": "sha512-9jTI1HAM9HoXU2tV5CV8w7XXvJe7r37rythgo2FTe1Mws0+O6pQNjVVg7GDDr6FJ/eyMccyMx2SIf+5t7/XPrQ==", "requires": { "@ardatan/aggregate-error": "0.0.1", "camel-case": "4.1.1" @@ -3163,9 +3191,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==", "requires": { "@types/express": "*" } @@ -3667,9 +3695,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==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "@apollographql/graphql-playground-html": "1.6.26", @@ -3731,9 +3759,9 @@ "integrity": "sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ==" }, "apollo-server-express": { - "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==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.0.tgz", + "integrity": "sha512-mBIvKcF8gApj7wbmqe0A4Tsy+Pw66mI6cmtD912bG59KhUBveSCZ21dDlRSvnXUyK+GOo2ItwcUEtmks+Z2Pqw==", "requires": { "@apollographql/graphql-playground-html": "1.6.26", "@types/accepts": "^1.3.5", @@ -3741,7 +3769,7 @@ "@types/cors": "^2.8.4", "@types/express": "4.17.4", "accepts": "^1.3.5", - "apollo-server-core": "^2.15.1", + "apollo-server-core": "^2.16.0", "apollo-server-types": "^0.5.1", "body-parser": "^1.18.3", "cors": "^2.8.4", @@ -4813,11 +4841,6 @@ "simple-swizzle": "^0.2.2" } }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -5087,6 +5110,11 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" }, + "dataloader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", + "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -5380,16 +5408,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, "dicer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", @@ -5578,12 +5596,9 @@ "dev": true }, "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "requires": { - "env-variable": "0.0.x" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "encodeurl": { "version": "1.0.2", @@ -5614,11 +5629,6 @@ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", "dev": true }, - "env-variable": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -6402,9 +6412,9 @@ } }, "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" }, "figures": { "version": "3.1.0", @@ -6601,6 +6611,11 @@ "integrity": "sha512-mX6qjJVi7aLqR9sDf8QIHt8yYEWQbkMLw7qFoC7sM/AbJwvqFm3pATPN96thsaL9o1rrshvxJpSgoj1PJSC3KA==", "dev": true }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "follow-redirects": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", @@ -7716,9 +7731,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==", "requires": { "has-symbols": "^1.0.1" } @@ -8223,12 +8238,9 @@ } }, "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "requires": { - "colornames": "^1.1.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, "lcov-parse": { "version": "1.0.0", @@ -8244,9 +8256,9 @@ } }, "ldapjs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.0.0.tgz", - "integrity": "sha512-ZESQmVoG4a2ZX51pl/aRI+/kqiN2eRWMgHIsNZ2TYf37/S64OPnVJL5Vd5gdZR/qRPZVe5uuKW5p0GK2FUx/FQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.1.0.tgz", + "integrity": "sha512-ppGqhf2Jn7BTmCHgjrdQcPqq2ieR/x+CAvF1EZboTNwDt66T7h7A1tFbwQwJPna27s0F93H8jNWOIMt0DTKnvw==", "requires": { "abstract-logging": "^1.0.0", "asn1": "^0.2.4", @@ -8529,9 +8541,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==" }, "lodash.assignin": { "version": "4.2.0", @@ -8815,13 +8827,13 @@ } }, "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", "requires": { "colors": "^1.2.1", "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", + "fecha": "^4.2.0", "ms": "^2.1.1", "triple-beam": "^1.3.0" } @@ -9870,9 +9882,12 @@ } }, "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } }, "onetime": { "version": "5.1.0", @@ -12183,9 +12198,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "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==" }, "v8-compile-cache": { "version": "2.1.0", @@ -12255,30 +12270,70 @@ } }, "winston": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", - "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.2.tgz", + "integrity": "sha512-vTOrUZlyQPS8VpCcQ1JT8BumDAUe4awCHZ9nmGgO7LqkV4atj0dKa5suA7Trf7QKtBszE2yUs9d8744Kz9j4jQ==", "requires": { - "async": "^2.6.1", - "diagnostics": "^1.1.1", - "is-stream": "^1.1.0", - "logform": "^2.1.1", - "one-time": "0.0.4", - "readable-stream": "^3.1.1", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.3.0" + "winston-transport": "^4.4.0" }, "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "requires": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } } } }, diff --git a/package.json b/package.json index 3b4182e9f6..7a53adf36a 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,13 @@ "license": "BSD-3-Clause", "dependencies": { "@apollographql/graphql-playground-html": "1.6.26", - "@graphql-tools/stitch": "6.0.12", - "@graphql-tools/utils": "6.0.12", + "@graphql-tools/stitch": "6.0.13", + "@graphql-tools/utils": "6.0.13", "@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.1", + "apollo-server-express": "2.16.0", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "commander": "5.1.0", @@ -41,8 +41,8 @@ "intersect": "1.0.1", "jsonwebtoken": "8.5.1", "jwks-rsa": "1.8.1", - "ldapjs": "2.0.0", - "lodash": "4.17.19", + "ldapjs": "2.1.0", + "lodash": "4.17.20", "lru-cache": "5.1.1", "mime": "2.4.6", "mongodb": "3.5.9", @@ -53,8 +53,8 @@ "semver": "7.3.2", "subscriptions-transport-ws": "0.9.17", "tv4": "1.3.0", - "uuid": "8.2.0", - "winston": "3.2.1", + "uuid": "8.3.0", + "winston": "3.3.2", "winston-daily-rotate-file": "4.5.0", "ws": "7.3.1" }, diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index a42017769b..2465ab1181 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -25,7 +25,6 @@ describe('AuthenticationProviders', function () { 'gcenter', 'gpgames', 'facebook', - 'facebookaccountkit', 'github', 'instagram', 'google', @@ -43,7 +42,7 @@ describe('AuthenticationProviders', function () { 'phantauth', 'microsoft', ].map(function (providerName) { - it('Should validate structure of ' + providerName, (done) => { + it('Should validate structure of ' + providerName, done => { const provider = require('../lib/Adapters/Auth/' + providerName); jequal(typeof provider.validateAuthData, 'function'); jequal(typeof provider.validateAppId, 'function'); @@ -71,7 +70,7 @@ describe('AuthenticationProviders', function () { return; } spyOn(require('../lib/Adapters/Auth/httpsRequest'), 'get').and.callFake( - (options) => { + options => { if ( options === 'https://oauth.vk.com/access_token?client_id=appId&client_secret=appSecret&v=5.59&grant_type=client_credentials' @@ -175,7 +174,7 @@ describe('AuthenticationProviders', function () { body: jsonBody, }; return request(options) - .then((response) => { + .then(response => { if (callback) { callback(null, response, response.data); } @@ -184,7 +183,7 @@ describe('AuthenticationProviders', function () { body: response.data, }; }) - .catch((error) => { + .catch(error => { if (callback) { callback(error); } @@ -192,7 +191,7 @@ describe('AuthenticationProviders', function () { }); }; - it('should create user with REST API', (done) => { + it('should create user with REST API', done => { createOAuthUser((error, response, body) => { expect(error).toBe(null); const b = body; @@ -203,7 +202,7 @@ describe('AuthenticationProviders', function () { const q = new Parse.Query('_Session'); q.equalTo('sessionToken', sessionToken); q.first({ useMasterKey: true }) - .then((res) => { + .then(res => { if (!res) { fail('should not fail fetching the session'); done(); @@ -219,7 +218,7 @@ describe('AuthenticationProviders', function () { }); }); - it('should only create a single user with REST API', (done) => { + it('should only create a single user with REST API', done => { let objectId; createOAuthUser((error, response, body) => { expect(error).toBe(null); @@ -239,9 +238,9 @@ describe('AuthenticationProviders', function () { }); }); - it("should fail to link if session token don't match user", (done) => { + it("should fail to link if session token don't match user", done => { Parse.User.signUp('myUser', 'password') - .then((user) => { + .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); }) .then(() => { @@ -250,7 +249,7 @@ describe('AuthenticationProviders', function () { .then(() => { return Parse.User.signUp('myUser2', 'password'); }) - .then((user) => { + .then(user => { return createOAuthUserWithSessionToken(user.getSessionToken()); }) .then(fail, ({ data }) => { @@ -330,7 +329,7 @@ describe('AuthenticationProviders', function () { expect(typeof authAdapter.validateAppId).toBe('function'); } - it('properly loads custom adapter', (done) => { + it('properly loads custom adapter', done => { const validAuthData = { id: 'hello', token: 'world', @@ -370,14 +369,14 @@ describe('AuthenticationProviders', function () { expect(appIdSpy).not.toHaveBeenCalled(); done(); }, - (err) => { + err => { jfail(err); done(); } ); }); - it('properly loads custom adapter module object', (done) => { + it('properly loads custom adapter module object', done => { const authenticationHandler = authenticationLoader({ customAuthentication: path.resolve('./spec/support/CustomAuth.js'), }); @@ -394,14 +393,14 @@ describe('AuthenticationProviders', function () { () => { done(); }, - (err) => { + err => { jfail(err); done(); } ); }); - it('properly loads custom adapter module object (again)', (done) => { + it('properly loads custom adapter module object (again)', done => { const authenticationHandler = authenticationLoader({ customAuthentication: { module: path.resolve('./spec/support/CustomAuthFunction.js'), @@ -421,7 +420,7 @@ describe('AuthenticationProviders', function () { () => { done(); }, - (err) => { + err => { jfail(err); done(); } @@ -512,84 +511,6 @@ describe('AuthenticationProviders', function () { expect(appIds).toEqual(['a', 'b']); expect(providerOptions).toEqual(options.custom); }); - - it('properly loads Facebook accountkit adapter with options', () => { - const options = { - facebookaccountkit: { - appIds: ['a', 'b'], - appSecret: 'secret', - }, - }; - const { - adapter, - appIds, - providerOptions, - } = authenticationLoader.loadAuthAdapter('facebookaccountkit', options); - validateAuthenticationAdapter(adapter); - expect(appIds).toEqual(['a', 'b']); - expect(providerOptions.appSecret).toEqual('secret'); - }); - - it('should fail if Facebook appIds is not configured properly', (done) => { - const options = { - facebookaccountkit: { - appIds: [], - }, - }; - const { adapter, appIds } = authenticationLoader.loadAuthAdapter( - 'facebookaccountkit', - options - ); - adapter.validateAppId(appIds).then(done.fail, (err) => { - expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND); - done(); - }); - }); - - it('should fail to validate Facebook accountkit auth with bad token', (done) => { - const options = { - facebookaccountkit: { - appIds: ['a', 'b'], - }, - }; - const authData = { - id: 'fakeid', - access_token: 'badtoken', - }; - const { adapter } = authenticationLoader.loadAuthAdapter( - 'facebookaccountkit', - options - ); - adapter.validateAuthData(authData).then(done.fail, (err) => { - expect(err.code).toBe(190); - expect(err.type).toBe('OAuthException'); - done(); - }); - }); - - it('should fail to validate Facebook accountkit auth with bad token regardless of app secret proof', (done) => { - const options = { - facebookaccountkit: { - appIds: ['a', 'b'], - appSecret: 'badsecret', - }, - }; - const authData = { - id: 'fakeid', - access_token: 'badtoken', - }; - const { adapter, providerOptions } = authenticationLoader.loadAuthAdapter( - 'facebookaccountkit', - options - ); - adapter - .validateAuthData(authData, providerOptions) - .then(done.fail, (err) => { - expect(err.code).toBe(190); - expect(err.type).toBe('OAuthException'); - done(); - }); - }); }); describe('instagram auth adapter', () => { @@ -1653,13 +1574,13 @@ describe('microsoft graph auth adapter', () => { }); }); - it('should fail to validate Microsoft Graph auth with bad token', (done) => { + it('should fail to validate Microsoft Graph auth with bad token', done => { const authData = { id: 'fake-id', mail: 'fake@mail.com', access_token: 'very.long.bad.token', }; - microsoft.validateAuthData(authData).then(done.fail, (err) => { + microsoft.validateAuthData(authData).then(done.fail, err => { expect(err.code).toBe(101); expect(err.message).toBe( 'Microsoft Graph auth is invalid for this user.' diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 00e01c052d..a6dc604c20 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -24,7 +24,7 @@ const mockAdapter = { // Small additional tests to improve overall coverage describe('FilesController', () => { - it('should properly expand objects', (done) => { + it('should properly expand objects', done => { const config = Config.get(Parse.applicationId); const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' @@ -48,7 +48,7 @@ describe('FilesController', () => { done(); }); - it('should create a server log on failure', (done) => { + it('should create a server log on failure', done => { const logController = new LoggerController(new WinstonLoggerAdapter()); reconfigureServer({ filesAdapter: mockAdapter }) @@ -57,20 +57,20 @@ describe('FilesController', () => { () => done.fail('should not succeed'), () => setImmediate(() => Promise.resolve('done')) ) - .then(() => new Promise((resolve) => setTimeout(resolve, 200))) + .then(() => new Promise(resolve => setTimeout(resolve, 200))) .then(() => logController.getLogs({ from: Date.now() - 1000, size: 1000 }) ) - .then((logs) => { + .then(logs => { // we get two logs here: 1. the source of the failure to save the file // and 2 the message that will be sent back to the client. const log1 = logs.find( - (x) => x.message === 'Error creating a file: it failed with xyz' + x => x.message === 'Error creating a file: it failed with xyz' ); expect(log1.level).toBe('error'); - const log2 = logs.find((x) => x.message === 'it failed with xyz'); + const log2 = logs.find(x => x.message === 'it failed with xyz'); expect(log2.level).toBe('error'); expect(log2.code).toBe(130); @@ -78,7 +78,7 @@ describe('FilesController', () => { }); }); - it('should create a parse error when a string is returned', (done) => { + it('should create a parse error when a string is returned', done => { const mock2 = mockAdapter; mock2.validateFilename = () => { return 'Bad file! No biscuit!'; @@ -91,7 +91,7 @@ describe('FilesController', () => { done(); }); - it('should add a unique hash to the file name when the preserveFileName option is false', (done) => { + it('should add a unique hash to the file name when the preserveFileName option is false', done => { const config = Config.get(Parse.applicationId); const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' @@ -114,7 +114,7 @@ describe('FilesController', () => { done(); }); - it('should not add a unique hash to the file name when the preserveFileName option is true', (done) => { + it('should not add a unique hash to the file name when the preserveFileName option is true', done => { const config = Config.get(Parse.applicationId); const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' @@ -145,7 +145,7 @@ describe('FilesController', () => { expect(result).toEqual({}); }); - it('should reject slashes in file names', (done) => { + it('should reject slashes in file names', done => { const gridStoreAdapter = new GridFSBucketAdapter( 'mongodb://localhost:27017/parse' ); @@ -154,7 +154,7 @@ describe('FilesController', () => { done(); }); - it('should also reject slashes in file names', (done) => { + it('should also reject slashes in file names', done => { const gridStoreAdapter = new GridStoreAdapter( 'mongodb://localhost:27017/parse' ); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index c1f23bc733..34cd8a8f11 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -2401,585 +2401,580 @@ describe('ParseGraphQLServer', () => { expect(nodeResult.data.node2.objectId).toBe(obj2.id); expect(nodeResult.data.node2.someField).toBe('some value 2'); }); - - it_only_db('mongo')( - 'Id inputs should work either with global id or object id', - async () => { - try { - await apolloClient.mutate({ - mutation: gql` - mutation CreateClasses { - secondaryObject: createClass( - input: { - name: "SecondaryObject" - schemaFields: { addStrings: [{ name: "someField" }] } - } - ) { - clientMutationId - } - primaryObject: createClass( - input: { - name: "PrimaryObject" - schemaFields: { - addStrings: [{ name: "stringField" }] - addArrays: [{ name: "arrayField" }] - addPointers: [ - { - name: "pointerField" - targetClassName: "SecondaryObject" - } - ] - addRelations: [ - { - name: "relationField" - targetClassName: "SecondaryObject" - } - ] - } + // TODO: (moumouls, davimacedo) Fix flaky test + xit('Id inputs should work either with global id or object id', async () => { + try { + await apolloClient.mutate({ + mutation: gql` + mutation CreateClasses { + secondaryObject: createClass( + input: { + name: "SecondaryObject" + schemaFields: { addStrings: [{ name: "someField" }] } + } + ) { + clientMutationId + } + primaryObject: createClass( + input: { + name: "PrimaryObject" + schemaFields: { + addStrings: [{ name: "stringField" }] + addArrays: [{ name: "arrayField" }] + addPointers: [ + { + name: "pointerField" + targetClassName: "SecondaryObject" + } + ] + addRelations: [ + { + name: "relationField" + targetClassName: "SecondaryObject" + } + ] } - ) { - clientMutationId } + ) { + clientMutationId } - `, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - await resetGraphQLCache(); + await resetGraphQLCache(); - const createSecondaryObjectsResult = await apolloClient.mutate({ - mutation: gql` - mutation CreateSecondaryObjects { - secondaryObject1: createSecondaryObject( - input: { fields: { someField: "some value 1" } } - ) { - secondaryObject { - id - objectId - someField - } + const createSecondaryObjectsResult = await apolloClient.mutate({ + mutation: gql` + mutation CreateSecondaryObjects { + secondaryObject1: createSecondaryObject( + input: { fields: { someField: "some value 1" } } + ) { + secondaryObject { + id + objectId + someField } - secondaryObject2: createSecondaryObject( - input: { fields: { someField: "some value 2" } } - ) { - secondaryObject { - id - someField - } + } + secondaryObject2: createSecondaryObject( + input: { fields: { someField: "some value 2" } } + ) { + secondaryObject { + id + someField } - secondaryObject3: createSecondaryObject( - input: { fields: { someField: "some value 3" } } - ) { - secondaryObject { - objectId - someField - } + } + secondaryObject3: createSecondaryObject( + input: { fields: { someField: "some value 3" } } + ) { + secondaryObject { + objectId + someField } - secondaryObject4: createSecondaryObject( - input: { fields: { someField: "some value 4" } } - ) { - secondaryObject { - id - objectId - } + } + secondaryObject4: createSecondaryObject( + input: { fields: { someField: "some value 4" } } + ) { + secondaryObject { + id + objectId } - secondaryObject5: createSecondaryObject( - input: { fields: { someField: "some value 5" } } - ) { - secondaryObject { - id - } + } + secondaryObject5: createSecondaryObject( + input: { fields: { someField: "some value 5" } } + ) { + secondaryObject { + id } - secondaryObject6: createSecondaryObject( - input: { fields: { someField: "some value 6" } } - ) { - secondaryObject { - objectId - } + } + secondaryObject6: createSecondaryObject( + input: { fields: { someField: "some value 6" } } + ) { + secondaryObject { + objectId } - secondaryObject7: createSecondaryObject( - input: { fields: { someField: "some value 7" } } - ) { - secondaryObject { - someField - } + } + secondaryObject7: createSecondaryObject( + input: { fields: { someField: "some value 7" } } + ) { + secondaryObject { + someField } } - `, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const updateSecondaryObjectsResult = await apolloClient.mutate({ - mutation: gql` - mutation UpdateSecondaryObjects( - $id1: ID! - $id2: ID! - $id3: ID! - $id4: ID! - $id5: ID! - $id6: ID! - ) { - secondaryObject1: updateSecondaryObject( - input: { - id: $id1 - fields: { someField: "some value 11" } - } - ) { - secondaryObject { - id - objectId - someField - } + const updateSecondaryObjectsResult = await apolloClient.mutate({ + mutation: gql` + mutation UpdateSecondaryObjects( + $id1: ID! + $id2: ID! + $id3: ID! + $id4: ID! + $id5: ID! + $id6: ID! + ) { + secondaryObject1: updateSecondaryObject( + input: { + id: $id1 + fields: { someField: "some value 11" } } - secondaryObject2: updateSecondaryObject( - input: { - id: $id2 - fields: { someField: "some value 22" } - } - ) { - secondaryObject { - id - someField - } + ) { + secondaryObject { + id + objectId + someField } - secondaryObject3: updateSecondaryObject( - input: { - id: $id3 - fields: { someField: "some value 33" } - } - ) { - secondaryObject { - objectId - someField - } + } + secondaryObject2: updateSecondaryObject( + input: { + id: $id2 + fields: { someField: "some value 22" } } - secondaryObject4: updateSecondaryObject( - input: { - id: $id4 - fields: { someField: "some value 44" } - } - ) { - secondaryObject { - id - objectId - } + ) { + secondaryObject { + id + someField } - secondaryObject5: updateSecondaryObject( - input: { - id: $id5 - fields: { someField: "some value 55" } - } - ) { - secondaryObject { - id - } + } + secondaryObject3: updateSecondaryObject( + input: { + id: $id3 + fields: { someField: "some value 33" } } - secondaryObject6: updateSecondaryObject( - input: { - id: $id6 - fields: { someField: "some value 66" } - } - ) { - secondaryObject { - objectId - } + ) { + secondaryObject { + objectId + someField } } - `, - variables: { - id1: - createSecondaryObjectsResult.data.secondaryObject1 - .secondaryObject.id, - id2: - createSecondaryObjectsResult.data.secondaryObject2 - .secondaryObject.id, - id3: - createSecondaryObjectsResult.data.secondaryObject3 - .secondaryObject.objectId, - id4: - createSecondaryObjectsResult.data.secondaryObject4 - .secondaryObject.objectId, - id5: - createSecondaryObjectsResult.data.secondaryObject5 - .secondaryObject.id, - id6: - createSecondaryObjectsResult.data.secondaryObject6 - .secondaryObject.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, - }, - }); - - const deleteSecondaryObjectsResult = await apolloClient.mutate({ - mutation: gql` - mutation DeleteSecondaryObjects( - $id1: ID! - $id3: ID! - $id5: ID! - $id6: ID! + secondaryObject4: updateSecondaryObject( + input: { + id: $id4 + fields: { someField: "some value 44" } + } ) { - secondaryObject1: deleteSecondaryObject( - input: { id: $id1 } - ) { - secondaryObject { - id - objectId - someField - } + secondaryObject { + id + objectId } - secondaryObject3: deleteSecondaryObject( - input: { id: $id3 } - ) { - secondaryObject { - objectId - someField - } + } + secondaryObject5: updateSecondaryObject( + input: { + id: $id5 + fields: { someField: "some value 55" } } - secondaryObject5: deleteSecondaryObject( - input: { id: $id5 } - ) { - secondaryObject { - id - } + ) { + secondaryObject { + id } - secondaryObject6: deleteSecondaryObject( - input: { id: $id6 } - ) { - secondaryObject { - objectId - } + } + secondaryObject6: updateSecondaryObject( + input: { + id: $id6 + fields: { someField: "some value 66" } + } + ) { + secondaryObject { + objectId } } - `, - variables: { - id1: - updateSecondaryObjectsResult.data.secondaryObject1 - .secondaryObject.id, - id3: - updateSecondaryObjectsResult.data.secondaryObject3 - .secondaryObject.objectId, - id5: - updateSecondaryObjectsResult.data.secondaryObject5 - .secondaryObject.id, - id6: - updateSecondaryObjectsResult.data.secondaryObject6 - .secondaryObject.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + variables: { + id1: + createSecondaryObjectsResult.data.secondaryObject1 + .secondaryObject.id, + id2: + createSecondaryObjectsResult.data.secondaryObject2 + .secondaryObject.id, + id3: + createSecondaryObjectsResult.data.secondaryObject3 + .secondaryObject.objectId, + id4: + createSecondaryObjectsResult.data.secondaryObject4 + .secondaryObject.objectId, + id5: + createSecondaryObjectsResult.data.secondaryObject5 + .secondaryObject.id, + id6: + createSecondaryObjectsResult.data.secondaryObject6 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const getSecondaryObjectsResult = await apolloClient.query({ - query: gql` - query GetSecondaryObjects($id2: ID!, $id4: ID!) { - secondaryObject2: secondaryObject(id: $id2) { + const deleteSecondaryObjectsResult = await apolloClient.mutate({ + mutation: gql` + mutation DeleteSecondaryObjects( + $id1: ID! + $id3: ID! + $id5: ID! + $id6: ID! + ) { + secondaryObject1: deleteSecondaryObject( + input: { id: $id1 } + ) { + secondaryObject { id objectId someField } - secondaryObject4: secondaryObject(id: $id4) { + } + secondaryObject3: deleteSecondaryObject( + input: { id: $id3 } + ) { + secondaryObject { objectId someField } } - `, - variables: { - id2: - updateSecondaryObjectsResult.data.secondaryObject2 - .secondaryObject.id, - id4: - updateSecondaryObjectsResult.data.secondaryObject4 - .secondaryObject.objectId, + secondaryObject5: deleteSecondaryObject( + input: { id: $id5 } + ) { + secondaryObject { + id + } + } + secondaryObject6: deleteSecondaryObject( + input: { id: $id6 } + ) { + secondaryObject { + objectId + } + } + } + `, + variables: { + id1: + updateSecondaryObjectsResult.data.secondaryObject1 + .secondaryObject.id, + id3: + updateSecondaryObjectsResult.data.secondaryObject3 + .secondaryObject.objectId, + id5: + updateSecondaryObjectsResult.data.secondaryObject5 + .secondaryObject.id, + id6: + updateSecondaryObjectsResult.data.secondaryObject6 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + }, + }); + + const getSecondaryObjectsResult = await apolloClient.query({ + query: gql` + query GetSecondaryObjects($id2: ID!, $id4: ID!) { + secondaryObject2: secondaryObject(id: $id2) { + id + objectId + someField + } + secondaryObject4: secondaryObject(id: $id4) { + objectId + someField + } + } + `, + variables: { + id2: + updateSecondaryObjectsResult.data.secondaryObject2 + .secondaryObject.id, + id4: + updateSecondaryObjectsResult.data.secondaryObject4 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const findSecondaryObjectsResult = await apolloClient.query({ - query: gql` - query FindSecondaryObjects( - $id1: ID! - $id2: ID! - $id3: ID! - $id4: ID! - $id5: ID! - $id6: ID! - ) { - secondaryObjects( - where: { - AND: [ - { - OR: [ - { id: { equalTo: $id2 } } - { - AND: [ - { id: { equalTo: $id4 } } - { objectId: { equalTo: $id4 } } - ] - } - ] - } - { id: { notEqualTo: $id1 } } - { id: { notEqualTo: $id3 } } - { objectId: { notEqualTo: $id2 } } - { objectId: { notIn: [$id5, $id6] } } - { id: { in: [$id2, $id4] } } - ] - } - order: [id_ASC, objectId_ASC] - ) { - edges { - node { - id - objectId - someField + const findSecondaryObjectsResult = await apolloClient.query({ + query: gql` + query FindSecondaryObjects( + $id1: ID! + $id2: ID! + $id3: ID! + $id4: ID! + $id5: ID! + $id6: ID! + ) { + secondaryObjects( + where: { + AND: [ + { + OR: [ + { id: { equalTo: $id2 } } + { + AND: [ + { id: { equalTo: $id4 } } + { objectId: { equalTo: $id4 } } + ] + } + ] } + { id: { notEqualTo: $id1 } } + { id: { notEqualTo: $id3 } } + { objectId: { notEqualTo: $id2 } } + { objectId: { notIn: [$id5, $id6] } } + { id: { in: [$id2, $id4] } } + ] + } + order: [id_ASC, objectId_ASC] + ) { + edges { + node { + id + objectId + someField } - count } + count } - `, - variables: { - id1: - deleteSecondaryObjectsResult.data.secondaryObject1 - .secondaryObject.objectId, - id2: getSecondaryObjectsResult.data.secondaryObject2.id, - id3: - deleteSecondaryObjectsResult.data.secondaryObject3 - .secondaryObject.objectId, - id4: - getSecondaryObjectsResult.data.secondaryObject4.objectId, - id5: - deleteSecondaryObjectsResult.data.secondaryObject5 - .secondaryObject.id, - id6: - deleteSecondaryObjectsResult.data.secondaryObject6 - .secondaryObject.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + variables: { + id1: + deleteSecondaryObjectsResult.data.secondaryObject1 + .secondaryObject.objectId, + id2: getSecondaryObjectsResult.data.secondaryObject2.id, + id3: + deleteSecondaryObjectsResult.data.secondaryObject3 + .secondaryObject.objectId, + id4: getSecondaryObjectsResult.data.secondaryObject4.objectId, + id5: + deleteSecondaryObjectsResult.data.secondaryObject5 + .secondaryObject.id, + id6: + deleteSecondaryObjectsResult.data.secondaryObject6 + .secondaryObject.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - expect( - findSecondaryObjectsResult.data.secondaryObjects.count - ).toEqual(2); - expect( - findSecondaryObjectsResult.data.secondaryObjects.edges - .map(value => value.node.someField) - .sort() - ).toEqual(['some value 22', 'some value 44']); - expect( - findSecondaryObjectsResult.data.secondaryObjects.edges[0].node - .id - ).toBeLessThan( - findSecondaryObjectsResult.data.secondaryObjects.edges[1].node - .id - ); - expect( - findSecondaryObjectsResult.data.secondaryObjects.edges[0].node - .objectId - ).toBeLessThan( - findSecondaryObjectsResult.data.secondaryObjects.edges[1].node - .objectId - ); - - const createPrimaryObjectResult = await apolloClient.mutate({ - mutation: gql` - mutation CreatePrimaryObject( - $pointer: Any - $secondaryObject2: ID! - $secondaryObject4: ID! - ) { - createPrimaryObject( - input: { - fields: { - stringField: "some value" - arrayField: [1, "abc", $pointer] - pointerField: { link: $secondaryObject2 } - relationField: { - add: [$secondaryObject2, $secondaryObject4] - } + expect( + findSecondaryObjectsResult.data.secondaryObjects.count + ).toEqual(2); + expect( + findSecondaryObjectsResult.data.secondaryObjects.edges + .map(value => value.node.someField) + .sort() + ).toEqual(['some value 22', 'some value 44']); + expect( + findSecondaryObjectsResult.data.secondaryObjects.edges[0].node + .id + ).toBeLessThan( + findSecondaryObjectsResult.data.secondaryObjects.edges[1].node + .id + ); + expect( + findSecondaryObjectsResult.data.secondaryObjects.edges[0].node + .objectId + ).toBeLessThan( + findSecondaryObjectsResult.data.secondaryObjects.edges[1].node + .objectId + ); + + const createPrimaryObjectResult = await apolloClient.mutate({ + mutation: gql` + mutation CreatePrimaryObject( + $pointer: Any + $secondaryObject2: ID! + $secondaryObject4: ID! + ) { + createPrimaryObject( + input: { + fields: { + stringField: "some value" + arrayField: [1, "abc", $pointer] + pointerField: { link: $secondaryObject2 } + relationField: { + add: [$secondaryObject2, $secondaryObject4] } } - ) { - primaryObject { - id - stringField - arrayField { - ... on Element { - value - } - ... on SecondaryObject { - someField - } + } + ) { + primaryObject { + id + stringField + arrayField { + ... on Element { + value } - pointerField { - id - objectId + ... on SecondaryObject { someField } - relationField { - edges { - node { - id - objectId - someField - } + } + pointerField { + id + objectId + someField + } + relationField { + edges { + node { + id + objectId + someField } } } } } - `, - variables: { - pointer: { - __type: 'Pointer', - className: 'SecondaryObject', - objectId: - getSecondaryObjectsResult.data.secondaryObject4 - .objectId, - }, - secondaryObject2: - getSecondaryObjectsResult.data.secondaryObject2.id, - secondaryObject4: + } + `, + variables: { + pointer: { + __type: 'Pointer', + className: 'SecondaryObject', + objectId: getSecondaryObjectsResult.data.secondaryObject4.objectId, }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + secondaryObject2: + getSecondaryObjectsResult.data.secondaryObject2.id, + secondaryObject4: + getSecondaryObjectsResult.data.secondaryObject4.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - const updatePrimaryObjectResult = await apolloClient.mutate({ - mutation: gql` - mutation UpdatePrimaryObject( - $id: ID! - $secondaryObject2: ID! - $secondaryObject4: ID! - ) { - updatePrimaryObject( - input: { - id: $id - fields: { - pointerField: { link: $secondaryObject4 } - relationField: { - remove: [$secondaryObject2, $secondaryObject4] - } + const updatePrimaryObjectResult = await apolloClient.mutate({ + mutation: gql` + mutation UpdatePrimaryObject( + $id: ID! + $secondaryObject2: ID! + $secondaryObject4: ID! + ) { + updatePrimaryObject( + input: { + id: $id + fields: { + pointerField: { link: $secondaryObject4 } + relationField: { + remove: [$secondaryObject2, $secondaryObject4] } } - ) { - primaryObject { - id - stringField - arrayField { - ... on Element { - value - } - ... on SecondaryObject { - someField - } + } + ) { + primaryObject { + id + stringField + arrayField { + ... on Element { + value } - pointerField { - id - objectId + ... on SecondaryObject { someField } - relationField { - edges { - node { - id - objectId - someField - } + } + pointerField { + id + objectId + someField + } + relationField { + edges { + node { + id + objectId + someField } } } } } - `, - variables: { - id: - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.id, - secondaryObject2: - getSecondaryObjectsResult.data.secondaryObject2.id, - secondaryObject4: - getSecondaryObjectsResult.data.secondaryObject4.objectId, - }, - context: { - headers: { - 'X-Parse-Master-Key': 'test', - }, + } + `, + variables: { + id: + createPrimaryObjectResult.data.createPrimaryObject + .primaryObject.id, + secondaryObject2: + getSecondaryObjectsResult.data.secondaryObject2.id, + secondaryObject4: + getSecondaryObjectsResult.data.secondaryObject4.objectId, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', }, - }); + }, + }); - expect( - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.stringField - ).toEqual('some value'); - expect( - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.arrayField - ).toEqual([ - { __typename: 'Element', value: 1 }, - { __typename: 'Element', value: 'abc' }, - { __typename: 'SecondaryObject', someField: 'some value 44' }, - ]); - expect( - createPrimaryObjectResult.data.createPrimaryObject - .primaryObject.pointerField.someField - ).toEqual('some value 22'); - expect( - createPrimaryObjectResult.data.createPrimaryObject.primaryObject.relationField.edges - .map(value => value.node.someField) - .sort() - ).toEqual(['some value 22', 'some value 44']); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.stringField - ).toEqual('some value'); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.arrayField - ).toEqual([ - { __typename: 'Element', value: 1 }, - { __typename: 'Element', value: 'abc' }, - { __typename: 'SecondaryObject', someField: 'some value 44' }, - ]); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.pointerField.someField - ).toEqual('some value 44'); - expect( - updatePrimaryObjectResult.data.updatePrimaryObject - .primaryObject.relationField.edges - ).toEqual([]); - } catch (e) { - handleError(e); - } + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject + .stringField + ).toEqual('some value'); + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject + .arrayField + ).toEqual([ + { __typename: 'Element', value: 1 }, + { __typename: 'Element', value: 'abc' }, + { __typename: 'SecondaryObject', someField: 'some value 44' }, + ]); + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject + .pointerField.someField + ).toEqual('some value 22'); + expect( + createPrimaryObjectResult.data.createPrimaryObject.primaryObject.relationField.edges + .map(value => value.node.someField) + .sort() + ).toEqual(['some value 22', 'some value 44']); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .stringField + ).toEqual('some value'); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .arrayField + ).toEqual([ + { __typename: 'Element', value: 1 }, + { __typename: 'Element', value: 'abc' }, + { __typename: 'SecondaryObject', someField: 'some value 44' }, + ]); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .pointerField.someField + ).toEqual('some value 44'); + expect( + updatePrimaryObjectResult.data.updatePrimaryObject.primaryObject + .relationField.edges + ).toEqual([]); + } catch (e) { + handleError(e); } - ); + }); }); }); diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 80eebaa733..0e2b970e99 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -16,14 +16,469 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + it('expect afterEvent create', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Create'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBe('bar'); + }); + + const query = new Parse.Query(TestObject); + const subscription = await query.subscribe(); + subscription.on('create', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent payload', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Update'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + done(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('expect afterEvent enter', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Enter'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('enter', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent leave', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Leave'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBeUndefined(); + expect(req.original.get('foo')).toBe('bar'); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('leave', object => { + expect(object.get('foo')).toBeUndefined(); + done(); + }); + + object.unset('foo'); + await object.save(); + }); + + it('expect afterEvent delete', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Delete'); + expect(req.user).toBeUndefined(); + req.object.set('foo', 'bar'); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + + const subscription = await query.subscribe(); + subscription.on('delete', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + await object.destroy(); + }); + + it('can handle afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.object; + current.set('foo', 'yolo'); + + const original = req.original; + original.set('yolo', 'foo'); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', (object, original) => { + expect(object.get('foo')).toBe('yolo'); + expect(original.get('yolo')).toBe('foo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can return different object in afterEvent', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const object = new Parse.Object('Yolo'); + req.object = object; + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.className).toBe('Yolo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle afterEvent throw', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.object; + const original = req.original; + + setTimeout(() => { + done(); + }, 2000); + + if (current.get('foo') != original.get('foo')) { + throw "Don't pass an update trigger, or message"; + } + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', () => { + fail('update should not have been called.'); + }); + subscription.on('error', () => { + fail('error should not have been called.'); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + it('expect afterEvent create', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Create'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBe('bar'); + }); + + const query = new Parse.Query(TestObject); + const subscription = await query.subscribe(); + subscription.on('create', object => { expect(object.get('foo')).toBe('bar'); done(); }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent payload', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Update'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + done(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + await query.subscribe(); object.set({ foo: 'bar' }); await object.save(); }); + it('expect afterEvent enter', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Enter'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBe('bar'); + expect(req.original.get('foo')).toBeUndefined(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('enter', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + object.set('foo', 'bar'); + await object.save(); + }); + + it('expect afterEvent leave', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Leave'); + expect(req.user).toBeUndefined(); + expect(req.object.get('foo')).toBeUndefined(); + expect(req.original.get('foo')).toBe('bar'); + }); + + const object = new TestObject(); + object.set('foo', 'bar'); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('foo', 'bar'); + const subscription = await query.subscribe(); + subscription.on('leave', object => { + expect(object.get('foo')).toBeUndefined(); + done(); + }); + + object.unset('foo'); + await object.save(); + }); + + it('expect afterEvent delete', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + expect(req.event).toBe('Delete'); + expect(req.user).toBeUndefined(); + req.object.set('foo', 'bar'); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + + const subscription = await query.subscribe(); + subscription.on('delete', object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + + await object.destroy(); + }); + + it('can handle afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const object = new TestObject(); + await object.save(); + + Parse.Cloud.afterLiveQueryEvent('TestObject', req => { + const current = req.object; + current.set('foo', 'yolo'); + + const original = req.original; + original.set('yolo', 'foo'); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + subscription.on('update', (object, original) => { + expect(object.get('foo')).toBe('yolo'); + expect(original.get('yolo')).toBe('foo'); + done(); + }); + object.set({ foo: 'bar' }); + await object.save(); + }); + + it('can handle async afterEvent modification', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const parent = new TestObject(); + const child = new TestObject(); + child.set('bar', 'foo'); + await Parse.Object.saveAll([parent, child]); + + Parse.Cloud.afterLiveQueryEvent('TestObject', async req => { + const current = req.object; + const pointer = current.get('child'); + await pointer.fetch(); + }); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', parent.id); + const subscription = await query.subscribe(); + subscription.on('update', object => { + expect(object.get('child')).toBeDefined(); + expect(object.get('child').get('bar')).toBe('foo'); + done(); + }); + parent.set('child', child); + await parent.save(); + }); + it('can handle beforeConnect / beforeSubscribe hooks', async done => { await reconfigureServer({ liveQuery: { @@ -56,7 +511,7 @@ describe('ParseLiveQuery', function () { const query = new Parse.Query(TestObject); query.equalTo('objectId', object.id); const subscription = await query.subscribe(); - subscription.on('update', async object => { + subscription.on('update', object => { expect(object.get('foo')).toBe('bar'); done(); }); diff --git a/src/Adapters/Auth/facebookaccountkit.js b/src/Adapters/Auth/facebookaccountkit.js deleted file mode 100644 index a650d23840..0000000000 --- a/src/Adapters/Auth/facebookaccountkit.js +++ /dev/null @@ -1,60 +0,0 @@ -const crypto = require('crypto'); -const httpsRequest = require('./httpsRequest'); -const Parse = require('parse/node').Parse; - -const graphRequest = path => { - return httpsRequest.get(`https://graph.accountkit.com/v1.1/${path}`); -}; - -function getRequestPath(authData, options) { - const access_token = authData.access_token, - appSecret = options && options.appSecret; - if (appSecret) { - const appsecret_proof = crypto - .createHmac('sha256', appSecret) - .update(access_token) - .digest('hex'); - return `me?access_token=${access_token}&appsecret_proof=${appsecret_proof}`; - } - return `me?access_token=${access_token}`; -} - -function validateAppId(appIds, authData, options) { - if (!appIds.length) { - return Promise.reject( - new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Facebook app id for Account Kit is not configured.' - ) - ); - } - return graphRequest(getRequestPath(authData, options)).then(data => { - if (data && data.application && appIds.indexOf(data.application.id) != -1) { - return; - } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Facebook app id for Account Kit is invalid for this user.' - ); - }); -} - -function validateAuthData(authData, options) { - return graphRequest(getRequestPath(authData, options)).then(data => { - if (data && data.error) { - throw data.error; - } - if (data && data.id == authData.id) { - return; - } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, - 'Facebook Account Kit auth is invalid for this user.' - ); - }); -} - -module.exports = { - validateAppId, - validateAuthData, -}; diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index e5e8c955bd..296013ce9a 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -4,7 +4,6 @@ const apple = require('./apple'); const gcenter = require('./gcenter'); const gpgames = require('./gpgames'); const facebook = require('./facebook'); -const facebookaccountkit = require('./facebookaccountkit'); const instagram = require('./instagram'); const linkedin = require('./linkedin'); const meetup = require('./meetup'); @@ -39,7 +38,6 @@ const providers = { gcenter, gpgames, facebook, - facebookaccountkit, instagram, linkedin, meetup, @@ -62,7 +60,7 @@ const providers = { }; function authDataValidator(adapter, appIds, options) { - return function(authData) { + return function (authData) { return adapter.validateAuthData(authData, options).then(() => { if (appIds) { return adapter.validateAppId(appIds, authData, options); @@ -117,13 +115,13 @@ function loadAuthAdapter(provider, authOptions) { return { adapter, appIds, providerOptions }; } -module.exports = function(authOptions = {}, enableAnonymousUsers = true) { +module.exports = function (authOptions = {}, enableAnonymousUsers = true) { let _enableAnonymousUsers = enableAnonymousUsers; - const setEnableAnonymousUsers = function(enable) { + const setEnableAnonymousUsers = function (enable) { _enableAnonymousUsers = enable; }; // To handle the test cases on configuration - const getValidatorForProvider = function(provider) { + const getValidatorForProvider = function (provider) { if (provider === 'anonymous' && !_enableAnonymousUsers) { return; } diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 5d28367961..e535480a71 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -14,6 +14,7 @@ import { runLiveQueryEventHandlers, maybeRunConnectTrigger, maybeRunSubscribeTrigger, + maybeRunAfterEventTrigger, } from '../triggers'; import { getAuthForSessionToken, Auth } from '../Auth'; import { getCacheController } from '../Controllers'; @@ -124,7 +125,7 @@ class ParseLiveQueryServer { _onAfterDelete(message: any): void { logger.verbose(Parse.applicationId + 'afterDelete is triggered'); - const deletedParseObject = message.currentParseObject.toJSON(); + let deletedParseObject = message.currentParseObject.toJSON(); const classLevelPermissions = message.classLevelPermissions; const className = deletedParseObject.className; logger.verbose( @@ -158,6 +159,7 @@ class ParseLiveQueryServer { const acl = message.currentParseObject.getACL(); // Check CLP const op = this._getCLPOperation(subscription.query); + let res = {}; this._matchesCLP( classLevelPermissions, message.currentParseObject, @@ -173,6 +175,22 @@ class ParseLiveQueryServer { if (!isMatched) { return null; } + res = { + event: 'Delete', + sessionToken: client.sessionToken, + object: deletedParseObject, + clients: this.clients.size, + subscriptions: this.subscriptions.size, + useMasterKey: client.hasMasterKey, + installationId: client.installationId + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then(() => { + if (res.object && typeof res.object.toJSON === 'function') { + deletedParseObject = res.object.toJSON(); + deletedParseObject.className = className; + } client.pushDelete(requestId, deletedParseObject); }) .catch(error => { @@ -193,7 +211,7 @@ class ParseLiveQueryServer { originalParseObject = message.originalParseObject.toJSON(); } const classLevelPermissions = message.classLevelPermissions; - const currentParseObject = message.currentParseObject.toJSON(); + let currentParseObject = message.currentParseObject.toJSON(); const className = currentParseObject.className; logger.verbose( 'ClassName: %s | ObjectId: %s', @@ -243,6 +261,7 @@ class ParseLiveQueryServer { // Set current ParseObject ACL checking promise, if the object does not match // subscription, we do not need to check ACL let currentACLCheckingPromise; + let res = {}; if (!isCurrentSubscriptionMatched) { currentACLCheckingPromise = Promise.resolve(false); } else { @@ -267,40 +286,67 @@ class ParseLiveQueryServer { currentACLCheckingPromise, ]); }) - .then( - ([isOriginalMatched, isCurrentMatched]) => { - logger.verbose( - 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', - originalParseObject, - currentParseObject, - isOriginalSubscriptionMatched, - isCurrentSubscriptionMatched, - isOriginalMatched, - isCurrentMatched, - subscription.hash - ); - - // Decide event type - let type; - if (isOriginalMatched && isCurrentMatched) { - type = 'Update'; - } else if (isOriginalMatched && !isCurrentMatched) { - type = 'Leave'; - } else if (!isOriginalMatched && isCurrentMatched) { - if (originalParseObject) { - type = 'Enter'; - } else { - type = 'Create'; - } + .then(([isOriginalMatched, isCurrentMatched]) => { + logger.verbose( + 'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', + originalParseObject, + currentParseObject, + isOriginalSubscriptionMatched, + isCurrentSubscriptionMatched, + isOriginalMatched, + isCurrentMatched, + subscription.hash + ); + + // Decide event type + let type; + if (isOriginalMatched && isCurrentMatched) { + type = 'Update'; + } else if (isOriginalMatched && !isCurrentMatched) { + type = 'Leave'; + } else if (!isOriginalMatched && isCurrentMatched) { + if (originalParseObject) { + type = 'Enter'; } else { - return null; + type = 'Create'; + } + } else { + return null; + } + message.event = type; + res = { + event: type, + sessionToken: client.sessionToken, + object: currentParseObject, + original: originalParseObject, + clients: this.clients.size, + subscriptions: this.subscriptions.size, + useMasterKey: client.hasMasterKey, + installationId: client.installationId + }; + return maybeRunAfterEventTrigger('afterEvent', className, res); + }) + .then( + () => { + if (res.object && typeof res.object.toJSON === 'function') { + currentParseObject = res.object.toJSON(); + currentParseObject.className = + res.object.className || className; + } + + if (res.original && typeof res.original.toJSON === 'function') { + originalParseObject = res.original.toJSON(); + originalParseObject.className = + res.original.className || className; + } + const functionName = 'push' + message.event; + if (client[functionName]) { + client[functionName]( + requestId, + currentParseObject, + originalParseObject + ); } - const functionName = 'push' + type; - client[functionName]( - requestId, - currentParseObject, - originalParseObject - ); }, error => { logger.error('Matching ACL error : ', error); diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 088c4dc3c1..c3f0536c2b 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -511,6 +511,32 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +/** + * Registers an after live query server event function. + * + * **Available in Cloud Code only.** + * + * ``` + * Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => { + * // code here + * }) + *``` + * + * @method afterLiveQueryEvent + * @name Parse.Cloud.afterLiveQueryEvent + * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after live query event function for. This can instead be a String that is the className of the subclass. + * @param {Function} func The function to run after a live query event. This function can be async and should take one parameter, a {@link Parse.Cloud.LiveQueryEventTrigger}. + */ +ParseCloud.afterLiveQueryEvent = function (parseClass, handler) { + const className = getClassName(parseClass); + triggers.addTrigger( + triggers.Types.afterEvent, + className, + handler, + Parse.applicationId + ); +}; + ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); }; @@ -563,6 +589,19 @@ module.exports = ParseCloud; * @property {String} sessionToken If set, the session of the user that made the request. */ +/** + * @interface Parse.Cloud.LiveQueryEventTrigger + * @property {String} installationId If set, the installationId triggering the request. + * @property {Boolean} useMasterKey If true, means the master key was used. + * @property {Parse.User} user If set, the user that made the request. + * @property {String} sessionToken If set, the session of the user that made the request. + * @property {String} event The live query event that triggered the request. + * @property {Parse.Object} object The object triggering the hook. + * @property {Parse.Object} original If set, the object, as currently stored. + * @property {Integer} clients The number of clients connected. + * @property {Integer} subscriptions The number of subscriptions connected. + */ + /** * @interface Parse.Cloud.BeforeFindRequest * @property {String} installationId If set, the installationId triggering the request. diff --git a/src/triggers.js b/src/triggers.js index 96dcb65e47..75c26ff039 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,6 +18,7 @@ export const Types = { afterDeleteFile: 'afterDeleteFile', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', + afterEvent: 'afterEvent', }; const FileClassName = '@File'; @@ -797,6 +798,25 @@ export async function maybeRunSubscribeTrigger( return trigger(request); } +export async function maybeRunAfterEventTrigger( + triggerType, + className, + request +) { + const trigger = getTrigger(className, triggerType, Parse.applicationId); + if (!trigger) { + return; + } + if (request.object) { + request.object = Parse.Object.fromJSON(request.object); + } + if (request.original) { + request.original = Parse.Object.fromJSON(request.original); + } + request.user = await userForSessionToken(request.sessionToken); + return trigger(request); +} + async function userForSessionToken(sessionToken) { if (!sessionToken) { return;