diff --git a/CODEOWNERS b/CODEOWNERS index b1a5c843deaf..06834a6aebd3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -316,6 +316,13 @@ # - Standby owner(s): n/a /extensions/typeorm @hacksparrow +# GraphQL +# +# - Issue label: GraphQL +# - Primary owner(s): @raymondfeng +/extensions/graphql @raymondfeng +/examples/graphql @raymondfeng + # # Build & internal tooling # diff --git a/docs/site/GraphQL.md b/docs/site/GraphQL.md new file mode 100644 index 000000000000..50d11149daa2 --- /dev/null +++ b/docs/site/GraphQL.md @@ -0,0 +1,10 @@ +--- +lang: en +title: 'GraphQL' +keywords: LoopBack 4.0, LoopBack 4, Node.js, TypeScript, GraphQL +layout: readme +source: loopback-next +file: extensions/graphql/README.md +sidebar: lb4_sidebar +permalink: /doc/en/lb4/GraphQL.html +--- diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index 03fe3ffcda3b..3a3d6c442cf3 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -21,6 +21,7 @@ one in the monorepo: `npm run update-monorepo-file` | [examples/context](https://github.com/strongloop/loopback-next/tree/master/examples/context) | @loopback/example-context | Standalone examples to illustrate features provided by @loopback/context | | [examples/express-composition](https://github.com/strongloop/loopback-next/tree/master/examples/express-composition) | @loopback/example-express-composition | LoopBack 4 REST API on Express | | [examples/file-transfer](https://github.com/strongloop/loopback-next/tree/master/examples/file-transfer) | @loopback/example-file-transfer | Example application for file upload/download with LoopBack 4 | +| [examples/graphql](https://github.com/strongloop/loopback-next/tree/master/examples/graphql) | @loopback/example-graphql | GraphQL Example Application | | [examples/greeter-extension](https://github.com/strongloop/loopback-next/tree/master/examples/greeter-extension) | @loopback/example-greeter-extension | An example showing how to implement the extension point/extension pattern using LoopBack 4 | | [examples/greeting-app](https://github.com/strongloop/loopback-next/tree/master/examples/greeting-app) | @loopback/example-greeting-app | An example greeting application for LoopBack 4 | | [examples/hello-world](https://github.com/strongloop/loopback-next/tree/master/examples/hello-world) | @loopback/example-hello-world | A simple hello-world Application using LoopBack 4 | @@ -41,6 +42,7 @@ one in the monorepo: `npm run update-monorepo-file` | [extensions/authentication-passport](https://github.com/strongloop/loopback-next/tree/master/extensions/authentication-passport) | @loopback/authentication-passport | A package creating adapters between the passport module and @loopback/authentication | | [extensions/context-explorer](https://github.com/strongloop/loopback-next/tree/master/extensions/context-explorer) | @loopback/context-explorer | Visualize context hierarchy, bindings, configurations, and dependencies | | [extensions/cron](https://github.com/strongloop/loopback-next/tree/master/extensions/cron) | @loopback/cron | Schedule tasks using cron-like syntax | +| [extensions/graphql](https://github.com/strongloop/loopback-next/tree/master/extensions/graphql) | @loopback/graphql | LoopBack's graphql integration | | [extensions/health](https://github.com/strongloop/loopback-next/tree/master/extensions/health) | @loopback/extension-health | An extension exposes health check related endpoints with LoopBack 4 | | [extensions/logging](https://github.com/strongloop/loopback-next/tree/master/extensions/logging) | @loopback/extension-logging | An extension exposes logging for Winston and Fluentd with LoopBack 4 | | [extensions/metrics](https://github.com/strongloop/loopback-next/tree/master/extensions/metrics) | @loopback/extension-metrics | An extension exposes metrics for Prometheus with LoopBack 4 | diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index 3e250ef17c36..00ff2cdf310d 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -231,10 +231,18 @@ children: - title: 'Creating Other Forms of APIs' output: 'web, pdf' children: + - title: 'Creating GraphQL APIs' + url: GraphQL.html + output: 'web, pdf' + - title: 'Exposing GraphQL APIs' url: exposing-graphql-apis.html output: 'web, pdf' + - title: 'Running cron jobs' + url: Running-cron-jobs.html + output: 'web, pdf' + - title: 'Accessing Databases' output: 'web, pdf' children: diff --git a/examples/graphql/.npmrc b/examples/graphql/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/examples/graphql/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/examples/graphql/LICENSE b/examples/graphql/LICENSE new file mode 100644 index 000000000000..235b91329cbe --- /dev/null +++ b/examples/graphql/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/example-graphql +This project is licensed under the MIT License, full text below. + +-------- + +MIT License + +MIT License Copyright (c) IBM Corp. 2020 + +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 (including the next +paragraph) 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/examples/graphql/README.md b/examples/graphql/README.md new file mode 100644 index 000000000000..e98aa9f341ed --- /dev/null +++ b/examples/graphql/README.md @@ -0,0 +1,75 @@ +# @loopback/example-graphql + +An example application to demonstrate GraphQL integration for LoopBack 4 using +[@loopback/graphql](https://github.com/strongloop/loopback-next/tree/graphql/extensions/graphql). + +## Try it out + +```sh +npm start +``` + +You should see the following messages: + +```sh +Server is running at http://[::1]:3000 +Try http://[::1]:3000/graphql +``` + +Open http://127.0.0.1:3000/graphql in your browser to play with the GraphiQL. + +![graphql-demo](graphql-demo.png) + +1. Copy the query to the right panel: + +```graphql +query GetRecipe1 { + recipe(recipeId: "1") { + title + description + ratings + creationDate + ratingsCount(minRate: 2) + averageRating + ingredients + numberInCollection + } +} +``` + +2. Click on the run icon: + +```json +{ + "data": { + "recipe": { + "title": "Recipe 1", + "description": "Desc 1", + "ratings": [0, 3, 1], + "creationDate": "2018-04-11T00:00:00.000Z", + "ratingsCount": 1, + "averageRating": 1.3333333333333333, + "ingredients": ["one", "two", "three"], + "numberInCollection": 1 + } + } +} +``` + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/examples/graphql/graphql-demo.png b/examples/graphql/graphql-demo.png new file mode 100644 index 000000000000..a050867b1e7b Binary files /dev/null and b/examples/graphql/graphql-demo.png differ diff --git a/examples/graphql/package-lock.json b/examples/graphql/package-lock.json new file mode 100644 index 000000000000..edcbf598dbc4 --- /dev/null +++ b/examples/graphql/package-lock.json @@ -0,0 +1,1012 @@ +{ + "name": "@loopback/example-graphql", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz", + "integrity": "sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "dev": true + }, + "@types/multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-tWsKbF5LYtXrJ7eOfI0aLBgEv9B7fnJe1JRXTj5+Z6EMfX0yHVsRFsNGnKyN8Bs0gtDv+JR37xAqsPnALyVTqg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/node": { + "version": "10.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz", + "integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ==", + "dev": true + }, + "@types/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", + "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "class-transformer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz", + "integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.5.0.tgz", + "integrity": "sha512-vlUP10xse9sWt9SGRtcr1LAC67BENcQMFeV+w5EvLEoFe3xJ8cF1Skd0msziRx/VMC+72B4DxreCE+OR12OA6Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.2.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.2.0.tgz", + "integrity": "sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==", + "dev": true, + "requires": { + "acorn": "^7.3.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tslib": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", + "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/examples/graphql/package.json b/examples/graphql/package.json new file mode 100644 index 000000000000..0ffc9dcbc692 --- /dev/null +++ b/examples/graphql/package.json @@ -0,0 +1,70 @@ +{ + "name": "@loopback/example-graphql", + "version": "0.0.1", + "description": "GraphQL Example Application", + "keywords": [ + "loopback-application", + "loopback" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=10.16" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "acceptance": "lb-mocha \"dist/__tests__/acceptance/**/*.js\"", + "build": "lb-tsc", + "build:watch": "lb-tsc --watch", + "clean": "lb-clean *example-graphql*.tgz dist *.tsbuildinfo package", + "verify": "npm pack && tar xf *example-graphql*.tgz && tree package && npm run clean", + "lint": "npm run prettier:check && npm run eslint", + "lint:fix": "npm run eslint:fix && npm run prettier:fix", + "prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"", + "prettier:check": "npm run prettier:cli -- -l", + "prettier:fix": "npm run prettier:cli -- --write", + "eslint": "lb-eslint --report-unused-disable-directives .", + "eslint:fix": "npm run eslint -- --fix", + "pretest": "npm run clean && npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "test:dev": "lb-mocha dist/__tests__/**/*.js && npm run posttest", + "prestart": "npm run build", + "start": "node ." + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "examples/graphql" + }, + "author": "IBM Corp.", + "license": "MIT", + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "dependencies": { + "@loopback/graphql": "^0.0.1", + "@loopback/boot": "^2.4.0", + "@loopback/core": "^2.9.2", + "@loopback/repository": "^2.10.0", + "@loopback/rest": "^5.2.1", + "tslib": "^2.0.0", + "class-transformer": "^0.2.3" + }, + "devDependencies": { + "@loopback/build": "^6.1.1", + "@loopback/eslint-config": "^8.0.4", + "@loopback/testlab": "^3.2.1", + "@types/multer": "^1.4.3", + "@types/node": "^10.17.27", + "eslint": "^7.5.0", + "typescript": "~3.9.7", + "rimraf": "^3.0.2", + "source-map-support": "^0.5.19" + }, + "copyright.owner": "IBM Corp." +} diff --git a/examples/graphql/public/index.html b/examples/graphql/public/index.html new file mode 100644 index 000000000000..78296cb0e04b --- /dev/null +++ b/examples/graphql/public/index.html @@ -0,0 +1,87 @@ + + + + + GraphQL Test Application + + + + + + + + + + +
+

graphql-test

+

Version 1.0.0

+ +

GraphQL: /graphql

