From 70d7bafc5c9ede40b8f8c79f52b54098af608925 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 17:26:08 +0100 Subject: [PATCH 1/7] feat: create, update, delete many graphql --- package-lock.json | 181 +++---- spec/ParseGraphQLController.spec.js | 74 +++ spec/ParseGraphQLSchema.spec.js | 11 +- spec/ParseGraphQLServer.spec.js | 583 +++++++++++++++++++++ src/Controllers/ParseGraphQLController.js | 30 ++ src/GraphQL/loaders/defaultGraphQLTypes.js | 18 + src/GraphQL/loaders/parseClassMutations.js | 498 +++++++++++++++++- src/GraphQL/loaders/parseClassTypes.js | 15 + src/batch.js | 5 +- src/batchRequestLimit.js | 19 + 10 files changed, 1318 insertions(+), 116 deletions(-) create mode 100644 src/batchRequestLimit.js diff --git a/package-lock.json b/package-lock.json index 18967f7a13..44f1e743f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -162,6 +162,7 @@ "integrity": "sha512-YM9lQpm0VfVco4DSyKooHS/fDTiKQcCHfxr7i3iL6a0kP/jNO5+4NFK6vtRDxaYisd5BrwOZHLJpPBnvRVpKPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -228,6 +229,7 @@ "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", "license": "MIT", + "peer": true, "dependencies": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^2.0.0", @@ -506,6 +508,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3788,6 +3791,7 @@ "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -4306,6 +4310,7 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", "license": "MIT", + "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -4385,6 +4390,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5072,6 +5078,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", "dev": true, + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -5191,6 +5198,7 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.12.tgz", "integrity": "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==", "dev": true, + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -7568,6 +7576,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7965,6 +7974,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -8026,6 +8036,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", "dev": true, + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -8158,6 +8169,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", @@ -8250,6 +8262,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -9041,6 +9054,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9421,8 +9435,7 @@ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/backoff": { "version": "2.5.0", @@ -9601,6 +9614,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -11736,6 +11750,7 @@ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -12114,8 +12129,7 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/execa": { "version": "5.1.1", @@ -12166,6 +12180,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -12209,6 +12224,7 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", "license": "MIT", + "peer": true, "dependencies": { "ip-address": "10.1.0" }, @@ -13123,7 +13139,6 @@ "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", @@ -13139,7 +13154,6 @@ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -13150,7 +13164,6 @@ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "optional": true, - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -13168,7 +13181,6 @@ "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -13186,7 +13198,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "optional": true, - "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -13208,7 +13219,6 @@ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "optional": true, - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -13225,7 +13235,6 @@ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", "optional": true, - "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -13236,7 +13245,6 @@ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -13256,7 +13264,6 @@ "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "license": "ISC", "optional": true, - "peer": true, "dependencies": { "glob": "^10.3.7" }, @@ -13273,7 +13280,6 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", "optional": true, - "peer": true, "engines": { "node": ">=14" }, @@ -13695,7 +13701,6 @@ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "optional": true, - "peer": true, "engines": { "node": ">=14" } @@ -13748,6 +13753,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -14729,8 +14735,7 @@ "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/jackspeak": { "version": "3.4.3", @@ -14907,6 +14912,7 @@ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, + "peer": true, "dependencies": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", @@ -16061,6 +16067,7 @@ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -16088,6 +16095,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -16570,7 +16578,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "debug": "4" }, @@ -16641,7 +16648,6 @@ "dev": true, "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -16652,22 +16658,6 @@ "node": ">=12" } }, - "node_modules/mongodb-runner/node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/mongodb-runner/node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -16675,7 +16665,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -16737,7 +16726,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -16759,8 +16747,7 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/mongodb-runner/node_modules/webidl-conversions": { "version": "3.0.1", @@ -16768,8 +16755,7 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, "license": "BSD-2-Clause", - "optional": true, - "peer": true + "optional": true }, "node_modules/mongodb-runner/node_modules/whatwg-url": { "version": "5.0.0", @@ -16778,7 +16764,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -20470,6 +20455,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -20510,7 +20496,6 @@ "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.17.0.tgz", "integrity": "sha512-2Uio3Xfl5ldwJfls+RgGL+YbPcKQncWACWjYQFqlamvHZ4HJFjZhhZBbqd7jQ2LIkZYSvU90bm2dNW0rno+QFQ==", "license": "MIT", - "peer": true, "peerDependencies": { "pg": "^8" } @@ -20792,6 +20777,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -21799,6 +21785,7 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -23987,6 +23974,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -25091,7 +25079,6 @@ "deprecated": "The `subscriptions-transport-ws` package is no longer maintained. We recommend you use `graphql-ws` instead. For help migrating Apollo software to `graphql-ws`, see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws For general help using `graphql-ws`, see https://github.com/enisdenjo/graphql-ws/blob/master/README.md", "dev": true, "optional": true, - "peer": true, "dependencies": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -25109,7 +25096,6 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true, "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -25120,7 +25106,6 @@ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, "optional": true, - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -25601,6 +25586,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -25855,6 +25841,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26932,6 +26919,7 @@ "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", "integrity": "sha512-YM9lQpm0VfVco4DSyKooHS/fDTiKQcCHfxr7i3iL6a0kP/jNO5+4NFK6vtRDxaYisd5BrwOZHLJpPBnvRVpKPg==", "dev": true, + "peer": true, "requires": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -26971,6 +26959,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", + "peer": true, "requires": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^2.0.0", @@ -27156,6 +27145,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -29359,6 +29349,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.2.tgz", "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", "dev": true, + "peer": true, "requires": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -29784,6 +29775,7 @@ "version": "5.11.0", "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", + "peer": true, "requires": { "cluster-key-slot": "1.1.2" } @@ -29831,6 +29823,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, + "peer": true, "requires": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -30321,7 +30314,8 @@ "version": "9.1.6", "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", - "dev": true + "dev": true, + "peer": true }, "marked-terminal": { "version": "6.2.0", @@ -30390,6 +30384,7 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.12.tgz", "integrity": "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==", "dev": true, + "peer": true, "requires": { "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -31944,7 +31939,8 @@ "picomatch": { "version": "4.0.3", "bundled": true, - "dev": true + "dev": true, + "peer": true } } }, @@ -32201,6 +32197,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "peer": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -32260,6 +32257,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", "dev": true, + "peer": true, "requires": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -32388,6 +32386,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, + "peer": true, "requires": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", @@ -32441,6 +32440,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -32952,7 +32952,8 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -33235,8 +33236,7 @@ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "backoff": { "version": "2.5.0", @@ -33361,6 +33361,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -34860,6 +34861,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -35113,8 +35115,7 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "execa": { "version": "5.1.1", @@ -35154,6 +35155,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "peer": true, "requires": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -35204,6 +35206,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", + "peer": true, "requires": { "ip-address": "10.1.0" } @@ -35806,7 +35809,6 @@ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", "optional": true, - "peer": true, "requires": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", @@ -35818,7 +35820,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "optional": true, - "peer": true, "requires": { "balanced-match": "^1.0.0" } @@ -35828,7 +35829,6 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "optional": true, - "peer": true, "requires": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -35839,7 +35839,6 @@ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "optional": true, - "peer": true, "requires": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -35852,7 +35851,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "optional": true, - "peer": true, "requires": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -35867,7 +35865,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "optional": true, - "peer": true, "requires": { "brace-expansion": "^2.0.1" } @@ -35876,15 +35873,13 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "optional": true, - "peer": true + "optional": true }, "node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "optional": true, - "peer": true, "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -35896,7 +35891,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "optional": true, - "peer": true, "requires": { "glob": "^10.3.7" } @@ -35905,8 +35899,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "optional": true, - "peer": true + "optional": true } } }, @@ -36216,8 +36209,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "optional": true, - "peer": true + "optional": true }, "gopd": { "version": "1.2.0", @@ -36252,7 +36244,8 @@ "graphql": { "version": "16.13.2", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", - "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==" + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "peer": true }, "graphql-list-fields": { "version": "2.0.4", @@ -36931,8 +36924,7 @@ "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "jackspeak": { "version": "3.4.3", @@ -37065,6 +37057,7 @@ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, + "peer": true, "requires": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", @@ -37927,6 +37920,7 @@ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "peer": true, "requires": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -37947,7 +37941,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true + "dev": true, + "peer": true }, "marked-terminal": { "version": "7.3.0", @@ -38267,7 +38262,6 @@ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, - "peer": true, "requires": { "debug": "4" } @@ -38314,12 +38308,10 @@ "dev": true }, "gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "version": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", "dev": true, "optional": true, - "peer": true, "requires": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -38327,25 +38319,12 @@ "node-fetch": "^2.6.9" } }, - "gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "optional": true, - "peer": true, "requires": { "agent-base": "6", "debug": "4" @@ -38368,7 +38347,6 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "optional": true, - "peer": true, "requires": { "whatwg-url": "^5.0.0" } @@ -38378,16 +38356,14 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "whatwg-url": { "version": "5.0.0", @@ -38395,7 +38371,6 @@ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "optional": true, - "peer": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -40899,6 +40874,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", + "peer": true, "requires": { "pg-cloudflare": "^1.3.0", "pg-connection-string": "^2.11.0", @@ -40923,7 +40899,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.17.0.tgz", "integrity": "sha512-2Uio3Xfl5ldwJfls+RgGL+YbPcKQncWACWjYQFqlamvHZ4HJFjZhhZBbqd7jQ2LIkZYSvU90bm2dNW0rno+QFQ==", - "peer": true, "requires": {} }, "pg-int8": { @@ -41109,6 +41084,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -41819,6 +41795,7 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.3.tgz", "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, + "peer": true, "requires": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -43227,7 +43204,8 @@ "picomatch": { "version": "4.0.3", "bundled": true, - "dev": true + "dev": true, + "peer": true } } }, @@ -44002,7 +43980,6 @@ "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", "dev": true, "optional": true, - "peer": true, "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -44016,8 +43993,7 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "ws": { "version": "7.5.9", @@ -44025,7 +44001,6 @@ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, "optional": true, - "peer": true, "requires": {} } } @@ -44351,7 +44326,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true + "dev": true, + "peer": true } } }, @@ -44531,7 +44507,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true + "dev": true, + "peer": true }, "typescript-eslint": { "version": "8.53.1", diff --git a/spec/ParseGraphQLController.spec.js b/spec/ParseGraphQLController.spec.js index 15bbb48ab7..54784d14ac 100644 --- a/spec/ParseGraphQLController.spec.js +++ b/spec/ParseGraphQLController.spec.js @@ -872,6 +872,32 @@ describe('ParseGraphQLController', () => { ], }) ).toBeResolvedTo(successfulUpdateResponse); + expectAsync( + parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className: '_User', + mutation: { + createMany: 1, + }, + }, + ], + }) + ).toBeRejected(); + expectAsync( + parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className: '_User', + mutation: { + createMany: true, + updateMany: true, + deleteMany: false, + }, + }, + ], + }) + ).toBeResolvedTo(successfulUpdateResponse); }); it('should throw if _User create fields is missing username or password', async () => { @@ -1043,6 +1069,54 @@ describe('ParseGraphQLController', () => { ).toBeRejected( `Invalid graphQLConfig: classConfig:${className} is invalid because "mutation.destroyAlias" must be a string` ); + + expectAsync( + parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className, + mutation: { + createMany: true, + createManyAlias: true, + }, + }, + ], + }) + ).toBeRejected( + `Invalid graphQLConfig: classConfig:${className} is invalid because "mutation.createManyAlias" must be a string` + ); + + expectAsync( + parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className, + mutation: { + updateMany: true, + updateManyAlias: 1, + }, + }, + ], + }) + ).toBeRejected( + `Invalid graphQLConfig: classConfig:${className} is invalid because "mutation.updateManyAlias" must be a string` + ); + + expectAsync( + parseGraphQLController.updateGraphQLConfig({ + classConfigs: [ + { + className, + mutation: { + deleteMany: true, + deleteManyAlias: { not: 'valid' }, + }, + }, + ], + }) + ).toBeRejected( + `Invalid graphQLConfig: classConfig:${className} is invalid because "mutation.deleteManyAlias" must be a string` + ); }); }); }); diff --git a/spec/ParseGraphQLSchema.spec.js b/spec/ParseGraphQLSchema.spec.js index 0b3d9a9007..1ac3362d28 100644 --- a/spec/ParseGraphQLSchema.spec.js +++ b/spec/ParseGraphQLSchema.spec.js @@ -495,7 +495,16 @@ describe('ParseGraphQLSchema', () => { expect(Object.keys(queries1).sort()).toEqual(Object.keys(queries2).sort()); expect(mutations1).not.toBe(mutations2); expect( - Object.keys(mutations1).concat('createCars', 'updateCars', 'deleteCars').sort() + Object.keys(mutations1) + .concat( + 'createCars', + 'updateCars', + 'deleteCars', + 'createManyCars', + 'updateManyCars', + 'deleteManyCars' + ) + .sort() ).toEqual(Object.keys(mutations2).sort()); }); }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 976e58fcda..551877a620 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -11788,6 +11788,588 @@ describe('ParseGraphQLServer', () => { expect(getResult.data.get.objectId).toEqual(product.id); }); + + describe('Bulk class mutations', () => { + beforeEach(async () => { + const sc = new Parse.Schema('BulkTest'); + await sc.purge().catch(() => {}); + await sc.delete().catch(() => {}); + await sc.addString('title').save(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + }); + + async function reconfigureGraphQLWithBatchLimit2AndOpenClient() { + parseServer = await global.reconfigureServer({ + requestComplexity: { batchRequestLimit: 2 }, + }); + await createGQLFromParseServer(parseServer); + const httpLink = await createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, + }, + }); + const sc = new Parse.Schema('BulkTest'); + await sc.purge().catch(() => {}); + await sc.delete().catch(() => {}); + await sc.addString('title').save(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await updateCLP( + { + create: { '*': true }, + find: { '*': true }, + get: { '*': true }, + update: { '*': true }, + delete: { '*': true }, + }, + 'BulkTest' + ); + } + + const clientKeyHeaders = { + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test', + }, + }; + + it('should reject createMany when item count exceeds batchRequestLimit', async () => { + try { + await reconfigureGraphQLWithBatchLimit2AndOpenClient(); + const result = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyOverLimit($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + clientMutationId + } + } + `, + variables: { + input: { + clientMutationId: uuidv4(), + fields: [{ title: 'a' }, { title: 'b' }, { title: 'c' }], + }, + }, + context: clientKeyHeaders, + errorPolicy: 'all', + }); + expect(result.errors).toBeDefined(); + expect(result.errors[0].message).toContain('exceeds the limit of 2'); + } catch (e) { + handleError(e); + } + }); + + it('should reject updateMany when item count exceeds batchRequestLimit', async () => { + try { + await reconfigureGraphQLWithBatchLimit2AndOpenClient(); + const objs = []; + for (let i = 0; i < 3; i++) { + const o = new Parse.Object('BulkTest'); + o.set('title', `t${i}`); + await o.save(null, { useMasterKey: true }); + objs.push(o); + } + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const result = await apolloClient.mutate({ + mutation: gql` + mutation UpdateManyOverLimit($input: UpdateManyBulkTestInput!) { + updateManyBulkTest(input: $input) { + clientMutationId + } + } + `, + variables: { + input: { + clientMutationId: uuidv4(), + updates: objs.map((o, i) => ({ + id: toGlobalId('BulkTest', o.id), + fields: { title: `u${i}` }, + })), + }, + }, + context: clientKeyHeaders, + errorPolicy: 'all', + }); + expect(result.errors).toBeDefined(); + expect(result.errors[0].message).toContain('exceeds the limit of 2'); + } catch (e) { + handleError(e); + } + }); + + it('should reject deleteMany when item count exceeds batchRequestLimit', async () => { + try { + await reconfigureGraphQLWithBatchLimit2AndOpenClient(); + const objs = []; + for (let i = 0; i < 3; i++) { + const o = new Parse.Object('BulkTest'); + o.set('title', `d${i}`); + await o.save(null, { useMasterKey: true }); + objs.push(o); + } + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const result = await apolloClient.mutate({ + mutation: gql` + mutation DeleteManyOverLimit($input: DeleteManyBulkTestInput!) { + deleteManyBulkTest(input: $input) { + clientMutationId + } + } + `, + variables: { + input: { + clientMutationId: uuidv4(), + ids: objs.map(o => toGlobalId('BulkTest', o.id)), + }, + }, + context: clientKeyHeaders, + errorPolicy: 'all', + }); + expect(result.errors).toBeDefined(); + expect(result.errors[0].message).toContain('exceeds the limit of 2'); + } catch (e) { + handleError(e); + } + }); + + it('should allow createMany at exactly batchRequestLimit with client key', async () => { + try { + await reconfigureGraphQLWithBatchLimit2AndOpenClient(); + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyAtLimit($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + clientMutationId + results { + success + bulkTest { + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + fields: [{ title: 'one' }, { title: 'two' }], + }, + }, + context: clientKeyHeaders, + }); + expect(data.createManyBulkTest.results.length).toBe(2); + expect(data.createManyBulkTest.results.every(r => r.success)).toBe(true); + } catch (e) { + handleError(e); + } + }); + + it('should bypass batchRequestLimit for master key on createMany', async () => { + try { + await reconfigureGraphQLWithBatchLimit2AndOpenClient(); + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyMasterBypass($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + clientMutationId + results { + success + bulkTest { + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + fields: [{ title: 'm1' }, { title: 'm2' }, { title: 'm3' }], + }, + }, + context: { + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', + }, + }, + }); + expect(data.createManyBulkTest.results.length).toBe(3); + expect(data.createManyBulkTest.results.every(r => r.success)).toBe(true); + } catch (e) { + handleError(e); + } + }); + + it('should createMany with ordered successes', async () => { + try { + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyBulk($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + id + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + fields: [{ title: 'first' }, { title: 'second' }], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + expect(data.createManyBulkTest.results.length).toBe(2); + expect(data.createManyBulkTest.results[0].success).toBe(true); + expect(data.createManyBulkTest.results[0].bulkTest.title).toBe('first'); + expect(data.createManyBulkTest.results[1].success).toBe(true); + expect(data.createManyBulkTest.results[1].bulkTest.title).toBe('second'); + } catch (e) { + handleError(e); + } + }); + + it('should updateMany with mixed success and failure', async () => { + try { + const a = new Parse.Object('BulkTest'); + a.set('title', 'a'); + const b = new Parse.Object('BulkTest'); + b.set('title', 'b'); + await Parse.Object.saveAll([a, b]); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation UpdateManyBulk($input: UpdateManyBulkTestInput!) { + updateManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + updates: [ + { id: toGlobalId('BulkTest', a.id), fields: { title: 'a2' } }, + { id: 'nonexistentid000', fields: { title: 'x' } }, + ], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + expect(data.updateManyBulkTest.results.length).toBe(2); + expect(data.updateManyBulkTest.results[0].success).toBe(true); + expect(data.updateManyBulkTest.results[0].bulkTest.title).toBe('a2'); + expect(data.updateManyBulkTest.results[1].success).toBe(false); + expect(data.updateManyBulkTest.results[1].error).toBeDefined(); + expect(data.updateManyBulkTest.results[1].bulkTest).toBeNull(); + } catch (e) { + handleError(e); + } + }); + + it('should deleteMany', async () => { + try { + const a = new Parse.Object('BulkTest'); + a.set('title', 'del1'); + const b = new Parse.Object('BulkTest'); + b.set('title', 'del2'); + await Parse.Object.saveAll([a, b]); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation DeleteManyBulk($input: DeleteManyBulkTestInput!) { + deleteManyBulkTest(input: $input) { + clientMutationId + results { + success + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + ids: [toGlobalId('BulkTest', a.id), toGlobalId('BulkTest', b.id)], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + expect(data.deleteManyBulkTest.results.length).toBe(2); + expect(data.deleteManyBulkTest.results[0].success).toBe(true); + expect(data.deleteManyBulkTest.results[1].success).toBe(true); + } catch (e) { + handleError(e); + } + }); + + it('should createMany with partial failure when beforeSave rejects', async () => { + try { + Parse.Cloud.beforeSave('BulkTest', request => { + if (request.object.get('title') === 'FAIL') { + throw new Parse.Error( + Parse.Error.SCRIPT_FAILED, + 'beforeSave blocked this title' + ); + } + }); + + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyBulkPartial($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + fields: [{ title: 'ok' }, { title: 'FAIL' }], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + + const results = data.createManyBulkTest.results; + expect(results.length).toBe(2); + expect(results[0].success).toBe(true); + expect(results[0].bulkTest.title).toBe('ok'); + expect(results[0].error).toBeNull(); + expect(results[1].success).toBe(false); + expect(results[1].bulkTest).toBeNull(); + expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(results[1].error.message).toContain('beforeSave blocked'); + + const q = new Parse.Query('BulkTest'); + q.equalTo('title', 'ok'); + const saved = await q.find({ useMasterKey: true }); + expect(saved.length).toBe(1); + } catch (e) { + handleError(e); + } + }); + + it('should updateMany with partial failure when beforeSave rejects', async () => { + try { + const a = new Parse.Object('BulkTest'); + a.set('title', 'a'); + const b = new Parse.Object('BulkTest'); + b.set('title', 'b'); + await Parse.Object.saveAll([a, b]); + + Parse.Cloud.beforeSave('BulkTest', request => { + if (request.object.get('title') === 'BLOCKED') { + throw new Parse.Error( + Parse.Error.SCRIPT_FAILED, + 'beforeSave blocked update to BLOCKED' + ); + } + }); + + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation UpdateManyBulkPartial($input: UpdateManyBulkTestInput!) { + updateManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + updates: [ + { id: toGlobalId('BulkTest', a.id), fields: { title: 'a2' } }, + { id: toGlobalId('BulkTest', b.id), fields: { title: 'BLOCKED' } }, + ], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + + const results = data.updateManyBulkTest.results; + expect(results.length).toBe(2); + expect(results[0].success).toBe(true); + expect(results[0].bulkTest.title).toBe('a2'); + expect(results[0].error).toBeNull(); + expect(results[1].success).toBe(false); + expect(results[1].bulkTest).toBeNull(); + expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(results[1].error.message).toContain('beforeSave blocked'); + + await a.fetch({ useMasterKey: true }); + await b.fetch({ useMasterKey: true }); + expect(a.get('title')).toBe('a2'); + expect(b.get('title')).toBe('b'); + } catch (e) { + handleError(e); + } + }); + + it('should deleteMany with partial failure when beforeDelete rejects', async () => { + try { + const a = new Parse.Object('BulkTest'); + a.set('title', 'deletable'); + const b = new Parse.Object('BulkTest'); + b.set('title', 'nodelete'); + await Parse.Object.saveAll([a, b]); + + Parse.Cloud.beforeDelete('BulkTest', request => { + if (request.object.get('title') === 'nodelete') { + throw new Parse.Error( + Parse.Error.SCRIPT_FAILED, + 'beforeDelete blocked delete for nodelete' + ); + } + }); + + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation DeleteManyBulkPartial($input: DeleteManyBulkTestInput!) { + deleteManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + ids: [toGlobalId('BulkTest', a.id), toGlobalId('BulkTest', b.id)], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + + const results = data.deleteManyBulkTest.results; + expect(results.length).toBe(2); + expect(results[0].success).toBe(true); + expect(results[0].bulkTest.title).toBe('deletable'); + expect(results[0].error).toBeNull(); + expect(results[1].success).toBe(false); + expect(results[1].bulkTest).toBeNull(); + expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(results[1].error.message).toContain('beforeDelete blocked'); + + const q = new Parse.Query('BulkTest'); + const remaining = await q.find({ useMasterKey: true }); + expect(remaining.length).toBe(1); + expect(remaining[0].id).toBe(b.id); + expect(remaining[0].get('title')).toBe('nodelete'); + } catch (e) { + handleError(e); + } + }); + }); }); }); }); @@ -12217,6 +12799,7 @@ describe('ParseGraphQLServer', () => { expect(result3.data.updateSomeClass.someClass.type).toEqual('human'); }); }); + describe('Async Function Based Merge', () => { let httpServer; const headers = { diff --git a/src/Controllers/ParseGraphQLController.js b/src/Controllers/ParseGraphQLController.js index 4445f4763c..f48845fa0a 100644 --- a/src/Controllers/ParseGraphQLController.js +++ b/src/Controllers/ParseGraphQLController.js @@ -258,9 +258,15 @@ class ParseGraphQLController { create = null, update = null, destroy = null, + createMany = null, + updateMany = null, + deleteMany = null, createAlias = null, updateAlias = null, destroyAlias = null, + createManyAlias = null, + updateManyAlias = null, + deleteManyAlias = null, ...invalidKeys } = mutation; if (Object.keys(invalidKeys).length) { @@ -275,6 +281,15 @@ class ParseGraphQLController { if (destroy !== null && typeof destroy !== 'boolean') { return `"mutation.destroy" must be a boolean`; } + if (createMany !== null && typeof createMany !== 'boolean') { + return `"mutation.createMany" must be a boolean`; + } + if (updateMany !== null && typeof updateMany !== 'boolean') { + return `"mutation.updateMany" must be a boolean`; + } + if (deleteMany !== null && typeof deleteMany !== 'boolean') { + return `"mutation.deleteMany" must be a boolean`; + } if (createAlias !== null && typeof createAlias !== 'string') { return `"mutation.createAlias" must be a string`; } @@ -284,6 +299,15 @@ class ParseGraphQLController { if (destroyAlias !== null && typeof destroyAlias !== 'string') { return `"mutation.destroyAlias" must be a string`; } + if (createManyAlias !== null && typeof createManyAlias !== 'string') { + return `"mutation.createManyAlias" must be a string`; + } + if (updateManyAlias !== null && typeof updateManyAlias !== 'string') { + return `"mutation.updateManyAlias" must be a string`; + } + if (deleteManyAlias !== null && typeof deleteManyAlias !== 'string') { + return `"mutation.deleteManyAlias" must be a string`; + } } else { return `"mutation" must be a valid object`; } @@ -352,9 +376,15 @@ export interface ParseGraphQLClassConfig { update: ?boolean, // delete is a reserved key word in js destroy: ?boolean, + createMany: ?boolean, + updateMany: ?boolean, + deleteMany: ?boolean, createAlias: ?String, updateAlias: ?String, destroyAlias: ?String, + createManyAlias: ?String, + updateManyAlias: ?String, + deleteManyAlias: ?String, }; } diff --git a/src/GraphQL/loaders/defaultGraphQLTypes.js b/src/GraphQL/loaders/defaultGraphQLTypes.js index d24047329c..9f1f675c4e 100644 --- a/src/GraphQL/loaders/defaultGraphQLTypes.js +++ b/src/GraphQL/loaders/defaultGraphQLTypes.js @@ -1177,6 +1177,22 @@ const POLYGON_WHERE_INPUT = new GraphQLInputObjectType({ }, }); +const PARSE_GRAPHQL_BULK_ERROR = new GraphQLObjectType({ + name: 'ParseGraphQLBulkError', + description: + 'Error for a single entry in a bulk GraphQL mutation (createMany, updateMany, deleteMany).', + fields: { + code: { + description: 'Parse error code.', + type: new GraphQLNonNull(GraphQLInt), + }, + message: { + description: 'Error message.', + type: new GraphQLNonNull(GraphQLString), + }, + }, +}); + const ELEMENT = new GraphQLObjectType({ name: 'Element', description: "The Element object type is used to return array items' value.", @@ -1263,6 +1279,7 @@ const load = parseGraphQLSchema => { parseGraphQLSchema.addGraphQLType(PUBLIC_ACL, true); parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true); parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true); + parseGraphQLSchema.addGraphQLType(PARSE_GRAPHQL_BULK_ERROR, true, true); }; export { @@ -1321,6 +1338,7 @@ export { CENTER_SPHERE_INPUT, GEO_WITHIN_INPUT, GEO_INTERSECTS_INPUT, + PARSE_GRAPHQL_BULK_ERROR, equalTo, notEqualTo, lessThan, diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index df9a096995..59581f9bbc 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -1,4 +1,5 @@ -import { GraphQLNonNull } from 'graphql'; +import Parse from 'parse/node'; +import { GraphQLNonNull, GraphQLList, GraphQLBoolean, GraphQLObjectType } from 'graphql'; import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay'; import getFieldNames from 'graphql-list-fields'; @@ -9,6 +10,46 @@ import * as objectsQueries from '../helpers/objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; import { transformClassNameToGraphQL } from '../transformers/className'; import { transformTypes } from '../transformers/mutation'; +import { createSanitizedError } from '../../Error'; +import { getBatchRequestLimit, isBatchRequestLimitExceeded } from '../../batchRequestLimit'; + +const bulkErrorFromReason = reason => { + if (reason instanceof Parse.Error) { + return { code: reason.code, message: reason.message }; + } + const message = + reason && typeof reason.message === 'string' ? reason.message : 'Internal server error'; + return { code: Parse.Error.INTERNAL_SERVER_ERROR, message }; +}; + +const normalizeObjectIdForClass = (id, className) => { + try { + const globalIdObject = fromGlobalId(id); + if (globalIdObject.type === className) { + return globalIdObject.id; + } + } catch { + // `id` is not a Relay global id; use as Parse objectId + } + return id; +}; + +const assertBulkInputLength = (count, config, auth) => { + if (count === 0) { + throw createSanitizedError( + Parse.Error.INVALID_JSON, + 'bulk input must contain at least one item', + config + ); + } + if (isBatchRequestLimitExceeded(count, config, auth)) { + const batchRequestLimit = getBatchRequestLimit(config); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + `bulk input contains ${count} items, which exceeds the limit of ${batchRequestLimit}.` + ); + } +}; const filterDeletedFields = fields => Object.keys(fields).reduce((acc, key) => { @@ -50,9 +91,26 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG destroyAlias: destroyAlias = '', } = getParseClassMutationConfig(parseClassConfig); + const createManyExplicit = parseClassConfig?.mutation?.createMany; + const updateManyExplicit = parseClassConfig?.mutation?.updateMany; + const deleteManyExplicit = parseClassConfig?.mutation?.deleteMany; + const isCreateManyEnabled = createManyExplicit !== undefined ? createManyExplicit : isCreateEnabled; + const isUpdateManyEnabled = updateManyExplicit !== undefined ? updateManyExplicit : isUpdateEnabled; + const isDeleteManyEnabled = deleteManyExplicit !== undefined ? deleteManyExplicit : isDestroyEnabled; + const allowCreateMany = + isCreateManyEnabled && (isCreateEnabled || createManyExplicit === true); + const allowUpdateMany = + isUpdateManyEnabled && (isUpdateEnabled || updateManyExplicit === true); + const allowDeleteMany = + isDeleteManyEnabled && (isDestroyEnabled || deleteManyExplicit === true); + const createManyAliasCfg = parseClassConfig?.mutation?.createManyAlias || ''; + const updateManyAliasCfg = parseClassConfig?.mutation?.updateManyAlias || ''; + const deleteManyAliasCfg = parseClassConfig?.mutation?.deleteManyAlias || ''; + const { classGraphQLCreateType, classGraphQLUpdateType, + classGraphQLUpdateManyItemType, classGraphQLOutputType, } = parseGraphQLSchema.parseClassTypes[className]; @@ -182,11 +240,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG if (!fields) { fields = {}; } const { config, auth, info } = context; - const globalIdObject = fromGlobalId(id); - - if (globalIdObject.type === className) { - id = globalIdObject.id; - } + id = normalizeObjectIdForClass(id, className); const parseFields = await transformTypes('update', fields, { className, @@ -287,11 +341,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG let { id } = cloneArgs(args); const { config, auth, info } = context; - const globalIdObject = fromGlobalId(id); - - if (globalIdObject.type === className) { - id = globalIdObject.id; - } + id = normalizeObjectIdForClass(id, className); const selectedFields = getFieldNames(mutationInfo) .filter(field => field.startsWith(`${getGraphQLQueryName}.`)) @@ -332,6 +382,432 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG parseGraphQLSchema.addGraphQLMutation(deleteGraphQLMutationName, deleteGraphQLMutation); } } + + if (allowCreateMany) { + const createManyGraphQLMutationName = createManyAliasCfg || `createMany${graphQLClassName}`; + const createManyResultsPrefix = `results.${getGraphQLQueryName}.`; + const createManyResultItemType = new GraphQLObjectType({ + name: `CreateMany${graphQLClassName}ResultItem`, + description: `One result entry for ${createManyGraphQLMutationName}.`, + fields: () => ({ + success: { + description: 'Whether this object was created successfully.', + type: new GraphQLNonNull(GraphQLBoolean), + }, + [getGraphQLQueryName]: { + description: `The created object when success is true.`, + type: classGraphQLOutputType || defaultGraphQLTypes.OBJECT, + }, + error: { + description: 'Present when success is false.', + type: defaultGraphQLTypes.PARSE_GRAPHQL_BULK_ERROR, + }, + }), + }); + const createManyGraphQLMutation = mutationWithClientMutationId({ + name: `CreateMany${graphQLClassName}`, + description: `The ${createManyGraphQLMutationName} mutation creates multiple objects of the ${graphQLClassName} class. Each entry succeeds or fails independently.`, + inputFields: { + fields: { + description: 'List of field sets; one object will be created per element.', + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(classGraphQLCreateType || defaultGraphQLTypes.OBJECT)) + ), + }, + }, + outputFields: { + results: { + description: 'Creation results in the same order as the input list.', + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(createManyResultItemType)) + ), + }, + }, + mutateAndGetPayload: async (args, context, mutationInfo) => { + try { + let { fields: fieldsList } = cloneArgs(args); + if (!fieldsList) { + fieldsList = []; + } + const { config, auth, info } = context; + assertBulkInputLength(fieldsList.length, config, auth); + + const settled = await Promise.allSettled( + fieldsList.map(fieldSet => + (async () => { + let fields = fieldSet ? cloneArgs({ fields: fieldSet }).fields : {}; + if (!fields) { + fields = {}; + } + const parseFields = await transformTypes('create', fields, { + className, + parseGraphQLSchema, + originalFields: fieldSet, + req: { config, auth, info }, + }); + const createdObject = await objectsMutations.createObject( + className, + parseFields, + config, + auth, + info + ); + const selectedFields = getFieldNames(mutationInfo) + .filter(field => field.startsWith(createManyResultsPrefix)) + .map(field => field.replace(createManyResultsPrefix, '')); + const { keys, include } = extractKeysAndInclude(selectedFields); + const { keys: requiredKeys, needGet } = getOnlyRequiredFields( + fields, + keys, + include, + ['id', 'objectId', 'createdAt', 'updatedAt'] + ); + const needToGetAllKeys = objectsQueries.needToGetAllKeys( + parseClass.fields, + keys, + parseGraphQLSchema.parseClasses + ); + let optimizedObject = {}; + if (needGet && !needToGetAllKeys) { + optimizedObject = await objectsQueries.getObject( + className, + createdObject.objectId, + requiredKeys, + include, + undefined, + undefined, + config, + auth, + info, + parseGraphQLSchema.parseClasses + ); + } else if (needToGetAllKeys) { + optimizedObject = await objectsQueries.getObject( + className, + createdObject.objectId, + undefined, + include, + undefined, + undefined, + config, + auth, + info, + parseGraphQLSchema.parseClasses + ); + } + return { + ...createdObject, + updatedAt: createdObject.createdAt, + ...filterDeletedFields(parseFields), + ...optimizedObject, + }; + })() + ) + ); + + const results = settled.map(r => { + if (r.status === 'fulfilled') { + return { + success: true, + [getGraphQLQueryName]: r.value, + error: null, + }; + } + return { + success: false, + [getGraphQLQueryName]: null, + error: bulkErrorFromReason(r.reason), + }; + }); + + return { results }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }); + + if ( + parseGraphQLSchema.addGraphQLType(createManyResultItemType) && + parseGraphQLSchema.addGraphQLType(createManyGraphQLMutation.args.input.type.ofType) && + parseGraphQLSchema.addGraphQLType(createManyGraphQLMutation.type) + ) { + parseGraphQLSchema.addGraphQLMutation(createManyGraphQLMutationName, createManyGraphQLMutation); + } + } + + if (allowUpdateMany) { + const updateManyGraphQLMutationName = updateManyAliasCfg || `updateMany${graphQLClassName}`; + const updateManyResultsPrefix = `results.${getGraphQLQueryName}.`; + const updateManyResultItemType = new GraphQLObjectType({ + name: `UpdateMany${graphQLClassName}ResultItem`, + description: `One result entry for ${updateManyGraphQLMutationName}.`, + fields: () => ({ + success: { + description: 'Whether this object was updated successfully.', + type: new GraphQLNonNull(GraphQLBoolean), + }, + [getGraphQLQueryName]: { + description: `The updated object when success is true.`, + type: classGraphQLOutputType || defaultGraphQLTypes.OBJECT, + }, + error: { + description: 'Present when success is false.', + type: defaultGraphQLTypes.PARSE_GRAPHQL_BULK_ERROR, + }, + }), + }); + const updateManyGraphQLMutation = mutationWithClientMutationId({ + name: `UpdateMany${graphQLClassName}`, + description: `The ${updateManyGraphQLMutationName} mutation updates multiple objects of the ${graphQLClassName} class. Each entry succeeds or fails independently.`, + inputFields: { + updates: { + description: 'List of id + fields pairs; one update per element.', + type: new GraphQLNonNull( + new GraphQLList( + new GraphQLNonNull( + classGraphQLUpdateManyItemType || defaultGraphQLTypes.OBJECT + ) + ) + ), + }, + }, + outputFields: { + results: { + description: 'Update results in the same order as the input list.', + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(updateManyResultItemType)) + ), + }, + }, + mutateAndGetPayload: async (args, context, mutationInfo) => { + try { + let { updates } = cloneArgs(args); + if (!updates) { + updates = []; + } + const { config, auth, info } = context; + assertBulkInputLength(updates.length, config, auth); + + const settled = await Promise.allSettled( + updates.map(updateEntry => + (async () => { + let { id, fields } = updateEntry; + if (!fields) { + fields = {}; + } + id = normalizeObjectIdForClass(id, className); + const parseFields = await transformTypes('update', fields, { + className, + parseGraphQLSchema, + originalFields: updateEntry.fields, + req: { config, auth, info }, + }); + const updatedObject = await objectsMutations.updateObject( + className, + id, + parseFields, + config, + auth, + info + ); + const selectedFields = getFieldNames(mutationInfo) + .filter(field => field.startsWith(updateManyResultsPrefix)) + .map(field => field.replace(updateManyResultsPrefix, '')); + const { keys, include } = extractKeysAndInclude(selectedFields); + const { keys: requiredKeys, needGet } = getOnlyRequiredFields( + fields, + keys, + include, + ['id', 'objectId', 'updatedAt'] + ); + const needToGetAllKeys = objectsQueries.needToGetAllKeys( + parseClass.fields, + keys, + parseGraphQLSchema.parseClasses + ); + let optimizedObject = {}; + if (needGet && !needToGetAllKeys) { + optimizedObject = await objectsQueries.getObject( + className, + id, + requiredKeys, + include, + undefined, + undefined, + config, + auth, + info, + parseGraphQLSchema.parseClasses + ); + } else if (needToGetAllKeys) { + optimizedObject = await objectsQueries.getObject( + className, + id, + undefined, + include, + undefined, + undefined, + config, + auth, + info, + parseGraphQLSchema.parseClasses + ); + } + return { + objectId: id, + ...updatedObject, + ...filterDeletedFields(parseFields), + ...optimizedObject, + }; + })() + ) + ); + + const results = settled.map(r => { + if (r.status === 'fulfilled') { + return { + success: true, + [getGraphQLQueryName]: r.value, + error: null, + }; + } + return { + success: false, + [getGraphQLQueryName]: null, + error: bulkErrorFromReason(r.reason), + }; + }); + + return { results }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }); + + if ( + parseGraphQLSchema.addGraphQLType(updateManyResultItemType) && + parseGraphQLSchema.addGraphQLType(updateManyGraphQLMutation.args.input.type.ofType) && + parseGraphQLSchema.addGraphQLType(updateManyGraphQLMutation.type) + ) { + parseGraphQLSchema.addGraphQLMutation(updateManyGraphQLMutationName, updateManyGraphQLMutation); + } + } + + if (allowDeleteMany) { + const deleteManyGraphQLMutationName = deleteManyAliasCfg || `deleteMany${graphQLClassName}`; + const deleteManyResultsPrefix = `results.${getGraphQLQueryName}.`; + const deleteManyResultItemType = new GraphQLObjectType({ + name: `DeleteMany${graphQLClassName}ResultItem`, + description: `One result entry for ${deleteManyGraphQLMutationName}.`, + fields: () => ({ + success: { + description: 'Whether this object was deleted successfully.', + type: new GraphQLNonNull(GraphQLBoolean), + }, + [getGraphQLQueryName]: { + description: `The object before deletion when success is true.`, + type: classGraphQLOutputType || defaultGraphQLTypes.OBJECT, + }, + error: { + description: 'Present when success is false.', + type: defaultGraphQLTypes.PARSE_GRAPHQL_BULK_ERROR, + }, + }), + }); + const deleteManyGraphQLMutation = mutationWithClientMutationId({ + name: `DeleteMany${graphQLClassName}`, + description: `The ${deleteManyGraphQLMutationName} mutation deletes multiple objects of the ${graphQLClassName} class. Each entry succeeds or fails independently.`, + inputFields: { + ids: { + description: 'Object ids to delete (global or object id).', + type: new GraphQLNonNull(new GraphQLList(defaultGraphQLTypes.OBJECT_ID)), + }, + }, + outputFields: { + results: { + description: 'Deletion results in the same order as the input list.', + type: new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(deleteManyResultItemType)) + ), + }, + }, + mutateAndGetPayload: async (args, context, mutationInfo) => { + try { + let { ids } = cloneArgs(args); + if (!ids) { + ids = []; + } + const { config, auth, info } = context; + assertBulkInputLength(ids.length, config, auth); + + const settled = await Promise.allSettled( + ids.map(id => + (async () => { + const objectId = normalizeObjectIdForClass(id, className); + const selectedFields = getFieldNames(mutationInfo) + .filter(field => field.startsWith(deleteManyResultsPrefix)) + .map(field => field.replace(deleteManyResultsPrefix, '')); + const { keys, include } = extractKeysAndInclude(selectedFields); + let optimizedObject = {}; + if ( + keys && + keys.split(',').filter(key => !['id', 'objectId'].includes(key)).length > 0 + ) { + optimizedObject = await objectsQueries.getObject( + className, + objectId, + keys, + include, + undefined, + undefined, + config, + auth, + info, + parseGraphQLSchema.parseClasses + ); + } + await objectsMutations.deleteObject(className, objectId, config, auth, info); + return { + className, + objectId, + ...optimizedObject, + }; + })() + ) + ); + + const results = settled.map(r => { + if (r.status === 'fulfilled') { + return { + success: true, + [getGraphQLQueryName]: r.value, + error: null, + }; + } + return { + success: false, + [getGraphQLQueryName]: null, + error: bulkErrorFromReason(r.reason), + }; + }); + + return { results }; + } catch (e) { + parseGraphQLSchema.handleError(e); + } + }, + }); + + if ( + parseGraphQLSchema.addGraphQLType(deleteManyResultItemType) && + parseGraphQLSchema.addGraphQLType(deleteManyGraphQLMutation.args.input.type.ofType) && + parseGraphQLSchema.addGraphQLType(deleteManyGraphQLMutation.type) + ) { + parseGraphQLSchema.addGraphQLMutation(deleteManyGraphQLMutationName, deleteManyGraphQLMutation); + } + } }; export { load }; diff --git a/src/GraphQL/loaders/parseClassTypes.js b/src/GraphQL/loaders/parseClassTypes.js index c6c08c8889..f7c980adb0 100644 --- a/src/GraphQL/loaders/parseClassTypes.js +++ b/src/GraphQL/loaders/parseClassTypes.js @@ -186,6 +186,20 @@ const load = (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseGraphQLCla }); classGraphQLUpdateType = parseGraphQLSchema.addGraphQLType(classGraphQLUpdateType); + const classGraphQLUpdateManyItemTypeName = `UpdateMany${graphQLClassName}ItemInput`; + let classGraphQLUpdateManyItemType = new GraphQLInputObjectType({ + name: classGraphQLUpdateManyItemTypeName, + description: `The ${classGraphQLUpdateManyItemTypeName} type is used for each entry in updateMany on the ${graphQLClassName} class.`, + fields: () => ({ + id: defaultGraphQLTypes.GLOBAL_OR_OBJECT_ID_ATT, + fields: { + description: 'These are the fields that will be used to update the object.', + type: new GraphQLNonNull(classGraphQLUpdateType || defaultGraphQLTypes.OBJECT), + }, + }), + }); + classGraphQLUpdateManyItemType = parseGraphQLSchema.addGraphQLType(classGraphQLUpdateManyItemType); + const classGraphQLPointerTypeName = `${graphQLClassName}PointerInput`; let classGraphQLPointerType = new GraphQLInputObjectType({ name: classGraphQLPointerTypeName, @@ -505,6 +519,7 @@ const load = (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseGraphQLCla classGraphQLRelationType, classGraphQLCreateType, classGraphQLUpdateType, + classGraphQLUpdateManyItemType, classGraphQLConstraintsType, classGraphQLRelationConstraintsType, classGraphQLFindArgs, diff --git a/src/batch.js b/src/batch.js index c3c1b2751d..e154ea251d 100644 --- a/src/batch.js +++ b/src/batch.js @@ -1,5 +1,6 @@ const Parse = require('parse/node').Parse; const path = require('path'); +const { isBatchRequestLimitExceeded, getBatchRequestLimit } = require('./batchRequestLimit'); // These methods handle batch requests. const batchPath = '/batch'; @@ -67,8 +68,8 @@ async function handleBatch(router, req) { if (!Array.isArray(req.body?.requests)) { throw new Parse.Error(Parse.Error.INVALID_JSON, 'requests must be an array'); } - const batchRequestLimit = req.config?.requestComplexity?.batchRequestLimit ?? -1; - if (batchRequestLimit > -1 && !req.auth?.isMaster && !req.auth?.isMaintenance && req.body.requests.length > batchRequestLimit) { + if (isBatchRequestLimitExceeded(req.body.requests.length, req.config, req.auth)) { + const batchRequestLimit = getBatchRequestLimit(req.config); throw new Parse.Error( Parse.Error.INVALID_JSON, `Batch request contains ${req.body.requests.length} sub-requests, which exceeds the limit of ${batchRequestLimit}.` diff --git a/src/batchRequestLimit.js b/src/batchRequestLimit.js new file mode 100644 index 0000000000..ebfc3f7e4c --- /dev/null +++ b/src/batchRequestLimit.js @@ -0,0 +1,19 @@ +/** + * Shared logic for `requestComplexity.batchRequestLimit` (REST batch, GraphQL bulk mutations, etc.). + * Matches `src/batch.js` behavior: master and maintenance keys bypass the limit. + */ +function getBatchRequestLimit(config) { + return config?.requestComplexity?.batchRequestLimit ?? -1; +} + +function isBatchRequestLimitExceeded(count, config, auth) { + const batchRequestLimit = getBatchRequestLimit(config); + return ( + batchRequestLimit > -1 && + !auth?.isMaster && + !auth?.isMaintenance && + count > batchRequestLimit + ); +} + +export { getBatchRequestLimit, isBatchRequestLimitExceeded }; From dce171fa8592187e3315fa0cf84bab8b8daee584 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 17:29:00 +0100 Subject: [PATCH 2/7] fix: lock --- package-lock.json | 346 +++++++++++++++++++++++++++------------------- 1 file changed, 201 insertions(+), 145 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44f1e743f1..4dace16fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.1", "@semantic-release/git": "10.0.1", - "@semantic-release/github": "12.0.0", + "@semantic-release/github": "12.0.6", "@semantic-release/npm": "13.0.0", "@semantic-release/release-notes-generator": "14.1.0", "all-node-versions": "13.0.1", @@ -83,7 +83,7 @@ "form-data": "4.0.5", "globals": "17.3.0", "graphql-tag": "2.12.6", - "jasmine": "5.7.1", + "jasmine": "6.1.0", "jasmine-spec-reporter": "7.0.0", "jsdoc": "4.0.4", "jsdoc-babel": "0.5.0", @@ -162,7 +162,6 @@ "integrity": "sha512-YM9lQpm0VfVco4DSyKooHS/fDTiKQcCHfxr7i3iL6a0kP/jNO5+4NFK6vtRDxaYisd5BrwOZHLJpPBnvRVpKPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -229,7 +228,6 @@ "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", "license": "MIT", - "peer": true, "dependencies": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^2.0.0", @@ -508,7 +506,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3238,6 +3235,13 @@ "node": ">=8" } }, + "node_modules/@jasminejs/reporters": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jasminejs/reporters/-/reporters-1.0.0.tgz", + "integrity": "sha512-rM3GG4vx2H1Gp5kYCTr9aKlOEJFd43pzpiMAiy5b1+FUc2ub4e6bS6yCi/WQNDzAa5MVp9++dwcoEtcIfoEnhA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3791,7 +3795,6 @@ "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -3892,13 +3895,13 @@ "dev": true }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.0.1.tgz", - "integrity": "sha512-m1KvHlueScy4mQJWvFDCxFBTIdXS0K1SgFGLmqHyX90mZdCIv6gWBbKRhatxRjhGlONuTK/hztYdaqrTXcFZdQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^14.1.0" + "@octokit/types": "^16.0.0" }, "engines": { "node": ">= 20" @@ -3908,20 +3911,20 @@ } }, "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^25.1.0" + "@octokit/openapi-types": "^27.0.0" } }, "node_modules/@octokit/plugin-retry": { @@ -4310,7 +4313,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -4390,7 +4392,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5078,7 +5079,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", "dev": true, - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -5198,7 +5198,6 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.12.tgz", "integrity": "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==", "dev": true, - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -5474,14 +5473,14 @@ } }, "node_modules/@semantic-release/github": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.0.tgz", - "integrity": "sha512-louWFjzZ+1dogfJTY8IuJuBcBUOTliYhBUYNcomnTfj0i959wtRQbr1POgdCoTHK7ut4N/0LNlYTH8SvSJM3hg==", + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.6.tgz", + "integrity": "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==", "dev": true, "license": "MIT", "dependencies": { "@octokit/core": "^7.0.0", - "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-retry": "^8.0.0", "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -5495,6 +5494,7 @@ "mime": "^4.0.0", "p-filter": "^4.0.0", "tinyglobby": "^0.2.14", + "undici": "^7.0.0", "url-join": "^5.0.0" }, "engines": { @@ -5568,6 +5568,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/github/node_modules/undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/@semantic-release/npm": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.0.0.tgz", @@ -7576,7 +7586,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7974,7 +7983,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -8036,7 +8044,6 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", "dev": true, - "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -8169,7 +8176,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", @@ -8262,7 +8268,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -9054,7 +9059,6 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9435,7 +9439,8 @@ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "node_modules/backoff": { "version": "2.5.0", @@ -9614,7 +9619,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -11750,7 +11754,6 @@ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -12129,7 +12132,8 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "node_modules/execa": { "version": "5.1.1", @@ -12180,7 +12184,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -12224,7 +12227,6 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", "license": "MIT", - "peer": true, "dependencies": { "ip-address": "10.1.0" }, @@ -13139,6 +13141,7 @@ "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", "license": "Apache-2.0", "optional": true, + "peer": true, "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", @@ -13154,6 +13157,7 @@ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -13164,6 +13168,7 @@ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -13181,6 +13186,7 @@ "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "license": "Apache-2.0", "optional": true, + "peer": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -13198,6 +13204,7 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -13219,6 +13226,7 @@ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -13235,6 +13243,7 @@ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", "optional": true, + "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -13245,6 +13254,7 @@ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -13264,6 +13274,7 @@ "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "glob": "^10.3.7" }, @@ -13280,6 +13291,7 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", "optional": true, + "peer": true, "engines": { "node": ">=14" }, @@ -13701,6 +13713,7 @@ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "optional": true, + "peer": true, "engines": { "node": ">=14" } @@ -13753,7 +13766,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -13857,9 +13869,9 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "dependencies": { "minimist": "^1.2.5", @@ -14735,7 +14747,8 @@ "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "node_modules/jackspeak": { "version": "3.4.3", @@ -14753,23 +14766,24 @@ } }, "node_modules/jasmine": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.7.1.tgz", - "integrity": "sha512-E/4fkRNy/9ALz6z3Z3/tYXFAohoznVy7In9FWutG2fqBSkILJHFzbgZtHJUw5UrL3jgUQ4sdGYOVZ5KpSXYjGw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-6.1.0.tgz", + "integrity": "sha512-WPphPqEMY0uBRMjuhRHoVoxQNvJuxIMqz0yIcJ3k3oYxBedeGoH60/NXNgasxnx2FvfXrq5/r+2wssJ7WE8ABw==", "dev": true, "license": "MIT", "dependencies": { - "glob": "^10.2.2", - "jasmine-core": "~5.7.0" + "@jasminejs/reporters": "^1.0.0", + "glob": "^10.2.2 || ^11.0.3 || ^12.0.0 || ^13.0.0", + "jasmine-core": "~6.1.0" }, "bin": { "jasmine": "bin/jasmine.js" } }, "node_modules/jasmine-core": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.7.1.tgz", - "integrity": "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-6.1.0.tgz", + "integrity": "sha512-p/tjBw58O6vxKIWMlrU+yys8lqR3+l3UrqwNTT7wpj+dQ7N4etQekFM8joI+cWzPDYqZf54kN+hLC1+s5TvZvg==", "dev": true, "license": "MIT" }, @@ -14912,7 +14926,6 @@ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, - "peer": true, "dependencies": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", @@ -16067,7 +16080,6 @@ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, - "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -16095,7 +16107,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -16578,6 +16589,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "debug": "4" }, @@ -16648,6 +16660,7 @@ "dev": true, "license": "Apache-2.0", "optional": true, + "peer": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -16658,6 +16671,22 @@ "node": ">=12" } }, + "node_modules/mongodb-runner/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/mongodb-runner/node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -16665,6 +16694,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -16726,6 +16756,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -16747,7 +16778,8 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/mongodb-runner/node_modules/webidl-conversions": { "version": "3.0.1", @@ -16755,7 +16787,8 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, "license": "BSD-2-Clause", - "optional": true + "optional": true, + "peer": true }, "node_modules/mongodb-runner/node_modules/whatwg-url": { "version": "5.0.0", @@ -16764,6 +16797,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -20455,7 +20489,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -20496,6 +20529,7 @@ "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.17.0.tgz", "integrity": "sha512-2Uio3Xfl5ldwJfls+RgGL+YbPcKQncWACWjYQFqlamvHZ4HJFjZhhZBbqd7jQ2LIkZYSvU90bm2dNW0rno+QFQ==", "license": "MIT", + "peer": true, "peerDependencies": { "pg": "^8" } @@ -20607,9 +20641,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "engines": { "node": ">=8.6" @@ -20777,7 +20811,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -21785,7 +21818,6 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -23974,7 +24006,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -25079,6 +25110,7 @@ "deprecated": "The `subscriptions-transport-ws` package is no longer maintained. We recommend you use `graphql-ws` instead. For help migrating Apollo software to `graphql-ws`, see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws For general help using `graphql-ws`, see https://github.com/enisdenjo/graphql-ws/blob/master/README.md", "dev": true, "optional": true, + "peer": true, "dependencies": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -25096,6 +25128,7 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -25106,6 +25139,7 @@ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -25582,11 +25616,10 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "peer": true, "engines": { "node": ">=12" }, @@ -25841,7 +25874,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26919,7 +26951,6 @@ "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.8.tgz", "integrity": "sha512-YM9lQpm0VfVco4DSyKooHS/fDTiKQcCHfxr7i3iL6a0kP/jNO5+4NFK6vtRDxaYisd5BrwOZHLJpPBnvRVpKPg==", "dev": true, - "peer": true, "requires": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -26959,7 +26990,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.0.tgz", "integrity": "sha512-vWtodBOK/SZwBTJzItECOmLfL8E8pn/IdvP7pnxN5g2tny9iW4+9sxdajE798wV1H2+PYp/rRcl/soSHIBKMPw==", - "peer": true, "requires": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^2.0.0", @@ -27145,7 +27175,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, - "peer": true, "requires": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -28971,6 +29000,12 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@jasminejs/reporters": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jasminejs/reporters/-/reporters-1.0.0.tgz", + "integrity": "sha512-rM3GG4vx2H1Gp5kYCTr9aKlOEJFd43pzpiMAiy5b1+FUc2ub4e6bS6yCi/WQNDzAa5MVp9++dwcoEtcIfoEnhA==", + "dev": true + }, "@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -29349,7 +29384,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.2.tgz", "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", "dev": true, - "peer": true, "requires": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -29439,27 +29473,27 @@ "dev": true }, "@octokit/plugin-paginate-rest": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.0.1.tgz", - "integrity": "sha512-m1KvHlueScy4mQJWvFDCxFBTIdXS0K1SgFGLmqHyX90mZdCIv6gWBbKRhatxRjhGlONuTK/hztYdaqrTXcFZdQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, "requires": { - "@octokit/types": "^14.1.0" + "@octokit/types": "^16.0.0" }, "dependencies": { "@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true }, "@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "requires": { - "@octokit/openapi-types": "^25.1.0" + "@octokit/openapi-types": "^27.0.0" } } } @@ -29775,7 +29809,6 @@ "version": "5.11.0", "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", - "peer": true, "requires": { "cluster-key-slot": "1.1.2" } @@ -29823,7 +29856,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, - "peer": true, "requires": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -30314,8 +30346,7 @@ "version": "9.1.6", "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", - "dev": true, - "peer": true + "dev": true }, "marked-terminal": { "version": "6.2.0", @@ -30384,7 +30415,6 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.12.tgz", "integrity": "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==", "dev": true, - "peer": true, "requires": { "@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -30587,13 +30617,13 @@ } }, "@semantic-release/github": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.0.tgz", - "integrity": "sha512-louWFjzZ+1dogfJTY8IuJuBcBUOTliYhBUYNcomnTfj0i959wtRQbr1POgdCoTHK7ut4N/0LNlYTH8SvSJM3hg==", + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.6.tgz", + "integrity": "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==", "dev": true, "requires": { "@octokit/core": "^7.0.0", - "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-retry": "^8.0.0", "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", @@ -30607,6 +30637,7 @@ "mime": "^4.0.0", "p-filter": "^4.0.0", "tinyglobby": "^0.2.14", + "undici": "^7.0.0", "url-join": "^5.0.0" }, "dependencies": { @@ -30646,6 +30677,12 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true + }, + "undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "dev": true } } }, @@ -31939,8 +31976,7 @@ "picomatch": { "version": "4.0.3", "bundled": true, - "dev": true, - "peer": true + "dev": true } } }, @@ -32197,7 +32233,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "peer": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -32257,7 +32292,6 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", "dev": true, - "peer": true, "requires": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -32386,7 +32420,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, - "peer": true, "requires": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", @@ -32440,7 +32473,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, - "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -32952,8 +32984,7 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "peer": true + "dev": true }, "acorn-jsx": { "version": "5.3.2", @@ -33236,7 +33267,8 @@ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "backoff": { "version": "2.5.0", @@ -33361,7 +33393,6 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, - "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -34861,7 +34892,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -35115,7 +35145,8 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "execa": { "version": "5.1.1", @@ -35155,7 +35186,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "peer": true, "requires": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -35206,7 +35236,6 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", - "peer": true, "requires": { "ip-address": "10.1.0" } @@ -35809,6 +35838,7 @@ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", "optional": true, + "peer": true, "requires": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", @@ -35820,6 +35850,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "optional": true, + "peer": true, "requires": { "balanced-match": "^1.0.0" } @@ -35829,6 +35860,7 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "optional": true, + "peer": true, "requires": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -35839,6 +35871,7 @@ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "optional": true, + "peer": true, "requires": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -35851,6 +35884,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "optional": true, + "peer": true, "requires": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -35865,6 +35899,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "optional": true, + "peer": true, "requires": { "brace-expansion": "^2.0.1" } @@ -35873,13 +35908,15 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "optional": true + "optional": true, + "peer": true }, "node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "optional": true, + "peer": true, "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -35891,6 +35928,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "optional": true, + "peer": true, "requires": { "glob": "^10.3.7" } @@ -35899,7 +35937,8 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "optional": true + "optional": true, + "peer": true } } }, @@ -36209,7 +36248,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "optional": true + "optional": true, + "peer": true }, "gopd": { "version": "1.2.0", @@ -36244,8 +36284,7 @@ "graphql": { "version": "16.13.2", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", - "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", - "peer": true + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==" }, "graphql-list-fields": { "version": "2.0.4", @@ -36312,9 +36351,9 @@ } }, "handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "requires": { "minimist": "^1.2.5", @@ -36924,7 +36963,8 @@ "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "jackspeak": { "version": "3.4.3", @@ -36937,13 +36977,14 @@ } }, "jasmine": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.7.1.tgz", - "integrity": "sha512-E/4fkRNy/9ALz6z3Z3/tYXFAohoznVy7In9FWutG2fqBSkILJHFzbgZtHJUw5UrL3jgUQ4sdGYOVZ5KpSXYjGw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-6.1.0.tgz", + "integrity": "sha512-WPphPqEMY0uBRMjuhRHoVoxQNvJuxIMqz0yIcJ3k3oYxBedeGoH60/NXNgasxnx2FvfXrq5/r+2wssJ7WE8ABw==", "dev": true, "requires": { - "glob": "^10.2.2", - "jasmine-core": "~5.7.0" + "@jasminejs/reporters": "^1.0.0", + "glob": "^10.2.2 || ^11.0.3 || ^12.0.0 || ^13.0.0", + "jasmine-core": "~6.1.0" }, "dependencies": { "brace-expansion": { @@ -37003,9 +37044,9 @@ } }, "jasmine-core": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.7.1.tgz", - "integrity": "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-6.1.0.tgz", + "integrity": "sha512-p/tjBw58O6vxKIWMlrU+yys8lqR3+l3UrqwNTT7wpj+dQ7N4etQekFM8joI+cWzPDYqZf54kN+hLC1+s5TvZvg==", "dev": true }, "jasmine-spec-reporter": { @@ -37057,7 +37098,6 @@ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, - "peer": true, "requires": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", @@ -37920,7 +37960,6 @@ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, - "peer": true, "requires": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -37941,8 +37980,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true, - "peer": true + "dev": true }, "marked-terminal": { "version": "7.3.0", @@ -38262,6 +38300,7 @@ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, + "peer": true, "requires": { "debug": "4" } @@ -38308,10 +38347,12 @@ "dev": true }, "gaxios": { - "version": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", "dev": true, "optional": true, + "peer": true, "requires": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -38319,12 +38360,25 @@ "node-fetch": "^2.6.9" } }, + "gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "optional": true, + "peer": true, "requires": { "agent-base": "6", "debug": "4" @@ -38347,6 +38401,7 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "optional": true, + "peer": true, "requires": { "whatwg-url": "^5.0.0" } @@ -38356,14 +38411,16 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "whatwg-url": { "version": "5.0.0", @@ -38371,6 +38428,7 @@ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "optional": true, + "peer": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -40874,7 +40932,6 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", - "peer": true, "requires": { "pg-cloudflare": "^1.3.0", "pg-connection-string": "^2.11.0", @@ -40899,6 +40956,7 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.17.0.tgz", "integrity": "sha512-2Uio3Xfl5ldwJfls+RgGL+YbPcKQncWACWjYQFqlamvHZ4HJFjZhhZBbqd7jQ2LIkZYSvU90bm2dNW0rno+QFQ==", + "peer": true, "requires": {} }, "pg-int8": { @@ -40976,9 +41034,9 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true }, "pidtree": { @@ -41084,7 +41142,6 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, - "peer": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -41795,7 +41852,6 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.3.tgz", "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, - "peer": true, "requires": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -43204,8 +43260,7 @@ "picomatch": { "version": "4.0.3", "bundled": true, - "dev": true, - "peer": true + "dev": true } } }, @@ -43980,6 +44035,7 @@ "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", "dev": true, "optional": true, + "peer": true, "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -43993,7 +44049,8 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "ws": { "version": "7.5.9", @@ -44001,6 +44058,7 @@ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true, "optional": true, + "peer": true, "requires": {} } } @@ -44323,11 +44381,10 @@ "requires": {} }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "peer": true + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true } } }, @@ -44507,8 +44564,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "peer": true + "dev": true }, "typescript-eslint": { "version": "8.53.1", From 4196811f768af8b63a09ea89e542d14ff7837d95 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 18:02:39 +0100 Subject: [PATCH 3/7] fix: sanitize bulk errors --- spec/ParseGraphQLServer.spec.js | 102 ++++++++++++++++++++- spec/Utils.spec.js | 34 ++++++- src/Error.js | 36 +++++++- src/GraphQL/loaders/parseClassMutations.js | 17 +--- 4 files changed, 171 insertions(+), 18 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index 551877a620..adaa0a62e7 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -11834,6 +11834,44 @@ describe('ParseGraphQLServer', () => { ); } + async function reconfigureGraphQLWithUnsanitizedErrorsAndOpenClient() { + parseServer = await global.reconfigureServer({ + maintenanceKey: 'test2', + maxUploadSize: '1kb', + enableSanitizedErrorResponse: false, + }); + await createGQLFromParseServer(parseServer); + const httpLink = await createUploadLink({ + uri: 'http://localhost:13377/graphql', + fetch, + headers, + }); + apolloClient = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + defaultOptions: { + query: { + fetchPolicy: 'no-cache', + }, + }, + }); + const sc = new Parse.Schema('BulkTest'); + await sc.purge().catch(() => {}); + await sc.delete().catch(() => {}); + await sc.addString('title').save(); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + await updateCLP( + { + create: { '*': true }, + find: { '*': true }, + get: { '*': true }, + update: { '*': true }, + delete: { '*': true }, + }, + 'BulkTest' + ); + } + const clientKeyHeaders = { headers: { 'X-Parse-Application-Id': 'test', @@ -12213,7 +12251,7 @@ describe('ParseGraphQLServer', () => { expect(results[1].success).toBe(false); expect(results[1].bulkTest).toBeNull(); expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); - expect(results[1].error.message).toContain('beforeSave blocked'); + expect(results[1].error.message).toBe('Permission denied'); const q = new Parse.Query('BulkTest'); q.equalTo('title', 'ok'); @@ -12224,6 +12262,64 @@ describe('ParseGraphQLServer', () => { } }); + it('should return detailed Parse.Error message in createMany bulk when enableSanitizedErrorResponse is false', async () => { + try { + await reconfigureGraphQLWithUnsanitizedErrorsAndOpenClient(); + + Parse.Cloud.beforeSave('BulkTest', request => { + if (request.object.get('title') === 'FAIL') { + throw new Parse.Error( + Parse.Error.SCRIPT_FAILED, + 'beforeSave blocked this title' + ); + } + }); + + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const clientMutationId = uuidv4(); + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyBulkUnsanitized($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId, + fields: [{ title: 'ok' }, { title: 'FAIL' }], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + + const results = data.createManyBulkTest.results; + expect(results.length).toBe(2); + expect(results[1].success).toBe(false); + expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(results[1].error.message).toBe('beforeSave blocked this title'); + } catch (e) { + handleError(e); + } + }); + it('should updateMany with partial failure when beforeSave rejects', async () => { try { const a = new Parse.Object('BulkTest'); @@ -12287,7 +12383,7 @@ describe('ParseGraphQLServer', () => { expect(results[1].success).toBe(false); expect(results[1].bulkTest).toBeNull(); expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); - expect(results[1].error.message).toContain('beforeSave blocked'); + expect(results[1].error.message).toBe('Permission denied'); await a.fetch({ useMasterKey: true }); await b.fetch({ useMasterKey: true }); @@ -12358,7 +12454,7 @@ describe('ParseGraphQLServer', () => { expect(results[1].success).toBe(false); expect(results[1].bulkTest).toBeNull(); expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); - expect(results[1].error.message).toContain('beforeDelete blocked'); + expect(results[1].error.message).toBe('Permission denied'); const q = new Parse.Query('BulkTest'); const remaining = await q.find({ useMasterKey: true }); diff --git a/spec/Utils.spec.js b/spec/Utils.spec.js index f6d24b55f9..2b3a999700 100644 --- a/spec/Utils.spec.js +++ b/spec/Utils.spec.js @@ -1,5 +1,5 @@ const Utils = require('../lib/Utils'); -const { createSanitizedError, createSanitizedHttpError } = require("../lib/Error") +const { createSanitizedError, createSanitizedHttpError, bulkErrorPayloadFromReason } = require("../lib/Error") const vm = require('vm'); describe('Utils', () => { @@ -289,6 +289,38 @@ describe('Utils', () => { }); }); + describe('bulkErrorPayloadFromReason', () => { + it('should sanitize Parse.Error messages when enableSanitizedErrorResponse is true', () => { + const config = { enableSanitizedErrorResponse: true }; + const reason = new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Cloud script detail'); + const payload = bulkErrorPayloadFromReason(reason, config); + expect(payload.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(payload.message).toBe('Permission denied'); + }); + + it('should return detailed Parse.Error messages when enableSanitizedErrorResponse is false', () => { + const config = { enableSanitizedErrorResponse: false }; + const reason = new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Cloud script detail'); + const payload = bulkErrorPayloadFromReason(reason, config); + expect(payload.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(payload.message).toBe('Cloud script detail'); + }); + + it('should sanitize non-Parse reasons', () => { + const config = { enableSanitizedErrorResponse: true }; + const payload = bulkErrorPayloadFromReason(new Error('internal stack trace'), config); + expect(payload.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR); + expect(payload.message).toBe('Internal server error'); + }); + + it('should return non-Parse message when enableSanitizedErrorResponse is false', () => { + const config = { enableSanitizedErrorResponse: false }; + const payload = bulkErrorPayloadFromReason(new Error('internal stack trace'), config); + expect(payload.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR); + expect(payload.message).toBe('internal stack trace'); + }); + }); + describe('isDate', () => { it('should return true for a Date', () => { expect(Utils.isDate(new Date())).toBe(true); diff --git a/src/Error.js b/src/Error.js index 55bbd6ebea..db1f549bb3 100644 --- a/src/Error.js +++ b/src/Error.js @@ -1,4 +1,5 @@ import defaultLogger from './logger'; +import Utils from './Utils'; /** * Creates a sanitized error that hides detailed information from clients @@ -43,4 +44,37 @@ function createSanitizedHttpError(statusCode, detailedMessage, config) { return error; } -export { createSanitizedError, createSanitizedHttpError }; +/** + * `{ code, message }` for GraphQL bulk mutation per-item failures (`ParseGraphQLBulkError`). + * `Parse.Error` uses `createSanitizedError`; other values are logged and mapped to a generic message when sanitizing. + * + * @param {unknown} reason + * @param {object} config + * @returns {{ code: number, message: string }} + */ +function bulkErrorPayloadFromReason(reason, config) { + if (reason instanceof Parse.Error) { + const sanitized = createSanitizedError(reason.code, reason.message, config); + return { code: sanitized.code, message: sanitized.message }; + } + const detailedMessage = + reason && typeof reason.message === 'string' + ? reason.message + : reason !== undefined && reason !== null + ? String(reason) + : 'Internal server error'; + if (process.env.TESTING) { + defaultLogger.error('Bulk mutation non-Parse error:', detailedMessage); + } else { + defaultLogger.error( + 'Bulk mutation non-Parse error:', + detailedMessage, + Utils.isNativeError(reason) ? reason.stack : '' + ); + } + const message = + config?.enableSanitizedErrorResponse !== false ? 'Internal server error' : detailedMessage; + return { code: Parse.Error.INTERNAL_SERVER_ERROR, message }; +} + +export { createSanitizedError, createSanitizedHttpError, bulkErrorPayloadFromReason }; diff --git a/src/GraphQL/loaders/parseClassMutations.js b/src/GraphQL/loaders/parseClassMutations.js index 59581f9bbc..3a2a904266 100644 --- a/src/GraphQL/loaders/parseClassMutations.js +++ b/src/GraphQL/loaders/parseClassMutations.js @@ -10,18 +10,9 @@ import * as objectsQueries from '../helpers/objectsQueries'; import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController'; import { transformClassNameToGraphQL } from '../transformers/className'; import { transformTypes } from '../transformers/mutation'; -import { createSanitizedError } from '../../Error'; +import { createSanitizedError, bulkErrorPayloadFromReason } from '../../Error'; import { getBatchRequestLimit, isBatchRequestLimitExceeded } from '../../batchRequestLimit'; -const bulkErrorFromReason = reason => { - if (reason instanceof Parse.Error) { - return { code: reason.code, message: reason.message }; - } - const message = - reason && typeof reason.message === 'string' ? reason.message : 'Internal server error'; - return { code: Parse.Error.INTERNAL_SERVER_ERROR, message }; -}; - const normalizeObjectIdForClass = (id, className) => { try { const globalIdObject = fromGlobalId(id); @@ -516,7 +507,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG return { success: false, [getGraphQLQueryName]: null, - error: bulkErrorFromReason(r.reason), + error: bulkErrorPayloadFromReason(r.reason, config), }; }); @@ -675,7 +666,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG return { success: false, [getGraphQLQueryName]: null, - error: bulkErrorFromReason(r.reason), + error: bulkErrorPayloadFromReason(r.reason, config), }; }); @@ -789,7 +780,7 @@ const load = function (parseGraphQLSchema, parseClass, parseClassConfig: ?ParseG return { success: false, [getGraphQLQueryName]: null, - error: bulkErrorFromReason(r.reason), + error: bulkErrorPayloadFromReason(r.reason, config), }; }); From 3cc83d922acd7f45400a23bb11b7bc70c8e04fed Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 18:11:10 +0100 Subject: [PATCH 4/7] fix: ignore duplicate check --- src/GraphQL/loaders/defaultGraphQLTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL/loaders/defaultGraphQLTypes.js b/src/GraphQL/loaders/defaultGraphQLTypes.js index 9f1f675c4e..82c34a0342 100644 --- a/src/GraphQL/loaders/defaultGraphQLTypes.js +++ b/src/GraphQL/loaders/defaultGraphQLTypes.js @@ -1279,7 +1279,7 @@ const load = parseGraphQLSchema => { parseGraphQLSchema.addGraphQLType(PUBLIC_ACL, true); parseGraphQLSchema.addGraphQLType(SUBQUERY_INPUT, true); parseGraphQLSchema.addGraphQLType(SELECT_INPUT, true); - parseGraphQLSchema.addGraphQLType(PARSE_GRAPHQL_BULK_ERROR, true, true); + parseGraphQLSchema.addGraphQLType(PARSE_GRAPHQL_BULK_ERROR, true); }; export { From 8f24cdbdfe16af4271729da4dd868461c26547b2 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 18:40:31 +0100 Subject: [PATCH 5/7] fix: improve error util --- spec/Utils.spec.js | 26 ++++++++++++++++++++++++++ src/Error.js | 24 ++++++++++++++++++------ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/spec/Utils.spec.js b/spec/Utils.spec.js index 2b3a999700..face2ceec1 100644 --- a/spec/Utils.spec.js +++ b/spec/Utils.spec.js @@ -319,6 +319,32 @@ describe('Utils', () => { expect(payload.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR); expect(payload.message).toBe('internal stack trace'); }); + + it('should not throw when reason.message getter throws', () => { + const reason = {}; + Object.defineProperty(reason, 'message', { + get() { + throw new Error('boom'); + }, + configurable: true, + }); + const sanitized = bulkErrorPayloadFromReason(reason, { enableSanitizedErrorResponse: true }); + expect(sanitized.message).toBe('Internal server error'); + const detailed = bulkErrorPayloadFromReason(reason, { enableSanitizedErrorResponse: false }); + expect(detailed.message).toBe('Internal server error'); + }); + + it('should not throw when String(reason) would throw', () => { + const reason = { + toString() { + throw new Error('boom'); + }, + }; + const sanitized = bulkErrorPayloadFromReason(reason, { enableSanitizedErrorResponse: true }); + expect(sanitized.message).toBe('Internal server error'); + const detailed = bulkErrorPayloadFromReason(reason, { enableSanitizedErrorResponse: false }); + expect(detailed.message).toBe('Internal server error'); + }); }); describe('isDate', () => { diff --git a/src/Error.js b/src/Error.js index db1f549bb3..3ed96a2140 100644 --- a/src/Error.js +++ b/src/Error.js @@ -44,6 +44,23 @@ function createSanitizedHttpError(statusCode, detailedMessage, config) { return error; } +function safeBulkReasonDetailedMessage(reason) { + if (reason === undefined || reason === null) { + return 'Internal server error'; + } + try { + let detail; + if (typeof reason.message === 'string') { + detail = reason.message; + } else { + detail = String(reason); + } + return typeof detail === 'string' ? detail : 'Internal server error'; + } catch { + return 'Internal server error'; + } +} + /** * `{ code, message }` for GraphQL bulk mutation per-item failures (`ParseGraphQLBulkError`). * `Parse.Error` uses `createSanitizedError`; other values are logged and mapped to a generic message when sanitizing. @@ -57,12 +74,7 @@ function bulkErrorPayloadFromReason(reason, config) { const sanitized = createSanitizedError(reason.code, reason.message, config); return { code: sanitized.code, message: sanitized.message }; } - const detailedMessage = - reason && typeof reason.message === 'string' - ? reason.message - : reason !== undefined && reason !== null - ? String(reason) - : 'Internal server error'; + const detailedMessage = safeBulkReasonDetailedMessage(reason); if (process.env.TESTING) { defaultLogger.error('Bulk mutation non-Parse error:', detailedMessage); } else { From 8b41f8dbbc9334af438f92a5d3cd2a2e925d7cb7 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 19:40:30 +0100 Subject: [PATCH 6/7] test: cover sanitize errors --- spec/ParseGraphQLServer.spec.js | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index adaa0a62e7..b9c94cf0c4 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -12320,6 +12320,113 @@ describe('ParseGraphQLServer', () => { } }); + it('should sanitize non-Parse Error messages in createMany bulk when sanitization is enabled', async () => { + try { + await reconfigureGraphQLWithBatchLimit2AndOpenClient(); + + Parse.Cloud.beforeSave('BulkTest', request => { + if (request.object.get('title') === 'FAIL') { + throw new Error('internal stack detail'); + } + }); + + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyBulkPlainErrorSanitized($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId: uuidv4(), + fields: [{ title: 'ok' }, { title: 'FAIL' }], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + + const [ok, failed] = data.createManyBulkTest.results; + expect(ok.success).toBe(true); + expect(failed.success).toBe(false); + // Cloud Code wraps a plain Error as Parse.Error(SCRIPT_FAILED) before bulk handling; + // sanitized response matches other Parse.Error bulk failures. + expect(failed.error.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(failed.error.message).toBe('Permission denied'); + } catch (e) { + handleError(e); + } + }); + + it('should return raw non-Parse Error message in createMany bulk when sanitization is disabled', async () => { + try { + await reconfigureGraphQLWithUnsanitizedErrorsAndOpenClient(); + + Parse.Cloud.beforeSave('BulkTest', request => { + if (request.object.get('title') === 'FAIL') { + throw new Error('internal stack detail'); + } + }); + + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const { data } = await apolloClient.mutate({ + mutation: gql` + mutation CreateManyBulkPlainErrorUnsanitized($input: CreateManyBulkTestInput!) { + createManyBulkTest(input: $input) { + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId: uuidv4(), + fields: [{ title: 'ok' }, { title: 'FAIL' }], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + + const failed = data.createManyBulkTest.results[1]; + expect(failed.success).toBe(false); + expect(failed.error.code).toBe(Parse.Error.SCRIPT_FAILED); + expect(failed.error.message).toBe('internal stack detail'); + } catch (e) { + handleError(e); + } + }); + it('should updateMany with partial failure when beforeSave rejects', async () => { try { const a = new Parse.Object('BulkTest'); From 9eb43e1f36ba1e98bd73853c77bfa1497f6d95d9 Mon Sep 17 00:00:00 2001 From: Antoine Cormouls Date: Sat, 28 Mar 2026 19:58:45 +0100 Subject: [PATCH 7/7] test: duplicate id during delete --- spec/ParseGraphQLServer.spec.js | 87 +++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index b9c94cf0c4..83582cca3f 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -12192,6 +12192,58 @@ describe('ParseGraphQLServer', () => { expect(data.deleteManyBulkTest.results.length).toBe(2); expect(data.deleteManyBulkTest.results[0].success).toBe(true); expect(data.deleteManyBulkTest.results[1].success).toBe(true); + + const dup = new Parse.Object('BulkTest'); + dup.set('title', 'dupDel'); + await dup.save(null, { useMasterKey: true }); + await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); + + const dupClientMutationId = uuidv4(); + const { data: dupData } = await apolloClient.mutate({ + mutation: gql` + mutation DeleteManyBulkDup($input: DeleteManyBulkTestInput!) { + deleteManyBulkTest(input: $input) { + clientMutationId + results { + success + error { + code + message + } + bulkTest { + objectId + title + } + } + } + } + `, + variables: { + input: { + clientMutationId: dupClientMutationId, + ids: [ + toGlobalId('BulkTest', dup.id), + toGlobalId('BulkTest', dup.id), + ], + }, + }, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + const dupResults = dupData.deleteManyBulkTest.results; + expect(dupResults.length).toBe(2); + expect(dupResults.filter(r => r.success).length).toBe(1); + expect(dupResults.filter(r => !r.success).length).toBe(1); + expect(dupResults.find(r => !r.success).error.code).toBe(Parse.Error.OBJECT_NOT_FOUND); + expect(dupResults.find(r => r.success).bulkTest.title).toBe('dupDel'); + + const qDup = new Parse.Query('BulkTest'); + qDup.equalTo('objectId', dup.id); + const dupRemaining = await qDup.find({ useMasterKey: true }); + expect(dupRemaining.length).toBe(0); } catch (e) { handleError(e); } @@ -12543,7 +12595,11 @@ describe('ParseGraphQLServer', () => { variables: { input: { clientMutationId, - ids: [toGlobalId('BulkTest', a.id), toGlobalId('BulkTest', b.id)], + ids: [ + toGlobalId('BulkTest', a.id), + toGlobalId('BulkTest', a.id), + toGlobalId('BulkTest', b.id), + ], }, }, context: { @@ -12554,14 +12610,27 @@ describe('ParseGraphQLServer', () => { }); const results = data.deleteManyBulkTest.results; - expect(results.length).toBe(2); - expect(results[0].success).toBe(true); - expect(results[0].bulkTest.title).toBe('deletable'); - expect(results[0].error).toBeNull(); - expect(results[1].success).toBe(false); - expect(results[1].bulkTest).toBeNull(); - expect(results[1].error.code).toBe(Parse.Error.SCRIPT_FAILED); - expect(results[1].error.message).toBe('Permission denied'); + expect(results.length).toBe(3); + expect(results.filter(r => r.success).length).toBe(1); + expect( + results.filter(r => !r.success && r.error.code === Parse.Error.OBJECT_NOT_FOUND) + .length + ).toBe(1); + expect( + results.filter(r => !r.success && r.error.code === Parse.Error.SCRIPT_FAILED).length + ).toBe(1); + const deleted = results.find(r => r.success); + expect(deleted.bulkTest.title).toBe('deletable'); + expect(deleted.error).toBeNull(); + const notFound = results.find( + r => !r.success && r.error.code === Parse.Error.OBJECT_NOT_FOUND + ); + expect(notFound.bulkTest).toBeNull(); + const blocked = results.find( + r => !r.success && r.error.code === Parse.Error.SCRIPT_FAILED + ); + expect(blocked.bulkTest).toBeNull(); + expect(blocked.error.message).toBe('Permission denied'); const q = new Parse.Query('BulkTest'); const remaining = await q.find({ useMasterKey: true });