diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 4c5a7a0..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1 +0,0 @@ -github: jaydenseric diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79b8cb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Personal Configs +.DS_Store + +# node directories +node_modules +*.tsbuildinfo + +# build folder +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index d2504b4..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "proseWrap": "never" -} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..64bdd4d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Workday, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4ecfbfc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2910 @@ +{ + "name": "graphql-multipart-request-spec", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "graphql-multipart-request-spec", + "devDependencies": { + "chokidar": "^3.6.0", + "live-server": "^1.2.2", + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "spec-md": "^3.1.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/git@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/git@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/gitaccepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/gitansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha1-MYPjj66aZdfLXlOUXNWJfQJgoGo=", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/gitansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/gitanymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apache-crypt": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/gitapache-crypt/-/apache-crypt-1.2.6.tgz", + "integrity": "sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==", + "dev": true, + "dependencies": { + "unix-crypt-td-js": "^1.1.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/apache-md5": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/gitapache-md5/-/apache-md5-1.1.8.tgz", + "integrity": "sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gitarr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gitarr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gitarr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/gitarray-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitassign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-each": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/gitasync-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gitatob/-/atob-2.1.2.tgz", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitbalanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4=", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/gitbase/-/base-0.11.2.tgz", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitbasic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha1-uZgnm/R844NEtPPPkW1Gebv1Hjo=", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/gitbatch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/gitbcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gitbinary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/gitbindings/-/bindings-1.5.0.tgz", + "integrity": "sha1-EDU8npRTNLwFEabZCzj7x8nFBN8=", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitbrace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha1-HtxFng8MVISG7Pn8mfIiE2S5oK4=", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gitbraces/-/braces-3.0.2.tgz", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitcache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/gitchokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/gitclass-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/gitis-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitcollection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitcolor-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gitcolor-name/-/color-name-1.1.4.tgz", + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gitcolors/-/colors-1.4.0.tgz", + "integrity": "sha1-xQSRR51MG9rtLJztMs98fcI2D3g=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/gitcomponent-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/gitconnect/-/connect-3.7.0.tgz", + "integrity": "sha1-XUk0iRDKpeB6AYALAw0MNfIEhPg=", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitcopy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gitcore-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha1-pgQtNjTCsn6TKPg3uWX6yDgI24U=", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/gitcors/-/cors-2.8.5.tgz", + "integrity": "sha1-6sEdpRWS3Ya58G9uesKTs9+HXSk=", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/gitcross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha1-9zqFudXUHQRVUcF34ogtSshXKKY=", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/gitdebug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/gitdecode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-2.0.2.tgz", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gitdepd/-/depd-2.0.0.tgz", + "integrity": "sha1-tpYWPMdXVg0JzyLMj60Vcbeedt8=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gitdestroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/gitduplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha1-Or5DrvODX4rgd9E23c4PJ2sEAOY=", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/giteastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha1-aWzi7Aqg5uqTo5f/zySqeEDIJ8s=", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gitee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/gitemoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha1-hAyIA7DYBH9P8M+WMXazLU7z7XI=", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitencodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gitescape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/gitetag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/gitevent-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/gitexpand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/gitis-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/gitextglob/-/extglob-2.0.4.tgz", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/gitfaye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha1-fw2Sdc/dhqHJY9yLZfzEUe3Lsdo=", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitfile-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=", + "dev": true, + "optional": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gitfill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gitfinalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitfor-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gitforeground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/gitfragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gitfresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/gitfrom/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gitfsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gitfunction-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/gitget-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/gitglob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/gitglob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ=", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/gitgraceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/githas-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/githas-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gitis-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/githasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/githttp-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha1-lFz63WZSHq+PfISRPTd9exXyTjE=", + "dev": true, + "dependencies": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">=4.6.1" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/githttp-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitstatuses/-/statuses-2.0.1.tgz", + "integrity": "sha1-VcsADM8dSHKL0jxoWgY5mM8aG2M=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/githttp-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/gitinherits/-/inherits-2.0.4.tgz", + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=", + "dev": true + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitis-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitis-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/gitis-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "dev": true + }, + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitis-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gitis-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gitis-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gitis-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gitis-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gitis-number/-/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/gitis-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitis-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gitis-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitisarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gitisexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/gitisobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/gitjackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha1-B8BQNKbDSfoG4k+jWqdttFgM5N0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/gitlive-server/-/live-server-1.2.2.tgz", + "integrity": "sha512-t28HXLjITRGoMSrCOv4eZ88viHaBVIjKjdI5PO92Vxlu+twbk6aE0t7dVIaz6ZWkjPilYFV6OSdMYl9ybN2B4w==", + "dev": true, + "dependencies": { + "chokidar": "^2.0.4", + "colors": "1.4.0", + "connect": "^3.6.6", + "cors": "latest", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "latest", + "opn": "latest", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" + }, + "bin": { + "live-server": "live-server.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gitanymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/live-server/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gitnormalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/gitbinary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/gitbraces/-/braces-2.3.2.tgz", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/gitchokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha1-gEs6e2qZNYw8XGHnHYco8EHP+Rc=", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/live-server/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gitfill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/gitfsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha1-8yXLBFVZJCi88Rs4M3DvcOO/zDg=", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/live-server/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gitglob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/live-server/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gitis-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitis-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gitis-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/gitreaddirp/-/readdirp-2.2.1.tgz", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/live-server/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gitto-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/gitlru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/gitmap-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gitmap-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitmap-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/gitmicromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/gitbraces/-/braces-2.3.2.tgz", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gitfill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gitis-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gitto-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/gitmime/-/mime-1.6.0.tgz", + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/gitmime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/gitmime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/gitminimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gitminipass/-/minipass-7.1.0.tgz", + "integrity": "sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/gitmixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY=", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/gitmorgan/-/morgan-1.10.0.tgz", + "integrity": "sha1-CRd4q8H8R801CYJGU9rh+qtrF9c=", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gitms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/gitnan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/gitnanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/gitnegotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gitnormalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/gitobject-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gitobject-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/gitis-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitobject-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/gitobject.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/giton-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/giton-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gitopn/-/opn-6.0.0.tgz", + "integrity": "sha1-PFsNtnbV+X2hIz0e1C0YK8WifS0=", + "deprecated": "The package has been renamed to `open`", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/gitparseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitpascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitpath-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitpath-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gitpath-key/-/path-key-3.1.1.tgz", + "integrity": "sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U=", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/gitpath-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/gitpause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/gitpicomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitposix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/gitprettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/gitprismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitprocess-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=", + "dev": true + }, + "node_modules/proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/gitproxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/gitrange-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/gitreadable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/gitreaddirp/-/readdirp-3.6.0.tgz", + "integrity": "sha1-dKNwvYVxFuJFspzJc0DNQxoCpsc=", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitregex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gitremove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gitrepeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha1-vmgVIIR6tYx1aKx1+/rSjtQtOek=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/gitrepeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/gitresolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/gitret/-/ret-0.1.15.tgz", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/gitrimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/gitsafe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gitsafe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/gitsend/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/gitms/-/ms-2.1.3.tgz", + "integrity": "sha1-V0yBOM4dK1hh8LRFedut1gxmFbI=", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/giton-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitstatuses/-/statuses-2.0.1.tgz", + "integrity": "sha1-VcsADM8dSHKL0jxoWgY5mM8aG2M=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/gitserve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gitdepd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/githttp-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/gitinherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gitsetprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitset-value/-/set-value-2.0.1.tgz", + "integrity": "sha1-oY1AUw5vB95CKMfe/kInr4ytAFs=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gitsetprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha1-ZsmiSnP5/CjL5msJ/tPTPcrxtCQ=", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gitshebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha1-zNCvT4g1+9wmW4JGGq8MNmY/NOo=", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gitshebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha1-rhbxZE2HPsrYQ7AwexQzYtTEIXI=", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gitsignal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/gitsnapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gitsnapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/gitsnapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gitextend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/gitis-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/gitsource-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/gitsource-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha1-GQhmvs51U+H48mei7oLGBrVQmho=", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/gitsource-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha1-CvZmBadFpaL5HPG7+KevvCg97FY=", + "dev": true + }, + "node_modules/spec-md": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gitspec-md/-/spec-md-3.1.0.tgz", + "integrity": "sha512-h4/Pc+ICB+q8cXKTyy8f5JJHSQRNy1oh05P/vTrq+qIV2fLmEaalwpZMRYMzlq/g+qxnIp/hywGGYA3rpefYmA==", + "dev": true, + "dependencies": { + "prismjs": "^1.23.0" + }, + "bin": { + "spec-md": "bin/spec-md" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/gitsplit/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gitsplit-string/-/split-string-3.1.0.tgz", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/gitstatic-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/gitdefine-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/gitis-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/gitstatuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/gitstream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gitstring_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/gitstring-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/gitstring-width/-/string-width-4.2.3.tgz", + "integrity": "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA=", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gitansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gitemoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/gitstrip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gitstrip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/gitstrip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gitansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/gitthrough/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitto-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/gitkind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gitto-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gitto-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gittoidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitunion-value/-/union-value-1.0.1.tgz", + "integrity": "sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc=", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gitis-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gitunix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha1-SRLfrRyK630g+go55MMZGMHV1d0=", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitunpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitunset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/githas-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitisobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/githas-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gitupath/-/upath-1.2.0.tgz", + "integrity": "sha1-j2bbzVWog6za5ECK+LA1pQRMGJQ=", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/giturix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gituse/-/use-3.1.1.tgz", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gitutil-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gitutils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/gituuid/-/uuid-3.4.0.tgz", + "integrity": "sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gitvary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/gitwebsocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha1-ia1Slbv2S0gKvLox5JU6ynBvV2A=", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/gitwebsocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha1-f4RzvIOd/YdgituV1+sHUhFXikI=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gitwhich/-/which-2.0.2.tgz", + "integrity": "sha1-fGqN0KY2oDJ+ELWckobu6T8/UbE=", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/gitwrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gitwrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM=", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gitansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gitansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gitemoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/gitstring-width/-/string-width-4.2.3.tgz", + "integrity": "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA=", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/gitstrip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee12bd3 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "graphql-multipart-request-spec", + "private": true, + "scripts": { + "build": "node scripts/build.js", + "dev": "node scripts/build.js --watch", + "release": "npm run build", + "publish": "npm run release && ./scripts/publish.sh", + "clean": "rimraf build" + }, + "type": "module", + "devDependencies": { + "chokidar": "^3.6.0", + "live-server": "^1.2.2", + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "spec-md": "^3.1.0" + }, + "prettier": { + "proseWrap": "always", + "trailingComma": "none" + } +} diff --git a/readme.md b/readme.md index 54067a3..9cfa3c3 100644 --- a/readme.md +++ b/readme.md @@ -1,259 +1,25 @@ -# GraphQL multipart request specification +# GraphQL Multipart Request Spec -An interoperable [multipart form](https://tools.ietf.org/html/rfc7578) field structure for GraphQL requests, used by various file upload client/server implementations. +This specification describes how to attach additional files to a GraphQL request using `multipart/form-data`. The intent +is to be the continuation of https://github.com/jaydenseric/graphql-multipart-request-spec starting at Version 3 and to +support backwards compatibility with Version 2 for both clients and servers. -It’s possible to implement: +## Example -- Nesting files anywhere within operations (usually in `variables`). -- Operation batching. -- File deduplication. -- File upload streams in resolvers. -- Aborting file uploads in resolvers. +```http +POST https://example.com/graphql +content-type: multipart/form-data; boundary=--boundary -![Sync vs async GraphQL multipart request middleware](sync-vs-async-graphql-multipart-request-middleware.svg) - -## Multipart form field structure - -An “operations object” is an [Apollo GraphQL POST request](https://www.apollographql.com/docs/apollo-server/workflow/requests/#post-requests) (or array of requests if batching). An “operations path” is an [`object-path`](https://npm.im/object-path) string to locate a file within an operations object. - -So operations can be resolved while the files are still uploading, the fields are ordered: - -1. `operations`: A JSON encoded operations object with files replaced with `null`. -2. `map`: A JSON encoded map of where files occurred in the operations. For each file, the key is the file multipart form field name and the value is an array of operations paths. -3. File fields: Each file extracted from the operations object with a unique, arbitrary field name. - -## Examples - -### Single file - -#### Operations - -```js -{ - query: ` - mutation($file: Upload!) { - singleUpload(file: $file) { - id - } - } - `, - variables: { - file: File // a.txt - } -} -``` - -#### cURL request - -```shell -curl localhost:3001/graphql \ - -F operations='{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }' \ - -F map='{ "0": ["variables.file"] }' \ - -F 0=@a.txt -``` - -#### Request payload - -``` ---------------------------cec8e8123c05ba25 -Content-Disposition: form-data; name="operations" - -{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } } ---------------------------cec8e8123c05ba25 -Content-Disposition: form-data; name="map" - -{ "0": ["variables.file"] } ---------------------------cec8e8123c05ba25 -Content-Disposition: form-data; name="0"; filename="a.txt" -Content-Type: text/plain - -Alpha file content. - ---------------------------cec8e8123c05ba25-- -``` - -### File list - -#### Operations - -```js -{ - query: ` - mutation($files: [Upload!]!) { - multipleUpload(files: $files) { - id - } - } - `, - variables: { - files: [ - File, // b.txt - File // c.txt - ] - } -} -``` - -#### cURL request - -```shell -curl localhost:3001/graphql \ - -F operations='{ "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }' \ - -F map='{ "0": ["variables.files.0"], "1": ["variables.files.1"] }' \ - -F 0=@b.txt \ - -F 1=@c.txt -``` - -#### Request payload - -``` ---------------------------ec62457de6331cad -Content-Disposition: form-data; name="operations" - -{ "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } } ---------------------------ec62457de6331cad -Content-Disposition: form-data; name="map" - -{ "0": ["variables.files.0"], "1": ["variables.files.1"] } ---------------------------ec62457de6331cad -Content-Disposition: form-data; name="0"; filename="b.txt" -Content-Type: text/plain - -Bravo file content. - ---------------------------ec62457de6331cad -Content-Disposition: form-data; name="1"; filename="c.txt" -Content-Type: text/plain - -Charlie file content. - ---------------------------ec62457de6331cad-- -``` - -### Batching - -#### Operations - -```js -[ - { - query: ` - mutation($file: Upload!) { - singleUpload(file: $file) { - id - } - } - `, - variables: { - file: File, // a.txt - }, - }, - { - query: ` - mutation($files: [Upload!]!) { - multipleUpload(files: $files) { - id - } - } - `, - variables: { - files: [ - File, // b.txt - File, // c.txt - ], - }, - }, -]; -``` - -#### cURL request - -```shell -curl localhost:3001/graphql \ - -F operations='[{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }]' \ - -F map='{ "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] }' \ - -F 0=@a.txt \ - -F 1=@b.txt \ - -F 2=@c.txt -``` - -#### Request payload - -``` ---------------------------627436eaefdbc285 +--boundary Content-Disposition: form-data; name="operations" -[{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }] ---------------------------627436eaefdbc285 -Content-Disposition: form-data; name="map" +{ "query": "mutation { upload(file: \"fileA\") }" } -{ "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] } ---------------------------627436eaefdbc285 -Content-Disposition: form-data; name="0"; filename="a.txt" +--boundary +Content-Disposition: form-data; name="fileA"; filename="a.txt" Content-Type: text/plain Alpha file content. ---------------------------627436eaefdbc285 -Content-Disposition: form-data; name="1"; filename="b.txt" -Content-Type: text/plain - -Bravo file content. - ---------------------------627436eaefdbc285 -Content-Disposition: form-data; name="2"; filename="c.txt" -Content-Type: text/plain - -Charlie file content. - ---------------------------627436eaefdbc285-- +--boundary-- ``` - -## Security - -GraphQL server authentication and security mechanisms are beyond the scope of this specification, which only covers a multipart form field structure for GraphQL requests. - -Note that a GraphQL multipart request has the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) `multipart/form-data`; if a browser making such a request determines it meets the criteria for a “[simple request](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests)” as defined in the [Fetch specification](https://fetch.spec.whatwg.org) for the [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) protocol, it won’t cause a [CORS preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request). GraphQL server authentication and security mechanisms must consider this to prevent [Cross-Site Request Forgery](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) (CSRF) attacks. - -## Implementations - -Pull requests adding either experimental or mature implementations to these lists are welcome! ~~Strikethrough~~ means the project was renamed, deprecated, or no longer supports this spec out of the box (but might via an optional integration). - -### Client - -- [jaydenseric/graphql-react](https://github.com/jaydenseric/graphql-react) (JS: [npm](https://npm.im/graphql-react)) -- [jaydenseric/apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) (JS: [npm](https://npm.im/apollo-upload-client)) -- [jaydenseric/extract-files](https://github.com/jaydenseric/extract-files) (JS: [npm](https://npm.im/extract-files)) -- [nearform/graphql-hooks](https://github.com/nearform/graphql-hooks) (JS: [npm](https://npm.im/graphql-hooks)) -- [klis87/redux-saga-requests-graphql](https://github.com/klis87/redux-saga-requests/tree/master/packages/redux-saga-requests-graphql) (JS: [npm](https://npm.im/redux-saga-requests-graphql)) -- [imolorhe/altair](https://github.com/imolorhe/altair) (JS: [npm](https://npm.im/altair-static)) -- [haffdata/buoy](https://github.com/haffdata/buoy) (JS: [npm](https://npm.im/@buoy/client)) -- [FormidableLabs/urql](https://github.com/FormidableLabs/urql) (JS: [npm](https://npm.im/@urql/exchange-multipart-fetch)) -- [~~apollo-fetch-upload~~](https://github.com/apollographql/apollo-fetch/tree/master/packages/apollo-fetch-upload) (JS: [npm](https://npm.im/apollo-fetch-upload)) -- [apollographql/apollo-ios](https://github.com/apollographql/apollo-ios) (Swift: [CocoaPods](https://cocoapods.org/pods/Apollo)) -- [apollographql/apollo-android](https://github.com/apollographql/apollo-android) (Java: [Bintray](https://bintray.com/apollographql/android)) -- [zino-app/graphql-flutter](https://github.com/zino-app/graphql-flutter) (Dart: [Pub](https://pub.dev/packages/graphql)) -- [samirelanduk/kirjava](https://github.com/samirelanduk/kirjava) (Python: [PyPi](https://pypi.org/project/kirjava)) -- [DoctorJohn/aiogqlc](https://github.com/DoctorJohn/aiogqlc) (Python: [PyPi](https://pypi.org/project/aiogqlc)) -- [graphql-python/gql](https://github.com/graphql-python/gql) (Python: [PyPi](https://pypi.org/project/gql)) - -### Server - -- [jaydenseric/graphql-upload](https://github.com/jaydenseric/graphql-upload) (JS: [npm](https://npm.im/graphql-upload)) -- [koresar/graphql-upload-minimal](https://github.com/koresar/graphql-upload-minimal) (JS: [npm](https://npm.im/graphql-upload-minimal)) -- [dotansimha/graphql-yoga](https://github.com/dotansimha/graphql-yoga) (JS: [npm](https://npm.im/@graphql-yoga/common)) -- [~~apollographql/apollo-server~~](https://github.com/apollographql/apollo-server) (JS: [npm](https://npm.im/apollo-server)) -- [~~jaydenseric/apollo-upload-server~~](https://github.com/jaydenseric/apollo-upload-server) (JS: [npm](https://npm.im/apollo-upload-server)) -- [99designs/gqlgen](https://github.com/99designs/gqlgen) (Go: [GitHub](https://github.com/99designs/gqlgen)) -- [jpascal/graphql-upload](https://github.com/jpascal/graphql-upload) (Go: [GitHub](https://github.com/jpascal/graphql-upload)) -- [jetruby/apollo_upload_server-ruby](https://github.com/jetruby/apollo_upload_server-ruby) (Ruby: [Gem](https://rubygems.org/gems/apollo_upload_server)) -- [Ecodev/graphql-upload](https://github.com/Ecodev/graphql-upload) (PHP: [Composer](https://packagist.org/packages/ecodev/graphql-upload)) -- [rebing/graphql-laravel](https://github.com/rebing/graphql-laravel) (PHP: [Composer](https://packagist.org/packages/rebing/graphql-laravel)) -- [nuwave/lighthouse](https://github.com/nuwave/lighthouse) (PHP: [Composer](https://packagist.org/packages/nuwave/lighthouse)) -- [overblog/graphql-bundle](https://github.com/overblog/GraphQLBundle) (PHP: [Composer](https://packagist.org/packages/overblog/graphql-bundle)) -- [infinityloop-dev/graphpinator](https://github.com/infinityloop-dev/graphpinator) (PHP: [Composer](https://packagist.org/packages/infinityloop-dev/graphpinator)) -- [lmcgartland/graphene-file-upload](https://github.com/lmcgartland/graphene-file-upload) (Python: [PyPi](https://pypi.org/project/graphene-file-upload)) -- [strawberry-graphql/strawberry](https://github.com/strawberry-graphql/strawberry) (Python: [PyPi](https://pypi.org/project/strawberry-graphql)) -- [graphql-java-kickstart/graphql-java-servlet](https://github.com/graphql-java-kickstart/graphql-java-servlet) (Java: [Maven](https://mvnrepository.com/artifact/com.graphql-java/graphql-java-servlet)) -- [ChilliCream/hotchocolate](https://github.com/ChilliCream/hotchocolate) (C#: [NuGet](https://www.nuget.org/packages/HotChocolate)) -- [async-graphql/async-graphql](https://github.com/async-graphql/async-graphql) (Rust: [Crates](https://crates.io/crates/async-graphql)) diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..17578af --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,181 @@ +import { existsSync, mkdirSync } from "node:fs"; +import { readdir, readFile, writeFile } from "node:fs/promises"; +import path from "path"; +import process from "process"; +import { fileURLToPath } from "url"; + +import chokidar from "chokidar"; +import liveServer from "live-server"; +import specmd from "spec-md"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const rootFolder = path.resolve(__dirname, ".."); +const specFolder = path.resolve(rootFolder, "spec"); +const buildFolder = path.resolve(rootFolder, "build"); + +const metadataPath = path.resolve(specFolder, "metadata.json"); + +let metadata = readMetadata(); +let specPaths = refreshSpecPaths(); + +// make sure our build folder exists +if (!existsSync(buildFolder)) { + mkdirSync(buildFolder); +} + +// run a full build +(async function() { + await Promise.all((await specPaths).map(async specPath => await buildSpec(specPath, await metadata))); +})().catch((error) => logError("Full Build", error)); + +// start our watcher +if (process.argv.length > 1 && process.argv.reduce((acc, argv) => acc || argv === "--watch" || argv === "-w", false)) { + chokidar.watch(specFolder, { ignoreInitial: true }) + .on("all", (event, eventPath) => { + switch (path.extname(eventPath)) { + case ".md": + (async function() { + process.stderr.write(`Change Detected: ${path.relative(rootFolder, eventPath)} @ ${event}\n`); + if (event === "add" || event === "unlink") { + process.stderr.write("Refreshing Specs\n"); + specPaths = refreshSpecPaths(); + } + if (event !== "unlink") { + await buildSpec(eventPath, await metadata); + } + })().catch((error) => { + logError(`${path.relative(rootFolder, eventPath)}:${event}`, error); + }); + break; + case ".json": + (async function() { + process.stderr.write(`Change Detected: ${path.relative(rootFolder, eventPath)} @ ${event}\n`); + metadata = readMetadata(); + await Promise.all( + (await specPaths) + .map(async specPath => await buildSpec(specPath, await metadata)) + ); + })().catch((error) => { + logError(`${path.relative(rootFolder, eventPath)}:${event}`, error); + }); + break; + } + }); + + liveServer.start({ + host: "localhost", + root: buildFolder, + open: false, + logLevel: 2 + }); +} + +async function readMetadata() { + return JSON.parse(await readFile(metadataPath)); +} + +async function refreshSpecPaths() { + const specPaths = (await readdir(specFolder)) + .filter(file => path.extname(file) === ".md") + .reduce((results, specFile) => { + const version = /v(\d+)/.exec(specFile); + if (version != null) { + results.push({ + version: parseInt(version[1]), + specPath: path.resolve(specFolder, specFile), + basename: path.basename(specFile, ".md") + }); + } + return results; + }, []) + .sort(({ version: a }, { version: b }) => b - a); + + let latest = true; + const specsTable = specPaths.map(({ version, basename }) => { + const row = ` + ${(latest) ? "Latest Release" : ""} + Version ${version} + `; + latest = false; + return row; + }).join("\n"); + + const indexHtml = ` + + GraphQL Multipart Request Specification Versions + + + +