+
+ + + + + diff --git a/examples/graphql/src/__tests__/acceptance/graphql-context.acceptance.ts b/examples/graphql/src/__tests__/acceptance/graphql-context.acceptance.ts new file mode 100644 index 000000000000..f58e551740db --- /dev/null +++ b/examples/graphql/src/__tests__/acceptance/graphql-context.acceptance.ts @@ -0,0 +1,62 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {createBindingFromClass} from '@loopback/core'; +import {GraphQLBindings, GraphQLServer} from '@loopback/graphql'; +import {expect, supertest} from '@loopback/testlab'; +import {RecipesDataSource} from '../../datasources'; +import {RecipeResolver} from '../../graphql-resolvers/recipe-resolver'; +import {RecipeRepository} from '../../repositories'; +import {sampleRecipes} from '../../sample-recipes'; +import {RecipeService} from '../../services/recipe.service'; +import {exampleQuery} from './graphql-tests'; + +describe('GraphQL context', () => { + let server: GraphQLServer; + let repo: RecipeRepository; + + before(givenServer); + after(stopServer); + + it('invokes middleware', async () => { + await supertest(server.httpServer?.url) + .post('/graphql') + .set('content-type', 'application/json') + .accept('application/json') + .send({operationName: 'GetRecipe1', variables: {}, query: exampleQuery}) + .expect(200); + }); + + async function givenServer() { + server = new GraphQLServer({host: '127.0.0.1', port: 0}); + server.resolver(RecipeResolver); + + // Customize the GraphQL context with additional information for test verification + server.bind(GraphQLBindings.GRAPHQL_CONTEXT_RESOLVER).to(ctx => { + return {...ctx, meta: 'loopback'}; + }); + + // Register a GraphQL middleware to verify context resolution + server.middleware((resolverData, next) => { + expect(resolverData.context).to.containEql({meta: 'loopback'}); + return next(); + }); + + server.bind('recipes').to([...sampleRecipes]); + const repoBinding = createBindingFromClass(RecipeRepository); + server.add(repoBinding); + server.add(createBindingFromClass(RecipesDataSource)); + server.add(createBindingFromClass(RecipeService)); + await server.start(); + repo = await server.get(repoBinding.key); + await repo.start(); + } + + async function stopServer() { + if (!server) return; + await server.stop(); + repo.stop(); + } +}); diff --git a/examples/graphql/src/__tests__/acceptance/graphql-middleware.acceptance.ts b/examples/graphql/src/__tests__/acceptance/graphql-middleware.acceptance.ts new file mode 100644 index 000000000000..aea638abe233 --- /dev/null +++ b/examples/graphql/src/__tests__/acceptance/graphql-middleware.acceptance.ts @@ -0,0 +1,96 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {createBindingFromClass} from '@loopback/core'; +import {GraphQLBindings, GraphQLServer} from '@loopback/graphql'; +import {expect, supertest} from '@loopback/testlab'; +import {RecipesDataSource} from '../../datasources'; +import {RecipeResolver} from '../../graphql-resolvers/recipe-resolver'; +import {RecipeRepository} from '../../repositories'; +import {sampleRecipes} from '../../sample-recipes'; +import {RecipeService} from '../../services/recipe.service'; +import {exampleQuery} from './graphql-tests'; + +describe('GraphQL middleware', () => { + let server: GraphQLServer; + let repo: RecipeRepository; + + beforeEach(givenServer); + afterEach(stopServer); + + it('invokes middleware', async () => { + const fieldNamesCapturedByMiddleware: string[] = []; + // Register a GraphQL middleware + server.middleware((resolverData, next) => { + // It's invoked for each field resolver + fieldNamesCapturedByMiddleware.push(resolverData.info.fieldName); + return next(); + }); + + await startServerAndRepo(); + await supertest(server.httpServer?.url) + .post('/graphql') + .set('content-type', 'application/json') + .accept('application/json') + .send({operationName: 'GetRecipe1', variables: {}, query: exampleQuery}) + .expect(200); + expect(fieldNamesCapturedByMiddleware).to.eql([ + // the query + 'recipe', + // field resolvers + 'title', + 'description', + 'ratings', + 'creationDate', + 'ratingsCount', + 'averageRating', + 'ingredients', + 'numberInCollection', + ]); + }); + + it('invokes authChecker', async () => { + const authChecks: string[] = []; + server + .bind(GraphQLBindings.GRAPHQL_AUTH_CHECKER) + .to((resolverData, roles) => { + authChecks.push(`${resolverData.info.fieldName} ${roles}`); + return true; + }); + await startServerAndRepo(); + await supertest(server.httpServer?.url) + .post('/graphql') + .set('content-type', 'application/json') + .accept('application/json') + .send({operationName: 'GetRecipe1', variables: {}, query: exampleQuery}) + .expect(200); + expect(authChecks).to.eql([ + // the query + 'recipe owner', + ]); + }); + + async function givenServer() { + server = new GraphQLServer({host: '127.0.0.1', port: 0}); + server.resolver(RecipeResolver); + server.bind('recipes').to([...sampleRecipes]); + const repoBinding = createBindingFromClass(RecipeRepository); + server.add(repoBinding); + server.add(createBindingFromClass(RecipesDataSource)); + server.add(createBindingFromClass(RecipeService)); + repo = await server.get(repoBinding.key); + } + + async function startServerAndRepo() { + await server.start(); + await repo.start(); + } + + async function stopServer() { + if (!server) return; + await server.stop(); + repo.stop(); + } +}); diff --git a/examples/graphql/src/__tests__/acceptance/graphql-tests.ts b/examples/graphql/src/__tests__/acceptance/graphql-tests.ts new file mode 100644 index 000000000000..e82b3919a9e9 --- /dev/null +++ b/examples/graphql/src/__tests__/acceptance/graphql-tests.ts @@ -0,0 +1,128 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect, supertest} from '@loopback/testlab'; + +export function runTests(getClient: () => supertest.SuperTest) { + it('gets a recipe by id', async () => { + const expectedData = { + data: { + recipe: { + title: 'Recipe 1', + description: 'Desc 1', + ratings: [0, 3, 1], + creationDate: '2018-04-11T00:00:00.000Z', + ratingsCount: 1, + averageRating: 1.3333333333333333, + ingredients: ['one', 'two', 'three'], + numberInCollection: 1, + }, + }, + }; + await getClient() + .post('/graphql') + .set('content-type', 'application/json') + .accept('application/json') + .send({operationName: 'GetRecipe1', variables: {}, query: exampleQuery}) + .expect(200, expectedData); + }); + + it('adds a recipe', async () => { + const res = await getClient() + .post('/graphql') + .set('content-type', 'application/json') + .accept('application/json') + .send({operationName: 'AddRecipe', variables: {}, query: exampleQuery}) + .expect(200); + expect(res.body.data.addRecipe).to.containEql({ + id: '4', + numberInCollection: 4, + }); + }); + + it('gets recipes', async () => { + const expectedRecipes = [ + { + title: 'Recipe 1', + description: 'Desc 1', + creationDate: '2018-04-11T00:00:00.000Z', + averageRating: 1.3333333333333333, + ingredientsLength: 3, + numberInCollection: 1, + }, + { + title: 'Recipe 2', + description: 'Desc 2', + creationDate: '2018-04-15T00:00:00.000Z', + averageRating: 2.5, + ingredientsLength: 3, + numberInCollection: 2, + }, + { + title: 'Recipe 3', + description: null, + creationDate: '2020-05-24T00:00:00.000Z', + averageRating: 4.5, + ingredientsLength: 3, + numberInCollection: 3, + }, + { + title: 'New recipe', + description: 'Simple description', + creationDate: '2020-05-24T00:00:00.000Z', + averageRating: 3, + ingredientsLength: 3, + numberInCollection: 4, + }, + ]; + const res = await getClient() + .post('/graphql') + .set('content-type', 'application/json') + .accept('application/json') + .send({operationName: 'GetRecipes', variables: {}, query: exampleQuery}) + .expect(200); + expect(res.body.data.recipes).to.eql(expectedRecipes); + }); +} + +export const exampleQuery = `query GetRecipe1 { + recipe(recipeId: "1") { + title + description + ratings + creationDate + ratingsCount(minRate: 2) + averageRating + ingredients + numberInCollection + } +} + +query GetRecipes { + recipes { + title + description + creationDate + averageRating + ingredientsLength + numberInCollection + } +} + +mutation AddRecipe { + addRecipe(recipe: { + title: "New recipe" + description: "Simple description", + ingredients: [ + "One", + "Two", + "Three", + ], + }) { + id + numberInCollection + creationDate + } +}`; diff --git a/examples/graphql/src/__tests__/acceptance/graphql.acceptance.ts b/examples/graphql/src/__tests__/acceptance/graphql.acceptance.ts new file mode 100644 index 000000000000..b48247e63f63 --- /dev/null +++ b/examples/graphql/src/__tests__/acceptance/graphql.acceptance.ts @@ -0,0 +1,99 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application, createBindingFromClass} from '@loopback/core'; +import {GraphQLServer} from '@loopback/graphql'; +import { + createRestAppClient, + givenHttpServerConfig, + supertest, +} from '@loopback/testlab'; +import {GraphqlDemoApplication} from '../../'; +import {RecipesDataSource} from '../../datasources'; +import {RecipeResolver} from '../../graphql-resolvers/recipe-resolver'; +import {RecipeRepository} from '../../repositories'; +import {sampleRecipes} from '../../sample-recipes'; +import {RecipeService} from '../../services/recipe.service'; +import {runTests} from './graphql-tests'; + +describe('GraphQL server', () => { + let server: GraphQLServer; + let repo: RecipeRepository; + + before(givenServer); + after(stopServer); + + runTests(() => supertest(server.httpServer?.url)); + + async function givenServer() { + server = new GraphQLServer({host: '127.0.0.1', port: 0}); + server.resolver(RecipeResolver); + + server.bind('recipes').to([...sampleRecipes]); + const repoBinding = createBindingFromClass(RecipeRepository); + server.add(repoBinding); + server.add(createBindingFromClass(RecipesDataSource)); + server.add(createBindingFromClass(RecipeService)); + await server.start(); + repo = await server.get(repoBinding.key); + await repo.start(); + } + + async function stopServer() { + if (!server) return; + await server.stop(); + repo.stop(); + } +}); + +describe('GraphQL application', () => { + let server: GraphQLServer; + let app: Application; + + before(givenApp); + after(stopApp); + + runTests(() => supertest(server.httpServer?.url)); + + async function givenApp() { + app = new Application(); + const serverBinding = app.server(GraphQLServer); + app.configure(serverBinding.key).to({host: '127.0.0.1', port: 0}); + server = await app.getServer(GraphQLServer); + server.resolver(RecipeResolver); + + app.bind('recipes').to([...sampleRecipes]); + const repoBinding = createBindingFromClass(RecipeRepository); + app.add(repoBinding); + app.add(createBindingFromClass(RecipesDataSource)); + app.add(createBindingFromClass(RecipeService)); + await app.start(); + } + + async function stopApp() { + if (!app) return; + await app.stop(); + } +}); + +describe('GraphQL as middleware', () => { + let app: GraphqlDemoApplication; + + before(giveAppWithGraphQLMiddleware); + after(stopApp); + + runTests(() => createRestAppClient(app)); + + async function giveAppWithGraphQLMiddleware() { + app = new GraphqlDemoApplication({rest: givenHttpServerConfig()}); + await app.boot(); + await app.start(); + return app; + } + + async function stopApp() { + await app?.stop(); + } +}); diff --git a/examples/graphql/src/application.ts b/examples/graphql/src/application.ts new file mode 100644 index 000000000000..90a1da6ed5fc --- /dev/null +++ b/examples/graphql/src/application.ts @@ -0,0 +1,50 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BootMixin} from '@loopback/boot'; +import {ApplicationConfig} from '@loopback/core'; +import {GraphQLBindings, GraphQLComponent} from '@loopback/graphql'; +import {RepositoryMixin} from '@loopback/repository'; +import {RestApplication} from '@loopback/rest'; +import path from 'path'; +import {sampleRecipes} from './sample-recipes'; + +export {ApplicationConfig}; + +export class GraphqlDemoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + this.component(GraphQLComponent); + const server = this.getSync(GraphQLBindings.GRAPHQL_SERVER); + this.expressMiddleware('middleware.express.GraphQL', server.expressApp); + this.configure(GraphQLBindings.GRAPHQL_SERVER).to({ + asMiddlewareOnly: true, + }); + + // It's possible to register a graphql context resolver + this.bind(GraphQLBindings.GRAPHQL_CONTEXT_RESOLVER).to(context => { + return {...context}; + }); + + this.bind('recipes').to([...sampleRecipes]); + + // Set up default home page + this.static('/', path.join(__dirname, '../public')); + + this.projectRoot = __dirname; + // Customize @loopback/boot Booter Conventions here + this.bootOptions = { + graphqlResolvers: { + // Customize ControllerBooter Conventions here + dirs: ['graphql-resolvers'], + extensions: ['.js'], + nested: true, + }, + }; + } +} diff --git a/examples/graphql/src/datasources/README.md b/examples/graphql/src/datasources/README.md new file mode 100644 index 000000000000..57ae3823225d --- /dev/null +++ b/examples/graphql/src/datasources/README.md @@ -0,0 +1,3 @@ +# Datasources + +This directory contains config for datasources used by this app. diff --git a/examples/graphql/src/datasources/index.ts b/examples/graphql/src/datasources/index.ts new file mode 100644 index 000000000000..402e424025ee --- /dev/null +++ b/examples/graphql/src/datasources/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './recipes.datasource'; diff --git a/examples/graphql/src/datasources/recipes.datasource.ts b/examples/graphql/src/datasources/recipes.datasource.ts new file mode 100644 index 000000000000..3d0d2e5ae828 --- /dev/null +++ b/examples/graphql/src/datasources/recipes.datasource.ts @@ -0,0 +1,43 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + ContextTags, + inject, + lifeCycleObserver, + LifeCycleObserver, +} from '@loopback/core'; +import {juggler, RepositoryBindings} from '@loopback/repository'; + +const config = { + name: 'recipes', + connector: 'memory', + localStorage: '', + file: '', +}; + +// Observe application's life cycle to disconnect the datasource when +// application is stopped. This allows the application to be shut down +// gracefully. The `stop()` method is inherited from `juggler.DataSource`. +// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html +@lifeCycleObserver('datasource', { + tags: { + [ContextTags.NAME]: 'recipes', + [ContextTags.NAMESPACE]: RepositoryBindings.DATASOURCES, + }, +}) +export class RecipesDataSource + extends juggler.DataSource + implements LifeCycleObserver { + static dataSourceName = 'recipes'; + static readonly defaultConfig = config; + + constructor( + @inject('datasources.config.recipes', {optional: true}) + dsConfig: object = config, + ) { + super(dsConfig); + } +} diff --git a/examples/graphql/src/graphql-resolvers/recipe-resolver.ts b/examples/graphql/src/graphql-resolvers/recipe-resolver.ts new file mode 100644 index 000000000000..b3d9bb9a305f --- /dev/null +++ b/examples/graphql/src/graphql-resolvers/recipe-resolver.ts @@ -0,0 +1,67 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject, service} from '@loopback/core'; +import { + arg, + authorized, + fieldResolver, + GraphQLBindings, + Int, + mutation, + query, + resolver, + ResolverData, + ResolverInterface, + root, +} from '@loopback/graphql'; +import {repository} from '@loopback/repository'; +import {RecipeInput} from '../graphql-types/recipe-input'; +import {Recipe} from '../graphql-types/recipe-type'; +import {RecipeRepository} from '../repositories'; +import {RecipeService} from '../services/recipe.service'; + +@resolver(of => Recipe) +export class RecipeResolver implements ResolverInterface { + constructor( + // constructor injection of service + @repository('RecipeRepository') + private readonly recipeRepo: RecipeRepository, + @service(RecipeService) private readonly recipeService: RecipeService, + // It's possible to inject the resolver data + @inject(GraphQLBindings.RESOLVER_DATA) private resolverData: ResolverData, + ) {} + + @query(returns => Recipe, {nullable: true}) + @authorized('owner') + async recipe(@arg('recipeId') recipeId: string) { + return this.recipeRepo.getOne(recipeId); + } + + @query(returns => [Recipe]) + async recipes(): Promise { + return this.recipeRepo.getAll(); + } + + @mutation(returns => Recipe) + async addRecipe(@arg('recipe') recipe: RecipeInput): Promise { + return this.recipeRepo.add(recipe); + } + + @fieldResolver() + async numberInCollection(@root() recipe: Recipe): Promise { + const index = await this.recipeRepo.findIndex(recipe); + return index + 1; + } + + @fieldResolver() + ratingsCount( + @root() recipe: Recipe, + @arg('minRate', type => Int, {defaultValue: 0.0}) + minRate: number, + ): number { + return this.recipeService.ratingsCount(recipe, minRate); + } +} diff --git a/examples/graphql/src/graphql-types/recipe-input.ts b/examples/graphql/src/graphql-types/recipe-input.ts new file mode 100644 index 000000000000..0299c3a9d17b --- /dev/null +++ b/examples/graphql/src/graphql-types/recipe-input.ts @@ -0,0 +1,19 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {field, inputType} from '@loopback/graphql'; +import {Recipe} from './recipe-type'; + +@inputType() +export class RecipeInput implements Partial { + @field() + title: string; + + @field({nullable: true}) + description?: string; + + @field(type => [String]) + ingredients: string[]; +} diff --git a/examples/graphql/src/graphql-types/recipe-type.ts b/examples/graphql/src/graphql-types/recipe-type.ts new file mode 100644 index 000000000000..324fca444478 --- /dev/null +++ b/examples/graphql/src/graphql-types/recipe-type.ts @@ -0,0 +1,65 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {field, Float, ID, Int, objectType} from '@loopback/graphql'; +import {Entity, model, property} from '@loopback/repository'; + +@objectType({description: 'Object representing cooking recipe'}) +@model({settings: {strict: false}}) +export class Recipe extends Entity { + @field(type => ID) + @property({id: true}) + id: string; + + @field() + @property() + title: string; + + @field(type => String, { + nullable: true, + deprecationReason: 'Use `description` field instead', + }) + get specification(): string | undefined { + return this.description; + } + + @field({ + nullable: true, + description: 'The recipe description with preparation info', + }) + @property() + description?: string; + + @field(type => [Int]) + ratings: number[]; + + @field() + @property() + creationDate: Date; + + @field(type => Int) + protected numberInCollection: number; + + @field(type => Int) + ratingsCount: number; + + @field(type => [String]) + ingredients: string[]; + + @field(type => Int) + protected get ingredientsLength(): number { + return this.ingredients.length; + } + + @field(type => Float, {nullable: true}) + get averageRating(): number | null { + const ratingsCount = this.ratings.length; + if (ratingsCount === 0) { + return null; + } + const ratingsSum = this.ratings.reduce((a, b) => a + b, 0); + return ratingsSum / ratingsCount; + } +} diff --git a/examples/graphql/src/index.ts b/examples/graphql/src/index.ts new file mode 100644 index 000000000000..06d5e9a4f49e --- /dev/null +++ b/examples/graphql/src/index.ts @@ -0,0 +1,44 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {ApplicationConfig, GraphqlDemoApplication} from './application'; + +export * from './application'; + +export async function main(options: ApplicationConfig = {}) { + const app = new GraphqlDemoApplication(options); + await app.boot(); + await app.start(); + + const url = app.restServer.url; + console.log(`Server is running at ${url}`); + console.log(`Try ${url}/graphql`); + + return app; +} + +if (require.main === module) { + // Run the application + const config = { + rest: { + port: +(process.env.PORT ?? 3000), + host: process.env.HOST, + // The `gracePeriodForClose` provides a graceful close for http/https + // servers with keep-alive clients. The default value is `Infinity` + // (don't force-close). If you want to immediately destroy all sockets + // upon stop, set its value to `0`. + // See https://www.npmjs.com/package/stoppable + gracePeriodForClose: 5000, // 5 seconds + openApiSpec: { + // useful when used with OpenAPI-to-GraphQL to locate your application + setServersFromRequest: true, + }, + }, + }; + main(config).catch(err => { + console.error('Cannot start the application.', err); + process.exit(1); + }); +} diff --git a/examples/graphql/src/migrate.ts b/examples/graphql/src/migrate.ts new file mode 100644 index 000000000000..89d11f30f9cb --- /dev/null +++ b/examples/graphql/src/migrate.ts @@ -0,0 +1,27 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {GraphqlDemoApplication} from './application'; + +export async function migrate(args: string[]) { + const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter'; + console.log('Migrating schemas (%s existing schema)', existingSchema); + + const app = new GraphqlDemoApplication(); + await app.boot(); + await app.migrateSchema({existingSchema}); + + // Connectors usually keep a pool of opened connections, + // this keeps the process running even after all work is done. + // We need to exit explicitly. + process.exit(0); +} + +if (require.main === module) { + migrate(process.argv).catch(err => { + console.error('Cannot migrate database schema', err); + process.exit(1); + }); +} diff --git a/examples/graphql/src/repositories/README.md b/examples/graphql/src/repositories/README.md new file mode 100644 index 000000000000..08638a78783a --- /dev/null +++ b/examples/graphql/src/repositories/README.md @@ -0,0 +1,3 @@ +# Repositories + +This directory contains code for repositories provided by this app. diff --git a/examples/graphql/src/repositories/index.ts b/examples/graphql/src/repositories/index.ts new file mode 100644 index 000000000000..4e2ba225fc04 --- /dev/null +++ b/examples/graphql/src/repositories/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './recipe.repository'; diff --git a/examples/graphql/src/repositories/recipe.repository.ts b/examples/graphql/src/repositories/recipe.repository.ts new file mode 100644 index 000000000000..065f279ff126 --- /dev/null +++ b/examples/graphql/src/repositories/recipe.repository.ts @@ -0,0 +1,77 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + bind, + BindingScope, + ContextTags, + inject, + LifeCycleObserver, + lifeCycleObserver, +} from '@loopback/core'; +import {DefaultCrudRepository, RepositoryBindings} from '@loopback/repository'; +import {plainToClass} from 'class-transformer'; +import {RecipesDataSource} from '../datasources/recipes.datasource'; +import {RecipeInput} from '../graphql-types/recipe-input'; +import {Recipe} from '../graphql-types/recipe-type'; + +@bind({ + scope: BindingScope.SINGLETON, + tags: {[ContextTags.NAMESPACE]: RepositoryBindings.REPOSITORIES}, +}) +@lifeCycleObserver('repository') +export class RecipeRepository + extends DefaultCrudRepository + implements LifeCycleObserver { + private static idCounter = 0; + + @inject('recipes') + private sampleRecipes: Recipe[]; + + constructor(@inject('datasources.recipes') dataSource: RecipesDataSource) { + super(Recipe, dataSource); + } + + async start() { + await this.createAll(this.sampleRecipes); + // Increase the counter to avoid duplicate keys + RecipeRepository.idCounter += this.sampleRecipes.length; + } + + stop() { + // Reset the id counter + RecipeRepository.idCounter = 0; + } + + async getAll() { + return this.find(); + } + + async getOne(id: string) { + return this.findById(id); + } + + async add(data: RecipeInput) { + const recipe = this.createRecipe(data); + return this.create(recipe); + } + + async findIndex(recipe: Recipe) { + const recipes = await this.find(); + return recipes.findIndex(r => r.id === recipe.id); + } + + private createRecipe(recipeData: Partial): Recipe { + const recipe = plainToClass(Recipe, recipeData); + recipe.id = this.getId(); + recipe.creationDate = new Date('2020-05-24'); + recipe.ratings = [2, 4]; + return recipe; + } + + private getId(): string { + return (++RecipeRepository.idCounter).toString(); + } +} diff --git a/examples/graphql/src/sample-recipes.ts b/examples/graphql/src/sample-recipes.ts new file mode 100644 index 000000000000..c694f432efe1 --- /dev/null +++ b/examples/graphql/src/sample-recipes.ts @@ -0,0 +1,37 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {plainToClass} from 'class-transformer'; +import {Recipe} from './graphql-types/recipe-type'; + +export const sampleRecipes = [ + createRecipe({ + id: '1', + title: 'Recipe 1', + description: 'Desc 1', + ingredients: ['one', 'two', 'three'], + ratings: [0, 3, 1], + creationDate: new Date('2018-04-11'), + }), + createRecipe({ + id: '2', + title: 'Recipe 2', + description: 'Desc 2', + ingredients: ['four', 'five', 'six'], + ratings: [4, 2, 3, 1], + creationDate: new Date('2018-04-15'), + }), + createRecipe({ + id: '3', + title: 'Recipe 3', + ingredients: ['seven', 'eight', 'nine'], + ratings: [5, 4], + creationDate: new Date('2020-05-24'), + }), +]; + +function createRecipe(recipeData: Partial): Recipe { + return plainToClass(Recipe, recipeData); +} diff --git a/examples/graphql/src/services/recipe.service.ts b/examples/graphql/src/services/recipe.service.ts new file mode 100644 index 000000000000..dae4dd320f23 --- /dev/null +++ b/examples/graphql/src/services/recipe.service.ts @@ -0,0 +1,14 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/example-graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {asService, bind} from '@loopback/core'; +import {Recipe} from '../graphql-types/recipe-type'; + +@bind(asService(RecipeService)) +export class RecipeService { + ratingsCount(recipe: Recipe, minRate: number): number { + return recipe.ratings.filter(rating => rating >= minRate).length; + } +} diff --git a/examples/graphql/tsconfig.json b/examples/graphql/tsconfig.json new file mode 100644 index 000000000000..beb83571fe47 --- /dev/null +++ b/examples/graphql/tsconfig.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": [ + "src/**/*", + "src/**/*.json" + ], + "references": [ + { + "path": "../../extensions/graphql/tsconfig.json" + }, + { + "path": "../../packages/boot/tsconfig.json" + }, + { + "path": "../../packages/core/tsconfig.json" + }, + { + "path": "../../packages/repository/tsconfig.json" + }, + { + "path": "../../packages/testlab/tsconfig.json" + } + ] +} diff --git a/extensions/graphql/.npmrc b/extensions/graphql/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/extensions/graphql/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/extensions/graphql/LICENSE b/extensions/graphql/LICENSE new file mode 100644 index 000000000000..7924d14a9402 --- /dev/null +++ b/extensions/graphql/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/graphql +This project is licensed under the MIT License, full text below. + +-------- + +MIT License + +MIT License Copyright (c) IBM Corp. 2020 + +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 (including the next +paragraph) 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/extensions/graphql/README.md b/extensions/graphql/README.md new file mode 100644 index 000000000000..0e8b8afa6e23 --- /dev/null +++ b/extensions/graphql/README.md @@ -0,0 +1,401 @@ +# @loopback/graphql + +This module provides integration with [GraphQL](https://graphql.org/) using +[type-graphql](https://typegraphql.com/). + +![type-graphql](type-graphql.png) + +## Stability: ⚠️Experimental⚠️ + +> Experimental packages provide early access to advanced or experimental +> functionality to get community feedback. Such modules are published to npm +> using `0.x.y` versions. Their APIs and functionality may be subject to +> breaking changes in future releases. + +## Installation + +```sh +npm install --save @loopback/graphql +``` + +## Basic Use + +Let's assume we build an application to expose GraphQL endpoints similar as +[@loopback/example-graphql](https://github.com/strongloop/loopback-next/tree/master/examples/graphql). + +```ts +export class MyApplication extends BootMixin(RestApplication) { + constructor(config: ApplicationConfig) { + super(config); + this.projectRoot = __dirname; + this.component(GraphQLComponent); + this.configure(GraphQLBindings.GRAPHQL_SERVER).to({asMiddlewareOnly: true}); + } +} +``` + +## Configure GraphQLServer + +This package can be used in two flavors: + +- As a server for LoopBack applications + +```ts +import {Application} from '@loopback/core'; +import {GraphQLServer} from '@loopback/graphql'; + +const app = new Application(); +const serverBinding = app.server(GraphQLServer); +app.configure(serverBinding.key).to({host: '127.0.0.1', port: 0}); +server = await app.getServer(GraphQLServer); +// ... +await app.start(); +``` + +- As a middleware for LoopBack REST applications + +```ts +import {BootMixin} from '@loopback/boot'; +import {RepositoryMixin} from '@loopback/repository'; +import {RestApplication} from '@loopback/rest'; +import {ApplicationConfig} from '@loopback/core'; +import {GraphQLComponent, GraphQLBindings} from '@loopback/graphql'; + +export class GraphqlDemoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + this.component(GraphQLComponent); + const server = this.getSync(GraphQLBindings.GRAPHQL_SERVER); + this.expressMiddleware('middleware.express.GraphQL', server.expressApp); + this.configure(GraphQLBindings.GRAPHQL_SERVER).to({ + asMiddlewareOnly: true, + }); + + // ... + + // Customize @loopback/boot Booter Conventions here + this.bootOptions = { + graphqlResolvers: { + // Customize ControllerBooter Conventions here + dirs: ['graphql-resolvers'], + extensions: ['.js'], + nested: true, + }, + }; + } +} +``` + +## Add GraphQL types + +The `@loopback/graphql` packages supports GraphQL schemas to be defined using +only classes and decorators from `type-graphql`. + +```ts +import {Entity, model, property} from '@loopback/repository'; +import {field, Float, ID, Int, objectType} from '@loopback/graphql'; + +@objectType({description: 'Object representing cooking recipe'}) +@model({settings: {strict: false}}) +export class Recipe extends Entity { + @field(type => ID) + @property({id: true}) + id: string; + + @field() + @property() + title: string; + + @field(type => String, { + nullable: true, + deprecationReason: 'Use `description` field instead', + }) + get specification(): string | undefined { + return this.description; + } + + @field({ + nullable: true, + description: 'The recipe description with preparation info', + }) + @property() + description?: string; + + @field(type => [Int]) + ratings: number[]; + + @field() + @property() + creationDate: Date; + + @field(type => Int) + protected numberInCollection: number; + + @field(type => Int) + ratingsCount: number; + + @field(type => [String]) + ingredients: string[]; + + @field(type => Int) + protected get ingredientsLength(): number { + return this.ingredients.length; + } + + @field(type => Float, {nullable: true}) + get averageRating(): number | null { + const ratingsCount = this.ratings.length; + if (ratingsCount === 0) { + return null; + } + const ratingsSum = this.ratings.reduce((a, b) => a + b, 0); + return ratingsSum / ratingsCount; + } +} +``` + +Please note that we also use `@model` and `property` decorators to define how +the entities being persisted with LoopBack repositories. + +## Add GraphQL resolver classes + +To serve GraphQL, an application will provide resolver classes to group +query/mutation/resolver functions, similarly as how REST API endpoints are +exposed via controller classes. + +We use TypeScript decorators to provide metadata about individual resolvers. See +[type-graphql docs](https://typegraphql.com/) for more details. + +Let's add `recipe-resolver.ts` to `src/graphql-resolvers` so that it can be +automatically discovered and loaded by the `@loopback/graphql` component. + +Please note that we re-export `type-graphql` decorators as camel case variants, +such as `query` instead of `Query`. It's recommended that your applications +import such decorators from `@loopback/graphql`. + +```ts +import {service} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import { + arg, + fieldResolver, + Int, + mutation, + query, + resolver, + root, + ResolverInterface, +} from '@loopback/graphql'; +import {RecipeInput} from '../graphql-types/recipe-input'; +import {Recipe} from '../graphql-types/recipe-type'; +import {RecipeRepository} from '../repositories'; +import {RecipeService} from '../services/recipe.service'; + +@resolver(of => Recipe) +export class RecipeResolver implements ResolverInterface { + constructor( + // Inject an instance of RecipeRepository + @repository('RecipeRepository') + private readonly recipeRepo: RecipeRepository, + // Inject an instance of RecipeService + @service(RecipeService) private readonly recipeService: RecipeService, + ) {} + + // Map to a GraphQL query to get recipe by id + @query(returns => Recipe, {nullable: true}) + async recipe(@arg('recipeId') recipeId: string) { + return this.recipeRepo.getOne(recipeId); + } + + // Map to a GraphQL query to list all recipes + @query(returns => [Recipe]) + async recipes(): Promise { + return this.recipeRepo.getAll(); + } + + // Map to a GraphQL mutation to add a new recipe + @mutation(returns => Recipe) + async addRecipe(@arg('recipe') recipe: RecipeInput): Promise { + return this.recipeRepo.add(recipe); + } + + // Map to a calculated GraphQL field - `numberInCollection` + @fieldResolver() + async numberInCollection(@root() recipe: Recipe): Promise { + const index = await this.recipeRepo.findIndex(recipe); + return index + 1; + } + + // Map to a calculated GraphQL field - `ratingsCount` + @fieldResolver() + ratingsCount( + @root() recipe: Recipe, + @arg('minRate', type => Int, {defaultValue: 0.0}) + minRate: number, + ): number { + return this.recipeService.ratingsCount(recipe, minRate); + } +} +``` + +## Use LoopBack dependency injections in resolver classes + +All of LoopBack decorators for dependency injection , such as `@inject`, +`@service`, `@repository`, and `@config`, can be used with resolver classes. + +```ts +import {service} from '@loopback/core'; + +@resolver(of => Recipe) +export class RecipeResolver implements ResolverInterface { + constructor( + // constructor injection of service + @repository('RecipeRepository') + private readonly recipeRepo: RecipeRepository, + @service(RecipeService) private readonly recipeService: RecipeService, + // It's possible to inject the resolver data + @inject(GraphQLBindings.RESOLVER_DATA) private resolverData: ResolverData, + ) {} +} +``` + +## Discover and load GraphQL resolvers + +The `GraphQLComponent` contributes a booter that discovers and registers +resolver classes from `src/graphql-resolvers` during `app.boot()`. + +## Propagate context data + +The `GraphQLServer` allows you to propagate context from Express to resolvers. + +### Register a GraphQL context resolver + +The GraphQL context object can be built/enhanced by the context resolver. The +original value is `{req: Request, res: Response}` that represents the Express +request and response object. + +```ts +export class GraphqlDemoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + // ... + // It's possible to register a graphql context resolver + this.bind(GraphQLBindings.GRAPHQL_CONTEXT_RESOLVER).to(context => { + // Add your custom logic here to produce a context from incoming ExpressContext + return {...context}; + }); + } + // ... +} +``` + +### Access the GraphQL context inside a resolver + +```ts +@resolver(of => Recipe) +export class RecipeResolver implements ResolverInterface { + constructor( + // constructor injection of service + @repository('RecipeRepository') + private readonly recipeRepo: RecipeRepository, + @service(RecipeService) private readonly recipeService: RecipeService, + // It's possible to inject the resolver data + @inject(GraphQLBindings.RESOLVER_DATA) private resolverData: ResolverData, + ) { + // `this.resolverData.context` is the GraphQL context + } + // ... +} +``` + +### Set up authorization checker + +We can customize the `authChecker` for +[TypeGraphQL Authorization](https://typegraphql.com/docs/authorization.html). + +```ts +export class GraphqlDemoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + // ... + // It's possible to register a graphql auth checker + this.bind(GraphQLBindings.GRAPHQL_AUTH_CHECKER).to( + (resolverData, roles) => { + // Use resolverData and roles for authorization + return true; + }, + ); + } + // ... +} +``` + +The resolver classes and graphql types can be decorated with `@authorized` to +enforce authorization. + +```ts +@resolver(of => Recipe) +export class RecipeResolver implements ResolverInterface { + constructor() {} // ... + + @query(returns => Recipe, {nullable: true}) + @authorized('owner') // Authorized against `owner` role + async recipe(@arg('recipeId') recipeId: string) { + return this.recipeRepo.getOne(recipeId); + } +} +``` + +## Register GraphQL middleware + +We can register one or more +[TypeGraphQL Middleware](https://typegraphql.com/docs/middlewares.html) as +follows: + +```ts +export class GraphqlDemoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + // Register a GraphQL middleware + this.middleware((resolverData, next) => { + // It's invoked for each field resolver, query and mutation operations + return next(); + }); + } +} +``` + +## Try it out + +Check out +[@loopback/example-graphql](https://github.com/strongloop/loopback-next/tree/master/examples/graphql). + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/extensions/graphql/package-lock.json b/extensions/graphql/package-lock.json new file mode 100644 index 000000000000..6c1e292a5113 --- /dev/null +++ b/extensions/graphql/package-lock.json @@ -0,0 +1,1637 @@ +{ + "name": "@loopback/graphql", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@apollo/protobufjs": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.5.tgz", + "integrity": "sha512-ZtyaBH1icCgqwIGb3zrtopV2D5Q8yxibkJzlaViM08eOhTQc7rACdYu0pfORFfhllvdMZ3aq69vifYHszY4gNA==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + } + }, + "@apollographql/apollo-tools": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.4.8.tgz", + "integrity": "sha512-W2+HB8Y7ifowcf3YyPHgDI05izyRtOeZ4MqIr7LbTArtmJ0ZHULWpn84SGMW7NAvTV1tFExpHlveHhnXuJfuGA==", + "requires": { + "apollo-env": "^0.6.5" + } + }, + "@apollographql/graphql-playground-html": { + "version": "1.6.26", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz", + "integrity": "sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==", + "requires": { + "xss": "^1.0.6" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@types/accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", + "requires": { + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg==" + }, + "@types/cookies": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.4.tgz", + "integrity": "sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw==", + "requires": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/cors": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.7.tgz", + "integrity": "sha512-sOdDRU3oRS7LBNTIqwDkPJyq0lpHYcbMTt0TrjzsXbk/e37hcLTH6eZX7CdbDeN0yJJvzw9hFBZkbtCSbk/jAQ==", + "requires": { + "@types/express": "*" + } + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/express": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz", + "integrity": "sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/fs-capacitor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz", + "integrity": "sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ==", + "requires": { + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/graphql": { + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.5.0.tgz", + "integrity": "sha512-MOkzsEp1Jk5bXuAsHsUi6BVv0zCO+7/2PTiZMXWDSsMXvNU6w/PLMQT2vHn8hy2i0JqojPz1Sz6rsFjHtsU0lA==", + "requires": { + "graphql": "*" + } + }, + "@types/graphql-upload": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.3.tgz", + "integrity": "sha512-hmLg9pCU/GmxBscg8GCr1vmSoEmbItNNxdD5YH2TJkXm//8atjwuprB+xJBK714JG1dkxbbhp5RHX+Pz1KsCMA==", + "requires": { + "@types/express": "*", + "@types/fs-capacitor": "*", + "@types/koa": "*", + "graphql": "^14.5.3" + }, + "dependencies": { + "graphql": { + "version": "14.7.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", + "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", + "requires": { + "iterall": "^1.2.2" + } + } + } + }, + "@types/http-assert": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", + "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" + }, + "@types/http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==" + }, + "@types/keygrip": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", + "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" + }, + "@types/koa": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.4.tgz", + "integrity": "sha512-Etqs0kdqbuAsNr5k6mlZQelpZKVwMu9WPRHVVTLnceZlhr0pYmblRNJbCgoCMzKWWePldydU0AYEOX4Q9fnGUQ==", + "requires": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/koa-compose": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz", + "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", + "requires": { + "@types/koa": "*" + } + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "@types/node": { + "version": "10.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz", + "integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ==" + }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@types/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/semver": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.3.tgz", + "integrity": "sha512-jQxClWFzv9IXdLdhSaTf16XI3NYe6zrEbckSpb5xhKfPbWgIyAY0AFyWWWfaiDcBuj3UHmMkCIwSRqpKMTZL2Q==" + }, + "@types/serve-static": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", + "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/ws": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz", + "integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==", + "requires": { + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", + "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==" + } + } + }, + "@wry/equality": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz", + "integrity": "sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==", + "requires": { + "tslib": "^1.9.3" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "apollo-cache-control": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.1.tgz", + "integrity": "sha512-6iHa8TkcKt4rx5SKRzDNjUIpCQX+7/FlZwD7vRh9JDnM4VH8SWhpj8fUR3CiEY8Kuc4ChXnOY8bCcMju5KPnIQ==", + "requires": { + "apollo-server-env": "^2.4.5", + "apollo-server-plugin-base": "^0.9.1" + } + }, + "apollo-datasource": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.2.tgz", + "integrity": "sha512-ibnW+s4BMp4K2AgzLEtvzkjg7dJgCaw9M5b5N0YKNmeRZRnl/I/qBTQae648FsRKgMwTbRQIvBhQ0URUFAqFOw==", + "requires": { + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" + } + }, + "apollo-engine-reporting": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.3.0.tgz", + "integrity": "sha512-SbcPLFuUZcRqDEZ6mSs8uHM9Ftr8yyt2IEu0JA8c3LNBmYXSLM7MHqFe80SVcosYSTBgtMz8mLJO8orhYoSYZw==", + "requires": { + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-graphql": "^0.5.0", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5", + "apollo-server-errors": "^2.4.2", + "apollo-server-plugin-base": "^0.9.1", + "apollo-server-types": "^0.5.1", + "async-retry": "^1.2.1", + "uuid": "^8.0.0" + } + }, + "apollo-engine-reporting-protobuf": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz", + "integrity": "sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg==", + "requires": { + "@apollo/protobufjs": "^1.0.3" + } + }, + "apollo-env": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.5.tgz", + "integrity": "sha512-jeBUVsGymeTHYWp3me0R2CZRZrFeuSZeICZHCeRflHTfnQtlmbSXdy5E0pOyRM9CU4JfQkKDC98S1YglQj7Bzg==", + "requires": { + "@types/node-fetch": "2.5.7", + "core-js": "^3.0.1", + "node-fetch": "^2.2.0", + "sha.js": "^2.4.11" + } + }, + "apollo-graphql": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.5.0.tgz", + "integrity": "sha512-YSdF/BKPbsnQpxWpmCE53pBJX44aaoif31Y22I/qKpB6ZSGzYijV5YBoCL5Q15H2oA/v/02Oazh9lbp4ek3eig==", + "requires": { + "apollo-env": "^0.6.5", + "lodash.sortby": "^4.7.0" + } + }, + "apollo-link": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz", + "integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==", + "requires": { + "apollo-utilities": "^1.3.0", + "ts-invariant": "^0.4.0", + "tslib": "^1.9.3", + "zen-observable-ts": "^0.8.21" + } + }, + "apollo-server-caching": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", + "integrity": "sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, + "apollo-server-core": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.17.0.tgz", + "integrity": "sha512-rjAkBbKSrGLDfg/g5bohnPlQahmkAxgEBuMDVsoF3aa+RaEPXPUMYrLbOxntl0LWeLbPiMa/IyFF43dvlGqV7w==", + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "@apollographql/graphql-playground-html": "1.6.26", + "@types/graphql-upload": "^8.0.0", + "@types/ws": "^7.0.0", + "apollo-cache-control": "^0.11.1", + "apollo-datasource": "^0.7.2", + "apollo-engine-reporting": "^2.3.0", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5", + "apollo-server-errors": "^2.4.2", + "apollo-server-plugin-base": "^0.9.1", + "apollo-server-types": "^0.5.1", + "apollo-tracing": "^0.11.2", + "fast-json-stable-stringify": "^2.0.0", + "graphql-extensions": "^0.12.4", + "graphql-tag": "^2.9.2", + "graphql-tools": "^4.0.0", + "graphql-upload": "^8.0.2", + "loglevel": "^1.6.7", + "sha.js": "^2.4.11", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^6.0.0" + } + }, + "apollo-server-env": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", + "requires": { + "node-fetch": "^2.1.2", + "util.promisify": "^1.0.0" + } + }, + "apollo-server-errors": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz", + "integrity": "sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ==" + }, + "apollo-server-express": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.17.0.tgz", + "integrity": "sha512-PonpWOuM1DH3Cz0bu56Tusr3GXOnectC6AD/gy2GXK0v84E7tKTuxEY3SgsgxhvfvvhfwJbXTyIogL/wezqnCw==", + "requires": { + "@apollographql/graphql-playground-html": "1.6.26", + "@types/accepts": "^1.3.5", + "@types/body-parser": "1.19.0", + "@types/cors": "^2.8.4", + "@types/express": "4.17.7", + "accepts": "^1.3.5", + "apollo-server-core": "^2.17.0", + "apollo-server-types": "^0.5.1", + "body-parser": "^1.18.3", + "cors": "^2.8.4", + "express": "^4.17.1", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0", + "parseurl": "^1.3.2", + "subscriptions-transport-ws": "^0.9.16", + "type-is": "^1.6.16" + } + }, + "apollo-server-plugin-base": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.9.1.tgz", + "integrity": "sha512-kvrX4Z3FdpjrZdHkyl5iY2A1Wvp4b6KQp00DeZqss7GyyKNUBKr80/7RQgBLEw7EWM7WB19j459xM/TjvW0FKQ==", + "requires": { + "apollo-server-types": "^0.5.1" + } + }, + "apollo-server-types": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz", + "integrity": "sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA==", + "requires": { + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" + } + }, + "apollo-tracing": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.11.2.tgz", + "integrity": "sha512-QjmRd2ozGD+PfmF6U9w/w6jrclYSBNczN6Bzppr8qA5somEGl5pqdprIZYL28H0IapZiutA3x6p6ZVF/cVX8wA==", + "requires": { + "apollo-server-env": "^2.4.5", + "apollo-server-plugin-base": "^0.9.1" + } + }, + "apollo-utilities": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz", + "integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==", + "requires": { + "@wry/equality": "^0.1.2", + "fast-json-stable-stringify": "^2.0.0", + "ts-invariant": "^0.4.0", + "tslib": "^1.10.0" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "async-retry": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", + "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", + "requires": { + "retry": "0.12.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "busboy": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", + "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "requires": { + "dicer": "0.3.0" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "class-transformer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", + "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "deprecated-decorator": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", + "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-capacitor": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz", + "integrity": "sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graphql": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz", + "integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==" + }, + "graphql-extensions": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.4.tgz", + "integrity": "sha512-GnR4LiWk3s2bGOqIh6V1JgnSXw2RCH4NOgbCFEWvB6JqWHXTlXnLZ8bRSkCiD4pltv7RHUPWqN/sGh8R6Ae/ag==", + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "apollo-server-env": "^2.4.5", + "apollo-server-types": "^0.5.1" + } + }, + "graphql-query-complexity": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/graphql-query-complexity/-/graphql-query-complexity-0.7.0.tgz", + "integrity": "sha512-nl/aOzRdQ1Opj/sND9Wx+/FHnsU4SFA0wePWJzyZajyht43nP9/1edP6YaoupXfNeUocHFXsmAOOFVTwTLu/6A==", + "requires": { + "lodash.get": "^4.4.2" + } + }, + "graphql-subscriptions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz", + "integrity": "sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==", + "requires": { + "iterall": "^1.2.1" + } + }, + "graphql-tag": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz", + "integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==" + }, + "graphql-tools": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz", + "integrity": "sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==", + "requires": { + "apollo-link": "^1.2.14", + "apollo-utilities": "^1.0.1", + "deprecated-decorator": "^0.1.6", + "iterall": "^1.1.3", + "uuid": "^3.1.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, + "graphql-upload": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz", + "integrity": "sha512-U2OiDI5VxYmzRKw0Z2dmfk0zkqMRaecH9Smh1U277gVgVe9Qn+18xqf4skwr4YJszGIh7iQDZ57+5ygOK9sM/Q==", + "requires": { + "busboy": "^0.3.1", + "fs-capacitor": "^2.0.4", + "http-errors": "^1.7.3", + "object-path": "^0.11.4" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "loglevel": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", + "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-path": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz", + "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + } + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "subscriptions-transport-ws": { + "version": "0.9.18", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz", + "integrity": "sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA==", + "requires": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0" + }, + "dependencies": { + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "ts-invariant": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", + "integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", + "requires": { + "tslib": "^1.9.3" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "type-graphql": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-graphql/-/type-graphql-1.0.0.tgz", + "integrity": "sha512-ejXSEmqewrv9MX/0XOSKBc7utjrF6N0UeZJQ0w/R/Ene4Or9/cA+8iLQnuqgBDlNpXIdcq4rSvrk3mUMAIqOQA==", + "requires": { + "@types/glob": "^7.1.3", + "@types/node": "*", + "@types/semver": "^7.3.3", + "glob": "^7.1.6", + "graphql-query-complexity": "^0.7.0", + "graphql-subscriptions": "^1.1.0", + "semver": "^7.3.2", + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + } + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xss": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.8.tgz", + "integrity": "sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw==", + "requires": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "zen-observable-ts": { + "version": "0.8.21", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz", + "integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==", + "requires": { + "tslib": "^1.9.3", + "zen-observable": "^0.8.0" + } + } + } +} diff --git a/extensions/graphql/package.json b/extensions/graphql/package.json new file mode 100644 index 000000000000..6233bf7c0340 --- /dev/null +++ b/extensions/graphql/package.json @@ -0,0 +1,63 @@ +{ + "name": "@loopback/graphql", + "version": "0.0.1", + "description": "LoopBack's graphql integration", + "engines": { + "node": ">=10.16" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "acceptance": "lb-mocha \"dist/__tests__/acceptance/**/*.js\"", + "build": "lb-tsc", + "clean": "lb-clean loopback-graphql*.tgz dist *.tsbuildinfo package", + "pretest": "npm run build", + "integration": "lb-mocha \"dist/__tests__/integration/**/*.js\"", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "unit": "lb-mocha \"dist/__tests__/unit/**/*.js\"", + "verify": "npm pack && tar xf loopback-graphql*.tgz && tree package && npm run clean" + }, + "author": "IBM Corp.", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "dependencies": { + "@loopback/boot": "^2.5.1", + "@loopback/core": "^2.9.5", + "@loopback/http-server": "^2.2.0", + "@loopback/metadata": "^2.2.6", + "@types/graphql": "^14.5.0", + "apollo-server-express": "^2.16.1", + "debug": "^4.1.1", + "express": "^4.17.1", + "graphql": "^15.3.0", + "type-graphql": "^1.0.0-rc.3" + }, + "devDependencies": { + "@loopback/build": "^6.2.2", + "@loopback/eslint-config": "^9.0.2", + "@loopback/repository": "^2.11.2", + "@loopback/rest": "^6.2.0", + "@loopback/testlab": "^3.2.4", + "@types/debug": "^4.1.5", + "@types/node": "^10.17.28", + "class-transformer": "^0.3.1" + }, + "keywords": [ + "LoopBack", + "GraphQL" + ], + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "extensions/graphql" + } +} diff --git a/extensions/graphql/src/__tests__/unit/container.unit.ts b/extensions/graphql/src/__tests__/unit/container.unit.ts new file mode 100644 index 000000000000..304149f7a0b9 --- /dev/null +++ b/extensions/graphql/src/__tests__/unit/container.unit.ts @@ -0,0 +1,152 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Binding, + Context, + createBindingFromClass, + inject, + service, +} from '@loopback/core'; +import {expect} from '@loopback/testlab'; +import {GraphQLResolveInfo} from 'graphql'; +import { + Arg, + Field, + FieldResolver, + ID, + Int, + ObjectType, + Query, + Resolver, + ResolverData, + Root, +} from 'type-graphql'; +import {GraphQLTags, LoopBackContainer} from '../..'; + +describe('LoopBack container for type-graphql', () => { + let parentCtx: Context; + let container: LoopBackContainer; + const resolverData: ResolverData = { + root: '', + args: {}, + context: {}, + info: ({} as unknown) as GraphQLResolveInfo, + }; + + beforeEach(givenContainer); + + it('resolves from parent context', () => { + parentCtx.add(createBindingFromClass(BookService)); + parentCtx.bind('graphql.resolvers.BookResolver').toClass(BookResolver); + const resolver = container.get(BookResolver, resolverData); + expect(resolver).to.be.instanceof(BookResolver); + }); + + it('resolves from parent context by key', () => { + parentCtx.add(createBindingFromClass(BookService)); + parentCtx.bind('graphql.resolvers.BookResolver').toClass(BookResolver); + parentCtx + .bind('graphql.resolvers.AnotherBookResolver') + .toClass(BookResolver); + const resolver = container.get(BookResolver, resolverData) as BookResolver; + // Prefer the binding with the same key + expect(resolver.binding.key).to.eql('graphql.resolvers.BookResolver'); + }); + + it('resolves from parent context with the first binding', () => { + parentCtx.add(createBindingFromClass(BookService)); + // Add two bindings with RESOLVER tag + parentCtx + .bind('graphql.resolvers.BookResolver1') + .toClass(BookResolver) + .tag(GraphQLTags.RESOLVER); + parentCtx + .bind('graphql.resolvers.BookResolver2') + .toClass(BookResolver) + .tag(GraphQLTags.RESOLVER); + const resolver = container.get(BookResolver, resolverData) as BookResolver; + expect(resolver.binding.key).to.eql('graphql.resolvers.BookResolver1'); + }); + + it('resolves from child context', () => { + parentCtx.add(createBindingFromClass(BookService)); + const resolver = container.get(BookResolver, resolverData); + expect(resolver).to.be.instanceof(BookResolver); + expect(parentCtx.isBound('graphql.resolvers.BookResolver')).to.be.false(); + }); + + it('resolves from middleware context', () => { + const middlewareCtx = new Context('request'); + middlewareCtx.bind('graphql.resolvers.BookResolver').toClass(BookResolver); + parentCtx.add(createBindingFromClass(BookService)); + const MIDDLEWARE_CONTEXT = Symbol.for('loopback.middleware.context'); + const resolverDataWithMiddlewareContext: ResolverData = { + root: '', + args: {}, + context: { + request: { + // Simulate an Express request with LoopBack middleware/request context + [MIDDLEWARE_CONTEXT]: middlewareCtx, + }, + }, + info: ({} as unknown) as GraphQLResolveInfo, + }; + const resolver = container.get( + BookResolver, + resolverDataWithMiddlewareContext, + ); + expect(resolver).to.be.instanceof(BookResolver); + expect(parentCtx.isBound('graphql.resolvers.BookResolver')).to.be.false(); + }); + + @ObjectType({description: 'Book'}) + class Book { + @Field(type => ID) + id: string; + + @Field() + title: string; + + @Field(type => Int) + protected numberInCollection: number; + } + + class BookService { + static books: Record = { + '1': new Book(), + }; + getOne(id: string) { + return BookService.books[id]; + } + } + + @Resolver(of => Book) + class BookResolver { + @inject.binding() + readonly binding: Binding; + + constructor( + // constructor injection of service + @service() + private readonly bookService: BookService, + ) {} + + @Query(returns => Book, {nullable: true}) + async book(@Arg('bookId') bookId: string) { + return this.bookService.getOne(bookId); + } + + @FieldResolver() + numberInCollection(@Root() book: Book): number { + return 1; + } + } + + function givenContainer() { + parentCtx = new Context(); + container = new LoopBackContainer(parentCtx); + } +}); diff --git a/extensions/graphql/src/__tests__/unit/graphql.component.unit.ts b/extensions/graphql/src/__tests__/unit/graphql.component.unit.ts new file mode 100644 index 000000000000..b9de6ce4f1a9 --- /dev/null +++ b/extensions/graphql/src/__tests__/unit/graphql.component.unit.ts @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application} from '@loopback/core'; +import {expect} from '@loopback/testlab'; +import {GraphQLComponent} from '../../graphql.component'; +import {GraphQLBindings} from '../../keys'; + +describe('GraphQL component', () => { + it('binds server and booter bindings;', () => { + const app = new Application(); + app.component(GraphQLComponent); + expect(app.isBound(GraphQLBindings.COMPONENT)).to.be.true(); + expect(app.isBound('booters.GraphQLResolverBooter')).to.be.true(); + }); +}); diff --git a/extensions/graphql/src/__tests__/unit/graphql.server.unit.ts b/extensions/graphql/src/__tests__/unit/graphql.server.unit.ts new file mode 100644 index 000000000000..ef5e1860ca1f --- /dev/null +++ b/extensions/graphql/src/__tests__/unit/graphql.server.unit.ts @@ -0,0 +1,68 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import { + ExpressContext, + field, + GraphQLBindings, + GraphQLMiddleware, + GraphQLServer, + ID, + objectType, + query, + resolver, +} from '../..'; + +describe('GraphQL server', () => { + let server: GraphQLServer; + + beforeEach(givenServer); + + it('registers resolver classes', () => { + server.resolver(RecipeResolver); + expect(server.getResolvers()).to.containEql(RecipeResolver); + }); + + it('registers resolver classes with name', () => { + const binding = server.resolver(RecipeResolver, 'my-resolver'); + expect(binding.key).to.eql(`${GraphQLBindings.RESOLVERS}.my-resolver`); + }); + + it('registers middleware', async () => { + const middleware: GraphQLMiddleware = ( + resolverData, + next, + ) => { + return next(); + }; + server.middleware(middleware); + const middlewareList = await server.getMiddleware(); + expect(middlewareList).to.containEql(middleware); + }); + + function givenServer() { + server = new GraphQLServer(); + } + + @objectType({description: 'Object representing cooking recipe'}) + class Recipe { + @field(type => ID) + id: string; + + @field() + title: string; + } + + @resolver(of => Recipe) + class RecipeResolver { + constructor() {} + + @query(returns => [Recipe]) + async recipes(): Promise { + return []; + } + } +}); diff --git a/extensions/graphql/src/booters/resolver.booter.ts b/extensions/graphql/src/booters/resolver.booter.ts new file mode 100644 index 000000000000..902a92ee0933 --- /dev/null +++ b/extensions/graphql/src/booters/resolver.booter.ts @@ -0,0 +1,83 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + ArtifactOptions, + BaseArtifactBooter, + BootBindings, + booter, +} from '@loopback/boot'; +import { + Application, + config, + Constructor, + CoreBindings, + inject, +} from '@loopback/core'; +import debugFactory from 'debug'; +import {ResolverClassMetadata} from 'type-graphql/dist/metadata/definitions'; +import {getMetadataStorage} from 'type-graphql/dist/metadata/getMetadataStorage'; +import {registerResolver} from '../graphql.server'; + +const debug = debugFactory('loopback:graphql:resolver-booter'); + +type GraphQLResolverClass = Constructor; + +/** + * A class that extends BaseArtifactBooter to boot the 'GraphQLResolver' artifact type. + * + * Supported phases: configure, discover, load + * + * @param app - Application instance + * @param projectRoot - Root of User Project relative to which all paths are resolved + * @param bootConfig - GraphQLResolver Artifact Options Object + */ +@booter('graphqlResolvers') +export class GraphQLResolverBooter extends BaseArtifactBooter { + resolvers: GraphQLResolverClass[]; + + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) + public app: Application, + @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @config() + public interceptorConfig: ArtifactOptions = {}, + ) { + super( + projectRoot, + // Set GraphQLResolver Booter Options if passed in via bootConfig + Object.assign({}, GraphQLResolverDefaults, interceptorConfig), + ); + } + + /** + * Uses super method to get a list of Artifact classes. Boot each file by + * creating a DataSourceConstructor and binding it to the application class. + */ + async load() { + await super.load(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const resolverClasses: ResolverClassMetadata[] = (getMetadataStorage() as any) + .resolverClasses; + this.resolvers = this.classes.filter(cls => { + return resolverClasses.some(r => !r.isAbstract && r.target === cls); + }); + for (const resolver of this.resolvers) { + debug('Bind interceptor: %s', resolver.name); + const binding = registerResolver(this.app, resolver); + debug('Binding created for interceptor: %j', binding); + } + } +} + +/** + * Default ArtifactOptions for GraphQLResolverBooter. + */ +export const GraphQLResolverDefaults: ArtifactOptions = { + dirs: ['graphql-resolvers'], + extensions: ['.js'], + nested: true, +}; diff --git a/extensions/graphql/src/decorators/index.ts b/extensions/graphql/src/decorators/index.ts new file mode 100644 index 000000000000..ca522c7126f2 --- /dev/null +++ b/extensions/graphql/src/decorators/index.ts @@ -0,0 +1,40 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// Force require('reflect-metadata'); +import '@loopback/metadata'; +import { + Arg, + Args, + ArgsType, + Authorized, + Field, + FieldResolver, + InputType, + Mutation, + ObjectType, + Query, + Resolver, + Root, +} from 'type-graphql'; + +/** + * Re-exporting type-graphql decorators as lower case versions for two purposes: + * - To be consistent with LoopBack's naming convention of decorators + * - Allow future possibility to add extra metadata in addition to type-graphql's + * behavior, for example, mapping to LoopBack model properties + */ +export const arg = Arg; +export const args = Args; +export const argsType = ArgsType; +export const fieldResolver = FieldResolver; +export const mutation = Mutation; +export const query = Query; +export const resolver = Resolver; +export const root = Root; +export const field = Field; +export const inputType = InputType; +export const objectType = ObjectType; +export const authorized = Authorized; diff --git a/extensions/graphql/src/graphql.component.ts b/extensions/graphql/src/graphql.component.ts new file mode 100644 index 000000000000..86d92d4f52c7 --- /dev/null +++ b/extensions/graphql/src/graphql.component.ts @@ -0,0 +1,26 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Binding, + Component, + config, + createBindingFromClass, +} from '@loopback/core'; +import {GraphQLResolverBooter} from './booters/resolver.booter'; +import {GraphQLServer} from './graphql.server'; +import {GraphQLComponentOptions} from './types'; + +/** + * Component for GraphQL + */ +export class GraphQLComponent implements Component { + bindings: Binding[] = [ + createBindingFromClass(GraphQLServer), + createBindingFromClass(GraphQLResolverBooter), + ]; + + constructor(@config() private options: GraphQLComponentOptions = {}) {} +} diff --git a/extensions/graphql/src/graphql.container.ts b/extensions/graphql/src/graphql.container.ts new file mode 100644 index 000000000000..0f37f00c2c7d --- /dev/null +++ b/extensions/graphql/src/graphql.container.ts @@ -0,0 +1,99 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Binding, + Constructor, + Context, + createBindingFromClass, + filterByKey, + filterByServiceInterface, +} from '@loopback/core'; +import {ExpressContext} from 'apollo-server-express/dist/ApolloServer'; +import debugFactory from 'debug'; +import {ContainerType, ResolverData} from 'type-graphql'; +import {GraphQLBindings, GraphQLTags} from './keys'; + +const debug = debugFactory('loopback:graphql:container'); +const MIDDLEWARE_CONTEXT = Symbol.for('loopback.middleware.context'); + +/** + * Context for graphql resolver resolution + */ +export class GraphQLResolutionContext extends Context { + constructor( + parent: Context, + readonly resolverClass: Constructor, + readonly resolverData: ResolverData, + ) { + super(parent); + this.bind(GraphQLBindings.RESOLVER_DATA).to(resolverData); + this.bind(GraphQLBindings.RESOLVER_CLASS).to(resolverClass); + } +} + +/** + * Implementation of `ContainerType` to plug into `type-graphql` as the IoC + * container + */ +export class LoopBackContainer implements ContainerType { + constructor(readonly ctx: Context) {} + get( + resolverClass: Constructor, + resolverData: ResolverData, + ) { + debug('Resolving a resolver %s', resolverClass.name, resolverData); + + // Check if the resolverData has the LoopBack RequestContext + const graphQLCtx = resolverData.context as ExpressContext; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const reqCtx = (graphQLCtx?.req as any)?.[MIDDLEWARE_CONTEXT]; + const parent = reqCtx ?? this.ctx; + + const resolutionCtx = new GraphQLResolutionContext( + parent, + resolverClass, + resolverData, + ); + const resolverBinding = createBindingFromClass(resolverClass, { + defaultNamespace: GraphQLBindings.RESOLVERS, + }); + // Find resolver bindings that match the class + const bindings = this.ctx + .findByTag(GraphQLTags.RESOLVER) + .filter(filterByServiceInterface(resolverClass)); + if (bindings.length === 0) { + // No explicit binding found + debug( + 'Resolver %s not found in context %s', + resolverClass.name, + this.ctx.name, + ); + // Let's use the resolution context to resolve it from the class + resolutionCtx.add(resolverBinding); + return resolutionCtx.getValueOrPromise(resolverBinding.key); + } + + let found: Readonly | undefined; + if (bindings.length === 1) { + // Only one found, use it + found = bindings[0]; + } else { + // Narrow down by key + found = bindings.find(filterByKey(resolverBinding.key)); + if (!found) { + found = bindings[0]; + } + } + + debug( + 'Resolver %s found in context %s', + resolverClass.name, + resolutionCtx.name, + found, + ); + return resolutionCtx.getValueOrPromise(found.key); + } +} diff --git a/extensions/graphql/src/graphql.server.ts b/extensions/graphql/src/graphql.server.ts new file mode 100644 index 000000000000..b0edf1d59041 --- /dev/null +++ b/extensions/graphql/src/graphql.server.ts @@ -0,0 +1,210 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Binding, + BindingFromClassOptions, + BindingKey, + BindingScope, + config, + Constructor, + Context, + ContextTags, + createBindingFromClass, + filterByTag, + inject, + lifeCycleObserver, + Server, +} from '@loopback/core'; +import {HttpOptions, HttpServer} from '@loopback/http-server'; +import {ContextFunction} from 'apollo-server-core'; +import {ApolloServer, ApolloServerExpressConfig} from 'apollo-server-express'; +import {ExpressContext} from 'apollo-server-express/dist/ApolloServer'; +import express from 'express'; +import { + AuthChecker, + buildSchema, + NonEmptyArray, + ResolverInterface, +} from 'type-graphql'; +import {Middleware} from 'type-graphql/dist/interfaces/Middleware'; +import {LoopBackContainer} from './graphql.container'; +import {GraphQLBindings, GraphQLTags} from './keys'; + +export {ContextFunction} from 'apollo-server-core'; +export {ApolloServerExpressConfig} from 'apollo-server-express'; +export {ExpressContext} from 'apollo-server-express/dist/ApolloServer'; +export {Middleware as GraphQLMiddleware} from 'type-graphql/dist/interfaces/Middleware'; + +/** + * Options for GraphQL server + */ +export interface GraphQLServerOptions extends HttpOptions { + /** + * GraphQL related configuration + */ + graphql?: ApolloServerExpressConfig; + /** + * Express settings + */ + express?: Record; + /** + * Use as a middleware for RestServer instead of a standalone server + */ + asMiddlewareOnly?: boolean; +} + +/** + * GraphQL Server + */ +@lifeCycleObserver('server', { + scope: BindingScope.SINGLETON, + tags: {[ContextTags.KEY]: GraphQLBindings.GRAPHQL_SERVER}, +}) +export class GraphQLServer extends Context implements Server { + readonly httpServer?: HttpServer; + readonly expressApp: express.Application; + + constructor( + @config() private options: GraphQLServerOptions = {}, + @inject.context() + parent?: Context, + ) { + super(parent, 'graphql-server'); + this.expressApp = express(); + if (options.express) { + for (const p in options.express) { + this.expressApp.set(p, options.express[p]); + } + } + + if (!options.asMiddlewareOnly) { + this.httpServer = new HttpServer(this.expressApp, this.options); + } + } + + /** + * Get a list of resolver classes + */ + getResolvers(): Constructor>[] { + const view = this.createView(filterByTag(GraphQLTags.RESOLVER)); + return view.bindings + .filter(b => b.valueConstructor != null) + .map(b => b.valueConstructor as Constructor>); + } + + /** + * Get a list of middleware + */ + async getMiddleware(): Promise[]> { + const view = this.createView>( + filterByTag(GraphQLTags.MIDDLEWARE), + ); + return view.values(); + } + + /** + * Register a GraphQL middleware + * @param middleware - GraphQL middleware + */ + middleware(middleware: Middleware): Binding> { + return this.bind>(BindingKey.generate(`graphql.middleware`)) + .to(middleware) + .tag(GraphQLTags.MIDDLEWARE); + } + + /** + * Register a GraphQL resolver class + * @param resolverClass -GraphQL resolver class + * @param nameOrOptions - Resolver name or binding options + */ + resolver( + resolverClass: Constructor>, + nameOrOptions?: string | BindingFromClassOptions, + ) { + return registerResolver(this, resolverClass, nameOrOptions); + } + + async start() { + const resolverClasses = (this.getResolvers() as unknown) as NonEmptyArray< + Function + >; + + const authChecker: AuthChecker = + (await this.get(GraphQLBindings.GRAPHQL_AUTH_CHECKER, { + optional: true, + })) ?? ((resolverData, roles) => true); + + // build TypeGraphQL executable schema + const schema = await buildSchema({ + // See https://github.com/MichalLytek/type-graphql/issues/150#issuecomment-420181526 + validate: false, + resolvers: resolverClasses, + // automatically create `schema.gql` file with schema definition in current folder + // emitSchemaFile: path.resolve(__dirname, 'schema.gql'), + container: new LoopBackContainer(this), + authChecker, + globalMiddlewares: await this.getMiddleware(), + }); + + // Allow a graphql context resolver to be bound to GRAPHQL_CONTEXT_RESOLVER + const graphqlContextResolver: ContextFunction = + (await this.get(GraphQLBindings.GRAPHQL_CONTEXT_RESOLVER, { + optional: true, + })) ?? (context => context); + + const serverConfig: ApolloServerExpressConfig = { + // enable GraphQL Playground + playground: true, + context: graphqlContextResolver, + ...this.options.graphql, + schema, + }; + // Create GraphQL server + const graphQLServer = new ApolloServer(serverConfig); + + graphQLServer.applyMiddleware({app: this.expressApp}); + + await this.httpServer?.start(); + } + + async stop() { + await this.httpServer?.stop(); + } + + get listening() { + return !!this.httpServer?.listening; + } +} + +/** + * Register a GraphQL resolver class + * @param ctx - Context object + * @param resolverClass - Resolver class + * @param nameOrOptions - Resolver name or binding options + */ +export function registerResolver( + ctx: Context, + resolverClass: Constructor, + nameOrOptions?: string | BindingFromClassOptions, +): Binding { + const binding = createBindingFromClass(resolverClass, { + namespace: GraphQLBindings.RESOLVERS, + ...toOptions(nameOrOptions), + }).tag(GraphQLTags.RESOLVER); + ctx.add(binding); + return binding; +} + +/** + * Normalize name or options to `BindingFromClassOptions` + * @param nameOrOptions - Name or options for binding from class + */ +function toOptions(nameOrOptions?: string | BindingFromClassOptions) { + if (typeof nameOrOptions === 'string') { + return {name: nameOrOptions}; + } + return nameOrOptions ?? {}; +} diff --git a/extensions/graphql/src/index.ts b/extensions/graphql/src/index.ts new file mode 100644 index 000000000000..3e0d9582be46 --- /dev/null +++ b/extensions/graphql/src/index.ts @@ -0,0 +1,12 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from 'type-graphql'; +export * from './decorators'; +export * from './graphql.component'; +export * from './graphql.container'; +export * from './graphql.server'; +export * from './keys'; +export * from './types'; diff --git a/extensions/graphql/src/keys.ts b/extensions/graphql/src/keys.ts new file mode 100644 index 000000000000..82c8d8367630 --- /dev/null +++ b/extensions/graphql/src/keys.ts @@ -0,0 +1,81 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey, Constructor} from '@loopback/core'; +import {AuthChecker, ResolverData} from 'type-graphql'; +import {GraphQLComponent} from './graphql.component'; +import {ContextFunction, ExpressContext, GraphQLServer} from './graphql.server'; + +/** + * Namespace for GraphQL related bindings + */ +export namespace GraphQLBindings { + /** + * Binding key for the GraphQL server + */ + export const GRAPHQL_SERVER = BindingKey.create( + 'servers.GraphQLServer', + ); + + /** + * Binding key for the GraphQL component + */ + export const COMPONENT = BindingKey.create( + 'components.GraphQLComponent', + ); + + /** + * Binding key for the GraphQL context resolver + */ + export const GRAPHQL_CONTEXT_RESOLVER = BindingKey.create< + ContextFunction + >('graphql.contextResolver'); + + /** + * Binding key for the GraphQL auth checker + */ + export const GRAPHQL_AUTH_CHECKER = BindingKey.create( + 'graphql.authChecker', + ); + + /** + * Binding key for the GraphQL resolver data - which is bound per request + */ + export const RESOLVER_DATA = BindingKey.create>( + 'graphql.resolverData', + ); + + /** + * Binding key for the current resolver class + */ + export const RESOLVER_CLASS = BindingKey.create>( + 'graphql.resolverClass', + ); + + /** + * Binding key namespace for resolvers + */ + export const RESOLVERS = 'graphql.resolvers'; +} + +/** + * Namespace for GraphQL related tags + */ +export namespace GraphQLTags { + /** + * GraphQL + */ + export const GRAPHQL = 'graphql'; + + /** + * Tag for GraphQL resolver bindings + */ + export const RESOLVER = 'graphql.resolver'; + + /** + * Tag for GraphQL middleware bindings + */ + export const MIDDLEWARE = 'graphql.middleware'; +} diff --git a/extensions/graphql/src/types.ts b/extensions/graphql/src/types.ts new file mode 100644 index 000000000000..89d4eef7c427 --- /dev/null +++ b/extensions/graphql/src/types.ts @@ -0,0 +1,13 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export {Float, ID, Int, ResolverInterface} from 'type-graphql'; + +/** + * Options for GraphQL component + */ +export interface GraphQLComponentOptions { + // To be added +} diff --git a/extensions/graphql/tsconfig.json b/extensions/graphql/tsconfig.json new file mode 100644 index 000000000000..c7e8ca266bea --- /dev/null +++ b/extensions/graphql/tsconfig.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../packages/boot/tsconfig.json" + }, + { + "path": "../../packages/core/tsconfig.json" + }, + { + "path": "../../packages/http-server/tsconfig.json" + }, + { + "path": "../../packages/metadata/tsconfig.json" + }, + { + "path": "../../packages/repository/tsconfig.json" + }, + { + "path": "../../packages/rest/tsconfig.json" + }, + { + "path": "../../packages/testlab/tsconfig.json" + } + ] +} diff --git a/extensions/graphql/type-graphql.png b/extensions/graphql/type-graphql.png new file mode 100644 index 000000000000..db393e50baa0 Binary files /dev/null and b/extensions/graphql/type-graphql.png differ diff --git a/tsconfig.json b/tsconfig.json index 9335eaa67e3b..ba60b330e74c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,9 @@ { "path": "examples/file-transfer/tsconfig.json" }, + { + "path": "examples/graphql/tsconfig.json" + }, { "path": "examples/greeter-extension/tsconfig.json" }, @@ -94,6 +97,9 @@ { "path": "extensions/cron/tsconfig.json" }, + { + "path": "extensions/graphql/tsconfig.json" + }, { "path": "extensions/health/tsconfig.json" },