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
+
+
+
+
+
+
+
+
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