GraphQL Multipart Request

+ + ${specsTable} +
+ + +`; + + const indexPath = path.resolve(buildFolder, "index.html"); + await writeFile(indexPath, indexHtml); + process.stderr.write(`Built ${path.relative(rootFolder, indexPath)}\n`); + + return specPaths + .map(({ specPath }) => specPath); +} + +async function buildSpec(specPath, options) { + try { + const outputPath = path.resolve(buildFolder, `${path.basename(specPath, ".md")}.html`); + const result = specmd.html(specPath, options); + await writeFile(outputPath, result); + process.stderr.write(`Built ${path.relative(rootFolder, specPath)} -> ${path.relative(rootFolder, outputPath)}\n`); + + return Promise.resolve(); + } catch (error) { + return Promise.reject(error); + } +} + +function logError(context, error) { + process.stderr.write(`${context}\n`); + process.stderr.write(error.location ? error.message : (error.stack || error) + "\n"); +} + diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755 index 0000000..7670ec9 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -euo pipefail + +BASEDIR=$(cd $(dirname $0); pwd) +REPO_DIR=$(cd ${BASEDIR}/..; pwd) + +echo "====> deploying to github" + +PAGES_DIR=$(mktemp -d) +git worktree add -f ${PAGES_DIR} gh-pages +rm -rf ${PAGES_DIR}/* + +# copy the build files to the gh-pages folder +cp -rp ${REPO_DIR}/build/* ${PAGES_DIR} + +cd ${PAGES_DIR} +git add -A +git commit -m "deployed on $(date) by ${USER}" +git push origin gh-pages +git worktree remove ${PAGES_DIR} diff --git a/spec/graphql-multipart-request-v3.md b/spec/graphql-multipart-request-v3.md new file mode 100644 index 0000000..3a06be3 --- /dev/null +++ b/spec/graphql-multipart-request-v3.md @@ -0,0 +1,783 @@ +# GraphQL Multipart Request V3 + +# GraphQL Multipart Request + +## Overview + +This specification describes how a [GraphQL Over HTTP](https://graphql.github.io/graphql-over-http/draft) compliant +{Server} and {Client} can support passing additional data alongside a GraphQL request using *multipart/form-data*. + +Note: The traditional usage is to upload files as part of a GraphQL request and reference the uploaded file during the +GraphQL resolution. + +```http example +POST https://example.com/graphql +content-type: multipart/form-data; boundary=--boundary + +--boundary +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(file: \"fileA\") }" } + +--boundary +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--boundary-- +``` + +## Conformance + +A conforming implementation of GraphQL Multipart Request must fulfill all normative +requirements. Conformance requirements are described in this document via both +descriptive assertions and key words with clearly defined meanings. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", " +RECOMMENDED", "MAY", and "OPTIONAL" in the normative portions of this document are to be interpreted +as described in [IETF RFC 2119](https://tools.ietf.org/html/rfc2119). These key words may appear +in lowercase and still retain their meaning unless explicitly declared as non-normative. + +A conforming implementation of Multipart Request may provide additional +functionality, but must not where explicitly disallowed or would otherwise +result in non-conformance. + +## Non-Normative Portions + +All contents of this document are normative except portions explicitly declared +as non-normative. + +Examples in this document are non-normative, and are presented to aid +understanding of introduced concepts and the behavior of normative portions of +the specification. Examples are either introduced explicitly in prose (e.g. "for +example") or are set apart in example or counter-example blocks, like this: + +```example +This is an example of a non-normative example. +``` + +```counter-example +This is an example of a non-normative counter-example. +``` + +Notes in this document are non-normative, and are presented to clarify intent, +draw attention to potential edge-cases and pit-falls, and answer common +questions that arise during implementation. Notes are either introduced +explicitly in prose (e.g. "Note: ") or are set apart in a note block, like this: + +Note: This is an example of a non-normative note. + +# multipart/form-data Format + +:: *multipart/form-data* is defined by [RFC7578](https://datatracker.ietf.org/doc/html/rfc7578) + +## Parts + +A GraphQL {MultipartRequest} is made up of multiple {Part}s. These {Part}s MAY be arranged in any order, +however a {Client} SHOULD send the *operations part* first for optimal performance. The {Server} MUST +support receiving the parts in any order. + +Note: When the *operations part* is first received the {Server} can start processing the GraphQL Request before the +entire payload has been uploaded. + +### Operations Part + +operations part +: A {Part} with the {Name} `operations`. This part includes the GraphQL payload as defined in **GraphQL Over HTTP**. +: There MUST be exactly one *operations part*. + +```http example +POST https://example.com/graphql +content-type: multipart/form-data; boundary=--boundary + +--boundary +Content-Disposition: form-data; name="operations" + +{ "query": "query { field }" } +--boundary-- +``` + +### Embedded Parts + +embedded part +: There MAY be zero or more {Part}s in our *multipart/form-data* request which include additional data. +: These {Part}s MUST not have the name `operations`. + +Note: Supplying a *filename* for the *content-disposition* in an *embedded part* is optional. However, the {Server} MAY +choose to reject requests based on the presence of a *filename*. + +```http example +POST https://example.com/graphql +content-type: multipart/form-data; boundary=--boundary + +--boundary +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(file: \"fileA\") upload(file: \"fileB\") }" } + +--boundary +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--boundary +Content-Disposition: form-data; name="fileB" +Content-Type: text/plain + +Beta file content. + +--boundary-- +``` + +### Map Part + +map part +: There MAY be a single {Part} with the name `map`. +: A server implementing only V3 of this specification MUST ignore this `map` {Part}. + +Note: The *map part* is a legacy {Part} from V2 of the specification. If the {Server} only implements V3 of this +specification it ignores the *map part* so that clients can support sending +[backwards compatible](#sec-Backwards-Compatability) V2/V3 requests. + +# Schema + +There are no requirements on the GraphQL Schema by this specification. + +Note: The {Server} MAY choose to use a GraphQL `scalar` type to reference the *embedded part*s. The {Server} MAY have +multiple different `scalar`s with different meanings in the Schema. + +```graphql example +scalar Upload +scalar File +scalar Part +``` + +# Execution + +ExecuteRequest(httpRequest) : + +1. If the {Server} finds that {httpRequest} is a *multipart/form-data* request: + * Return {ExecuteMultipartRequest(httpRequest)} +2. Else + * Return {ExecuteGraphQLOverHTTPRequest(httpRequest)} + +ExecuteMultipartRequest(multipartRequest) : + +1. Let {partsMap} be the result of parsing the {multipartRequest} into a map of {Name} -> {Part} +2. Let {embeddedPartsMap} be an empty Map. +3. For each {partsMap} entry {partName} -> {partValue} + * If {partName} is `operations` + * Return {ProcessGraphQLRequest(partValue, embeddedPartsMap)} + * Else If {partName} is `map` + * If this server supports V2 via backwards compatability + * The {Server} must handle {multipartRequest} according + to [V2 of this spec](https://github.com/jaydenseric/graphql-multipart-request-spec) + * Else ignore this {Part} + * Else Store the entry {partName} -> {partValue} in {embeddedPartsMap} + +ProcessGraphQLRequest(query, embeddedPartsMap) : + +1. Process this according to {ExecuteGraphQLRequest(query)} +2. If the {Server} finds a piece of the GraphQL request that expects additional data it must pull this additional data + from the {embeddedPartsMap} as referenced by the {Name} and provide it to the internal resolver. + * If the {Server} encounters an error when looking up the additional data in the {embeddedPartsMap} it MUST bubble + up this error from the location in the GraphQL query that requested it. + +Note: {ProcessGraphQLRequest()} depends on async data being pulled from {embeddedPartsMap}. The {Server} shouldn't +fail if a required *embedded part* isn't available until the entire {multipartRequest} has been read. + +Note: This specification doesn't put any specific requirements on how the server knows that additional data is required +or what key to use to look this up. However, as mentioned, the expected flow is that a special `scalar` is created where +the client can set a key to reference the {Name} of one of the *embedded part*s. + +```graphql example +scalar Attachment + +type Mutation { + upload(attachment: Attachment): Boolean +} +``` + +```http example +POST https://example.com/graphql +content-type: multipart/form-data; boundary=--boundary + +--boundary +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(attachment: \"fileA\") }" } + +--boundary +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--boundary-- +``` + +## Error Handling + +Note: if the {Client}'s GraphQL request doesn't reference any *embedded part*s this isn't an error. + +### Missing Operations Part + +If the {Client} sends a *multipart/form-data* request without an *operations part*, the {Server} MUST respond according +to the GraphQL Over HTTP specification when receiving an invalid GraphQL request. + +**Request** + +```http counter-example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25-- +``` + +**Response** + +```json example +{ + "errors": [ + { + "message": "Missing GraphQL Operation" + } + ] +} +``` + +### Missing Embedded Parts + +The request is invalid if it is missing referenced *embedded part*s. This can happen when the {Client} sends a non +*multipart/form-data* request or sends a *multipart/form-data* request missing referenced *embedded part*s. The {Server} +MUST handle these errors as outlined in the GraphQL Spec: {HandleFieldError()}. + +**Request** + +```http counter-example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(file: \"fileA\") }" } + +--------------------------cec8e8123c05ba25 +``` + +**Response** + +```json example +{ + "data": { + "upload": null + }, + "errors": [ + { + "message": "Missing fileA", + "location": [ + { + "line": 1, + "column": 37 + } + ], + "path": [ + "upload" + ] + } + ] +} +``` + +### Duplicate Embedded Parts + +If the {Client} sends a *multipart/form-data* request with duplicate `name`s the {Server} MUST return an error for the +request. + +Note: The {Client} MAY send multiple files with the same `filename` + +**Invalid Request** + +```http counter-example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(file: \"fileA\") }" } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content Again. + +--------------------------cec8e8123c05ba25-- +``` + +**Response** + +```json example +{ + "errors": [ + { + "message": "Found duplicate parts: fileA" + } + ] +} +``` + +**Valid Request** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(file: \"fileA\") }" } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileB"; filename="a.txt" +Content-Type: text/plain + +Alpha file content Again. + +--------------------------cec8e8123c05ba25-- +``` + +# Backwards Compatability + +With [V2 of this spec](https://github.com/jaydenseric/graphql-multipart-request-spec) + +## Client Backwards Compatibility + +V3 {Client}s MAY choose to make requests that are compatible with both V2 AND V3 of this specification. + +A cross compatible request is a V2 compliant request where instead of using `null` for additional data. The *embedded +part* +name is used instead. + +* V2 Servers will overwrite the *embedded part* name when processing the *map part* +* V3 Servers will ignore the *map part* and process the request like normal. + +Note: This allows the V3 {Client} to not worry about the supported specification version of the {Server} as both V2 and +V3 {Server}s will be able to handle the request. + +**GraphQL Request** + +```graphql example +mutation($file: Upload!) { + upload(file: $file) +} +``` + +**Variables** + +```json example +{ + "file": "fileA" +} +``` + +**Map Part** + +```json example +{ + "fileA": [ + "variables.file" + ] +} +``` + +Note: In the above example, note that the key in the *map part* SHOULD be the same as the name of the *embedded part* +that contains the relevant additional data for clarity. + +## Server Backwards Compatibility + +V3 {Server}s MAY choose to support V2 requests of this specification. If a backwards compatible V3 {Server} receives +a request with a *map part* it must implement the execution flow defined +by [V2 of this spec](https://github.com/jaydenseric/graphql-multipart-request-spec). The file key's values MUST be +ignored and substituted sequentially with the contents of the *map part*. If no *map part* is present, it MUST implement +the flow described in {ExecuteMultipartRequest()}. + +# Examples + +**Schema** + +This is the schema for all of the examples in this section. + +```graphql example +scalar Upload + +type Mutation { + upload(file: Upload!): String +} + +``` + +## Single File + +**GraphQL Request** + +```graphql example +mutation { + upload(file: "fileA") +} +``` + +**Payload** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { upload(file: \"fileA\") }" } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25-- +``` + +**cURL Request** + +```shell example +curl localhost:3001/graphql \ + -F operations='{ "query": "mutation { upload(file: \"fileA\") }" }' \ + -F fileA=@a.txt +``` + +## Multiple Files + +**GraphQL Request** + +```graphql example +mutation { + a: upload(file: "fileA") + b: upload(file: "fileB") +} +``` + +**Payload** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation { a: upload(file: \"fileA\") b: upload(file: \"fileB\") }" } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileB"; filename="b.mpg" +Content-Type: video/mpeg + +Beta file content. + +--------------------------cec8e8123c05ba25-- +``` + +**cURL Request** + +```shell example +curl localhost:3001/graphql \ + -F operations='{ "query": "mutation { a: upload(file: \"fileA\") b: upload(file: \"fileB\") }" }' \ + -F fileA=@a.txt \ + -F fileB=@b.mpg +``` + +## Variables And File Reuse + +**GraphQL Request** + +```graphql example +mutation($file: Upload!) { + a: upload(file: $file) + b: upload(file: $file) +} +``` + +**Variables** + +```json example +{ + "file": "fileA" +} +``` + +**Payload** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation($file: Upload!) { a: upload(file: $file) b: upload(file: $file) }", "variables": { "file": "fileA" } } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25-- +``` + +**cURL Request** + +```shell example +curl localhost:3001/graphql \ + -F operations='{ "query": "mutation($file: Upload!) { a: upload(file: $file) b: upload(file: $file) }", "variables": { "file": "fileA" } }' \ + -F fileA=@a.txt +``` + +## V2 Client -> V3 Backwards Compatible Server + +The Backwards Compatible V3 {Server} will find the `map` {Part} and use it to replace the `null` value in +the `operations` json before executing the request via its standard flow. + +**GraphQL Request** + +```graphql example +mutation($file: Upload!) { + upload(file: $file) +} +``` + +**Variables** + +```json example +{ + "file": null +} +``` + +**Map Part** + +```json example +{ + "fileA": [ + "variables.file" + ] +} +``` + +**Payload** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation($file: Upload!) { upload(file: $file) }", "variables": { "file": null } } + +Content-Disposition: form-data; name="map" + +{ "fileA": ["variables.file"] } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25-- +``` + +**cURL Request** + +```shell example +curl localhost:3001/graphql \ + -F operations='{ "query": "mutation($file: Upload!) { upload(file: $file) }", "variables": { "file": null } }' \ + -F map='{ "fileA": ["variables.file"] }' \ + -F fileA=@a.txt +``` + +## V3 Backwards Compatible Client -> V2 Server + +The V3 {Client} has already filled in the value for the `file` variable in the *operations part* json. The V2 +{Server} will ignore this and still pull the value from the *map part*. + +**GraphQL Request** + +```graphql example +mutation($file: Upload!) { + upload(file: $file) +} +``` + +**Variables** + +```json example +{ + "file": "fileA" +} +``` + +**Map Part** + +```json example +{ + "fileA": [ + "variables.file" + ] +} +``` + +**Payload** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation($file: Upload!) { upload(file: $file) }", "variables": { "file": "fileA" } } + +Content-Disposition: form-data; name="map" + +{ "fileA": ["variables.file"] } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25-- +``` + +**cURL Request** + +```shell example +curl localhost:3001/graphql \ + -F operations='{ "query": "mutation($file: Upload!) { upload(file: $file) }", "variables": { "file": "fileA" } }' \ + -F map='{ "fileA": ["variables.file"] }' \ + -F fileA=@a.txt +``` + +## V3 Backwards Compatible Client -> Non Backwards Compatible V3 Server + +The V3 {Server} will ignore the *map part* however the client has already filled in the `fileA` key in +the `variables` json. The V3 {Server} will use this value. + +**GraphQL Request** + +```graphql example +mutation($file: Upload!) { + upload(file: $file) +} +``` + +**Variables** + +```json example +{ + "file": "fileA" +} +``` + +**Map Part** + +```json example +{ + "fileA": [ + "variables.file" + ] +} +``` + +**Payload** + +```http example +POST /graphql HTTP/1.1 +Host: localhost:3001 +content-type: multipart/form-data; boundary=------------------------cec8e8123c05ba25 +content-length: ? + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="operations" + +{ "query": "mutation($file: Upload!) { upload(file: $file) }", "variables": { "file": "fileA" } } + +Content-Disposition: form-data; name="map" + +{ "fileA": ["variables.file"] } + +--------------------------cec8e8123c05ba25 +Content-Disposition: form-data; name="fileA"; filename="a.txt" +Content-Type: text/plain + +Alpha file content. + +--------------------------cec8e8123c05ba25-- +``` + +**cURL Request** + +```shell example +curl localhost:3001/graphql \ + -F operations='{ "query": "mutation($file: Upload!) { upload(file: $file) }", "variables": { "file": "fileA" } }' \ + -F map='{ "fileA": ["variables.file"] }' \ + -F fileA=@a.txt +``` diff --git a/spec/metadata.json b/spec/metadata.json new file mode 100644 index 0000000..3b5f7ed --- /dev/null +++ b/spec/metadata.json @@ -0,0 +1,17 @@ +{ + "biblio": { + "https://spec.graphql.org/draft/": { + "ExecuteGraphQLRequest()": "#sec-Execution", + "HandleFieldError()": "#sec-Handling-Field-Errors" + }, + "https://graphql.github.io/graphql-over-http/draft": { + "Server": "#sec-Overview", + "Client": "#sec-Overview", + "ExecuteGraphQLOverHTTPRequest()": "#sec-Execution" + }, + "https://datatracker.ietf.org/doc/html/rfc7578": { + "Part": "#section-4", + "Name": "#section-4.2" + } + } +} diff --git a/sync-vs-async-graphql-multipart-request-middleware.sketch b/sync-vs-async-graphql-multipart-request-middleware.sketch deleted file mode 100644 index 3ed8d3c..0000000 Binary files a/sync-vs-async-graphql-multipart-request-middleware.sketch and /dev/null differ diff --git a/sync-vs-async-graphql-multipart-request-middleware.svg b/sync-vs-async-graphql-multipart-request-middleware.svg deleted file mode 100644 index 96becab..0000000 --- a/sync-vs-async-graphql-multipart-request-middleware.svg +++ /dev/null @@ -1,85 +0,0 @@ - - Sync vs async GraphQL multipart request middleware - - - - GraphQL API upload - - - - Cloud storage upload - - - - - - 1mb.png - - - - - - 2mb.png - - - - - - 3mb.png - - - - Response - - - Receive - request - - - Run - resolver - - - Sync: - Store files temporarily on API server (filesystem or memory). - - - - - - 1mb.png - - - - - - 2mb.png - - - - - - 3mb.png - - - - Receive - request - - - Response - - - Run - resolver - - - Async: - Pass file upload streams into resolver. - - - Sync vs async GraphQL - multipart request middleware - - -