├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .huskyrc.json ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── bob-esbuild.config.ts ├── examples ├── logging │ ├── README.md │ ├── index.js │ ├── package.json │ └── yarn.lock └── permissions │ ├── README.md │ ├── index.js │ ├── package.json │ └── yarn.lock ├── jest.config.js ├── media ├── idea.png └── logo.png ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js ├── renovate.json ├── src ├── applicator.ts ├── constructors.ts ├── fragments.ts ├── generator.ts ├── index.ts ├── middleware.ts ├── types.ts ├── utils.ts └── validation.ts ├── tests ├── core.test.ts ├── execution.test.ts ├── fragments.test.ts ├── generator.test.ts ├── immutability.test.ts ├── integration.test.ts └── validation.test.ts ├── tsconfig.json └── tsconfig.test.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: maticzav 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 45 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 10 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - kind/feature 8 | - reproduction-available 9 | - help-wanted 10 | # Label to use when marking an issue as stale 11 | staleLabel: stale 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: > 14 | This issue has been automatically marked as stale because it has not had 15 | recent activity. It will be closed if no further activity occurs. Thank you 16 | for your contributions. 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false 19 | # Limit to only `issues` 20 | only: issues 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | name: release 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Setup 14 | - uses: actions/checkout@master 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '14.x' 18 | - run: npm i -g pnpm@7 19 | - run: pnpm i 20 | # Publish 21 | - run: pnpm compile 22 | - run: pnpm test 23 | env: 24 | GH_TOKEN: ${{ secrets.GH_SPONSORS_TOKEN }} 25 | - run: pnpm coverage 26 | # This alias makes semantic-release to use pnpm when publishing 27 | # Which adds support for "publishConfig.directory" 28 | - run: alias npm=pnpm 29 | - run: npx semantic-release 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: release 8 | runs-on: ubuntu-latest 9 | steps: 10 | # Setup 11 | - uses: actions/checkout@master 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '14.x' 15 | - run: npm i -g pnpm@7 16 | - run: pnpm install 17 | # Test 18 | - run: pnpm compile 19 | - run: pnpm test 20 | env: 21 | GH_TOKEN: ${{ secrets.GH_SPONSORS_TOKEN }} 22 | - run: pnpm coverage 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .idea 4 | .DS_Store 5 | *.log* 6 | package-lock.json 7 | coverage -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "pretty-quick --staged" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "JS examples debug", 8 | "program": "${workspaceFolder}/examples/authorization/index.js", 9 | "skipFiles": ["/**/*.js"] 10 | }, 11 | { 12 | "type": "node", 13 | "request": "launch", 14 | "name": "AVA", 15 | "program": "${workspaceRoot}/node_modules/ava/profile.js", 16 | "args": ["--serial", "${file}"], 17 | "skipFiles": ["/**/*.js"] 18 | }, 19 | { 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Current TS File", 23 | "args": ["${relativeFile}"], 24 | "runtimeArgs": ["-r", "ts-node/register"], 25 | "cwd": "${workspaceRoot}", 26 | "protocol": "inspector", 27 | "internalConsoleOptions": "openOnSessionStart" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": false, 3 | "files.watcherExclude": { 4 | "**/.git/objects/**": true, 5 | "**/.git/subtree-cache/**": true, 6 | "**/node_modules/**": true 7 | }, 8 | "typescript.tsdk": "./node_modules/typescript/lib", 9 | "spellright.language": ["en"], 10 | "spellright.documentTypes": ["plaintext"], 11 | "editor.formatOnSave": true 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matic Zavadlal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # graphql-middleware 4 | 5 | [![Actions Status](https://github.com/maticzav/graphql-middleware/workflows/Test/badge.svg)](https://github.com/maticzav/graphql-middleware/actions) 6 | [![codecov](https://codecov.io/gh/maticzav/graphql-middleware/branch/master/graph/badge.svg?token=TuIfoaKhc5)](https://codecov.io/gh/maticzav/graphql-middleware) 7 | [![npm version](https://badge.fury.io/js/graphql-middleware.svg)](https://badge.fury.io/js/graphql-middleware) 8 | 9 | Split up your GraphQL resolvers in middleware functions. 10 | 11 | ## Overview 12 | 13 | GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently. 14 | 15 | - 💡 **Easy to use:** An intuitive, yet familiar API that you will pick up in a second. 16 | - 💪 **Powerful:** Allows complete control over your resolvers (Before, After). 17 | - 🌈 **Compatible:** Works with any GraphQL Schema. 18 | 19 | > **NOTE:** As of 3.0.0 `graphql-middleware` no longer wraps introspection queries. 20 | 21 | > **NOTE:** As of 5.0.0 `graphql-middleware` no longer supports GraphQL Yoga out of the box. We might bring back the support if the library becomes maintained again. We are keeping the docs as the reference for older versions. 22 | 23 | ## Install 24 | 25 | ```sh 26 | yarn add graphql-middleware 27 | ``` 28 | 29 | ## How does it work 30 | 31 | GraphQL Middleware lets you run arbitrary code before or after a resolver is invoked. It improves your code structure by enabling code reuse and a clear separation of concerns. 32 | 33 | ```ts 34 | const { ApolloServer } = require('apollo-server') 35 | const { makeExecutableSchema } = require('@graphql-tools/schema') 36 | 37 | const typeDefs = ` 38 | type Query { 39 | hello(name: String): String 40 | bye(name: String): String 41 | } 42 | ` 43 | const resolvers = { 44 | Query: { 45 | hello: (root, args, context, info) => { 46 | console.log(`3. resolver: hello`) 47 | return `Hello ${args.name ? args.name : 'world'}!` 48 | }, 49 | bye: (root, args, context, info) => { 50 | console.log(`3. resolver: bye`) 51 | return `Bye ${args.name ? args.name : 'world'}!` 52 | }, 53 | }, 54 | } 55 | 56 | const logInput = async (resolve, root, args, context, info) => { 57 | console.log(`1. logInput: ${JSON.stringify(args)}`) 58 | const result = await resolve(root, args, context, info) 59 | console.log(`5. logInput`) 60 | return result 61 | } 62 | 63 | const logResult = async (resolve, root, args, context, info) => { 64 | console.log(`2. logResult`) 65 | const result = await resolve(root, args, context, info) 66 | console.log(`4. logResult: ${JSON.stringify(result)}`) 67 | return result 68 | } 69 | 70 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 71 | 72 | const schemaWithMiddleware = applyMiddleware(schema, logInput, logResult) 73 | 74 | const server = new ApolloServer({ 75 | schema: schemaWithMiddleware, 76 | }) 77 | 78 | await server.listen({ port: 8008 }) 79 | ``` 80 | 81 | Execution of the middleware and resolver functions follow the "onion"-principle, meaning each middleware function adds a layer before and after the actual resolver invocation. 82 | 83 |

84 | 85 | > The order of the middleware functions in the middlewares array is important. The first resolver is the "most-outer" layer, so it gets executed first and last. The second resolver is the "second-outer" layer, so it gets executed second and second to last... And so forth. 86 | 87 | > You can read more about GraphQL Middleware in this fantastic [article](https://www.prisma.io/blog/graphql-middleware-zie3iphithxy/). 88 | 89 | ## Standalone usage 90 | 91 | ```ts 92 | const { ApolloServer } = require('apollo-server') 93 | const { makeExecutableSchema } = require('@graphql-tools/schema') 94 | 95 | // Minimal example middleware (before & after) 96 | const beepMiddleware = { 97 | Query: { 98 | hello: async (resolve, parent, args, context, info) => { 99 | // You can use middleware to override arguments 100 | const argsWithDefault = { name: 'Bob', ...args } 101 | const result = await resolve(parent, argsWithDefault, context, info) 102 | // Or change the returned values of resolvers 103 | return result.replace(/Trump/g, 'beep') 104 | }, 105 | }, 106 | } 107 | 108 | const typeDefs = ` 109 | type Query { 110 | hello(name: String): String 111 | } 112 | ` 113 | const resolvers = { 114 | Query: { 115 | hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`, 116 | }, 117 | } 118 | 119 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 120 | 121 | const schemaWithMiddleware = applyMiddleware( 122 | schema, 123 | metricsMiddleware, 124 | authMiddleware, 125 | beepMiddleware, 126 | ) 127 | 128 | const server = new ApolloServer({ 129 | schema: schemaWithMiddleware, 130 | }) 131 | 132 | await server.listen({ port: 8008 }) 133 | ``` 134 | 135 | ### Usage with `graphql-yoga` 136 | 137 | > `graphql-yoga` has built-in support for `graphql-middleware`! 138 | 139 | ```ts 140 | import { GraphQLServer } from 'graphql-yoga' 141 | import { authMiddleware, metricsMiddleware } from './middleware' 142 | 143 | const typeDefs = ` 144 | type Query { 145 | hello(name: String): String 146 | } 147 | ` 148 | const resolvers = { 149 | Query: { 150 | hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`, 151 | }, 152 | } 153 | 154 | const server = new GraphQLServer({ 155 | typeDefs, 156 | resolvers, 157 | middlewares: [authMiddleware, metricsMiddleware], 158 | documentMiddleware: [], 159 | }) 160 | server.start(() => console.log('Server is running on localhost:4000')) 161 | ``` 162 | 163 | ## Awesome Middlewares [![Awesome](https://awesome.re/badge.svg)](https://awesome.re) 164 | 165 | - [graphql-shield](https://github.com/maticzav/graphql-shield) - Permissions as another layer of abstraction. 166 | - [graphql-middleware-apollo-upload-server](http://github.com/homeroom-live/graphql-middleware-apollo-upload-server) - Uploading files is hard, that's why this package manages it for you! 167 | - [graphql-middleware-sentry](https://github.com/maticzav/graphql-middleware-sentry) - Report your server errors to Sentry. 168 | - [graphql-middleware-forward-binding](https://github.com/maticzav/graphql-middleware-forward-binding) - GraphQL Binding forwardTo plugin for GraphQL Middleware. 169 | - [graphql-yup-middleware](https://github.com/JCMais/graphql-yup-middleware) - Use yup to validate mutation arguments 170 | - [graphql-pino-middleware](https://github.com/addityasingh/graphql-pino-middleware) - GraphQL middleware to augment resolvers with pino logger 171 | - [graphql-lightstep-middleware](https://github.com/addityasingh/graphql-lightstep-middleware) - GraphQL middleware to instrument resolvers with `lightstep` traces 172 | - [graphql-filter](https://github.com/hata6502/graphql-filter) - A GraphQL middleware to filter output data. 173 | 174 | ## API 175 | 176 | A middleware is a resolver function that wraps another resolver function. 177 | 178 | ```ts 179 | export declare type IMiddlewareResolver< 180 | TSource = any, 181 | TContext = any, 182 | TArgs = any 183 | > = ( 184 | resolve: Function, 185 | parent: TSource, 186 | args: TArgs, 187 | context: TContext, 188 | info: GraphQLResolveInfo, 189 | ) => Promise 190 | 191 | export interface IMiddlewareWithOptions< 192 | TSource = any, 193 | TContext = any, 194 | TArgs = any 195 | > { 196 | fragment?: IMiddlewareFragment 197 | fragments?: IMiddlewareFragment[] 198 | resolve?: IMiddlewareResolver 199 | } 200 | 201 | export type IMiddlewareFunction = 202 | | IMiddlewareWithOptions 203 | | IMiddlewareResolver 204 | 205 | interface IMiddlewareTypeMap { 206 | [key: string]: IMiddlewareFunction | IMiddlewareFieldMap 207 | } 208 | 209 | interface IMiddlewareFieldMap { 210 | [key: string]: IMiddlewareFunction 211 | } 212 | 213 | type IMiddleware = IMiddlewareFunction | IMiddlewareTypeMap 214 | 215 | function middleware( 216 | generator: (schema: GraphQLSchema) => IMiddleware, 217 | ): MiddlewareGenerator 218 | 219 | function applyMiddleware( 220 | schema: GraphQLSchema, 221 | ...middleware: (IMiddleware | MiddlewareGenerator)[] 222 | ): GraphQLSchema & { 223 | schema?: GraphQLSchema 224 | fragmentReplacements?: FragmentReplacement[] 225 | } 226 | 227 | /** 228 | * Applies middleware to a schema like `applyMiddleware` but only applies the 229 | * middleware to fields that have non-default resolvers. This method can be 230 | * useful if you want to report performance of only non-trivial methods. 231 | */ 232 | function applyMiddlewareToDeclaredResolvers( 233 | schema: GraphQLSchema, 234 | ...middleware: (IMiddleware | MiddlewareGenerator)[] 235 | ): GraphQLSchema & { 236 | schema?: GraphQLSchema 237 | fragmentReplacements?: FragmentReplacement[] 238 | } 239 | ``` 240 | 241 | ### Middleware Generator 242 | 243 | In some cases, your middleware could depend on how your schema looks. In such situations, you can turn your middleware into a middleware generator. Middleware generators are denoted with function `middleware` and receive `schema` as the first argument. 244 | 245 | ```ts 246 | const schemaDependentMiddleware = middleware((schema) => { 247 | return generateMiddlewareFromSchema(schema) 248 | }) 249 | 250 | const schemaWithMiddleware = applyMiddleware( 251 | schema, 252 | schemaDependentMiddleware, 253 | someOtherOptionalMiddleware, 254 | etc, 255 | ) 256 | ``` 257 | 258 | ### Middleware Fragments 259 | 260 | Fragments are a way of expressing what information your resolver requires to make sure it can execute correctly. They are primarily used in schema forwarding when the client might not always request all the fields your resolver demands. Because of that, we need to provide a way of telling what other information we need from a remote schema and that's why we use fragments. 261 | 262 | You can read more about fragments in the [`graphql-binding`](https://github.com/graphql-binding/graphql-binding) repository and on [`graphql-tools`](https://www.apollographql.com/docs/graphql-tools/schema-transforms.html#Other) documentation website. 263 | 264 | GraphQL Middleware provides a convenient way to quickly and easily add fragments to your middleware. This might turn out particularly useful when your middleware depends on resolver data. 265 | 266 | We've made fragments extremely flexible by using the general API which, if you have ever run over fragments, you probably already know. 267 | 268 | ```ts 269 | // Schema wide - gets applied to every field. 270 | const middlewareWithFragments = { 271 | fragment: `fragment NodeID on Node { id }`, 272 | resolve: (resolve, { id }, args, ctx, info) => { 273 | const foo = doSomethingWithID(id) 274 | return resolve(foo) 275 | }, 276 | } 277 | 278 | // Type wide - gets applied to every field of certain type. 279 | const middlewareWithFragments = { 280 | Query: { 281 | fragment: `fragment NodeID on Node { id }`, 282 | resolve: (resolve, { id }, args, ctx, info) => { 283 | const foo = doSomethingWithID(id) 284 | return resolve(foo) 285 | }, 286 | }, 287 | Mutation: { 288 | fragments: [ 289 | `fragment NodeID on Node { id }`, 290 | `fragment NodeSecret on Node { secret }`, 291 | ], 292 | resolve: (resolve, parent, args, ctx, info) => { 293 | return resolve(parent, customArgs) 294 | }, 295 | }, 296 | } 297 | 298 | // Field scoped - gets applied to particular field. 299 | const middlewareWithFragments = { 300 | Query: { 301 | node: { 302 | fragment: `fragment NodeID on Node { id }`, 303 | resolve: (resolve, { id }, args, ctx, info) => { 304 | const foo = doSomethingWithID(id) 305 | return resolve(foo) 306 | }, 307 | }, 308 | books: (resolve, parent, args, ctx, info) => { 309 | return resolve(parent, customArgs) 310 | }, 311 | }, 312 | } 313 | 314 | const { schema, fragmentReplacements } = applyMiddleware( 315 | schema, 316 | middlewareWithFragments, 317 | someOtherMiddleware, 318 | ) 319 | ``` 320 | 321 | > `graphql-middleware` automatically merges fragments from multiple middlewares if possible. Otherwise, validation function throws an error. 322 | 323 | ## GraphQL Middleware Use Cases 324 | 325 | - Logging 326 | - Metrics 327 | - Input sanitisation 328 | - Performance measurement 329 | - Authorization 330 | - Caching 331 | - Tracing 332 | 333 | ## FAQ 334 | 335 | ### Can I use GraphQL Middleware without GraphQL Yoga? 336 | 337 | Yes. Nevertheless, we encourage you to use it in combination with Yoga. Combining the power of `middlewares` that GraphQL Middleware offers, with `documentMiddleware` which Yoga exposes, gives you unparalleled control over the execution of your queries. 338 | 339 | ### How does GraphQL Middleware compare to `directives`? 340 | 341 | GraphQL Middleware and `directives` tackle the same problem in a completely different way. GraphQL Middleware allows you to implement all your middleware logic in your code, whereas directives encourage you to mix schema with your functionality. 342 | 343 | ### Should I modify the context using GraphQL Middleware? 344 | 345 | GraphQL Middleware allows you to modify the context of your resolvers, but we encourage you to use GraphQL Yoga's `documentMiddleware` for this functionality instead. 346 | 347 | ## Thank you 348 | 349 | Thanks to everyone who supported the development of this project. It's an honor to lead a project that helps so many people. 350 | 351 | - [Prisma](http://github.com/prisma) - for sponsoring the project, 352 | - Johannes Schickling - for guiding the project development, and 353 | - everyone else who personally contributed to the project in one way or another. 354 | 355 | Thank you! :heart: 356 | -------------------------------------------------------------------------------- /bob-esbuild.config.ts: -------------------------------------------------------------------------------- 1 | export const config: import('bob-esbuild').BobConfig = { 2 | tsc: { 3 | dirs: ['.'], 4 | }, 5 | distDir: 'dist', 6 | verbose: true, 7 | singleBuild: true, 8 | } 9 | -------------------------------------------------------------------------------- /examples/logging/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Middleware - Logging example 2 | 3 | This example illustrates basic usage of GraphQL Middleware. The idea is to log every field that has been requested by user. 4 | 5 | ## Code 6 | 7 | > Mind the following parts 8 | 9 | ### Import 10 | 11 | This is where we import `graphql-middleware`. 12 | 13 | ```js 14 | const { applyMiddleware } = require('graphql-middleware') 15 | ``` 16 | 17 | ### Middleware 18 | 19 | Because we want every field of our schema to make a log once it's requested, we use `schema` wide middleware definition. 20 | 21 | ```js 22 | const logMiddleware = async (resolve, parent, args, ctx, info) => { 23 | console.log(args, info) 24 | return resolve() 25 | } 26 | ``` 27 | 28 | ### Applying middleware 29 | 30 | This is the part where we modify the schema to reflect the changed middleware introduce. 31 | 32 | ```js 33 | const analysedSchema = applyMiddleware(schema, logMiddleware) 34 | ``` 35 | 36 | ## License 37 | 38 | MIT 39 | -------------------------------------------------------------------------------- /examples/logging/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLServer } = require('graphql-yoga') 2 | 3 | // Schema 4 | 5 | const typeDefs = ` 6 | type Query { 7 | hello(name: String): String! 8 | bye(name: String): String! 9 | } 10 | ` 11 | 12 | const resolvers = { 13 | Query: { 14 | hello: (_, { name }) => { 15 | throw new Error('No hello!') 16 | }, 17 | bye: (_, { name }) => `Bye ${name || 'World'}`, 18 | }, 19 | } 20 | 21 | // Middleware 22 | 23 | const logMiddleware = async (resolve, parent, args, ctx, info) => { 24 | try { 25 | const res = await resolve() 26 | return res 27 | } catch (e) { 28 | console.log(e) 29 | } 30 | } 31 | 32 | const server = new GraphQLServer({ 33 | typeDefs: typeDefs, 34 | resolvers: resolvers, 35 | middlewares: [logMiddleware], 36 | context: req => ({ ...req }), 37 | }) 38 | 39 | server.start(() => console.log('Server is running on http://localhost:4000')) 40 | -------------------------------------------------------------------------------- /examples/logging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logging", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "graphql-yoga": "latest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/logging/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.0.0-beta.40": 6 | version "7.0.0-beta.44" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.44.tgz#ea5ad6c6fe9a2c1187b025bf42424d28050ee696" 8 | dependencies: 9 | core-js "^2.5.3" 10 | regenerator-runtime "^0.11.1" 11 | 12 | "@types/body-parser@*": 13 | version "1.16.8" 14 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.8.tgz#687ec34140624a3bec2b1a8ea9268478ae8f3be3" 15 | dependencies: 16 | "@types/express" "*" 17 | "@types/node" "*" 18 | 19 | "@types/cors@^2.8.3": 20 | version "2.8.3" 21 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.3.tgz#eaf6e476da0d36bee6b061a24d57e343ddce86d6" 22 | dependencies: 23 | "@types/express" "*" 24 | 25 | "@types/events@*": 26 | version "1.2.0" 27 | resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" 28 | 29 | "@types/express-serve-static-core@*": 30 | version "4.11.1" 31 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.11.1.tgz#f6f7212382d59b19d696677bcaa48a37280f5d45" 32 | dependencies: 33 | "@types/events" "*" 34 | "@types/node" "*" 35 | 36 | "@types/express@*", "@types/express@^4.11.1": 37 | version "4.11.1" 38 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.11.1.tgz#f99663b3ab32d04cb11db612ef5dd7933f75465b" 39 | dependencies: 40 | "@types/body-parser" "*" 41 | "@types/express-serve-static-core" "*" 42 | "@types/serve-static" "*" 43 | 44 | "@types/graphql@0.12.6": 45 | version "0.12.6" 46 | resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.12.6.tgz#3d619198585fcabe5f4e1adfb5cf5f3388c66c13" 47 | 48 | "@types/graphql@^0.13.0": 49 | version "0.13.0" 50 | resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.0.tgz#78a33a7f429a06a64714817d9130d578e0f35ecb" 51 | 52 | "@types/mime@*": 53 | version "2.0.0" 54 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" 55 | 56 | "@types/node@*": 57 | version "9.6.6" 58 | resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2" 59 | 60 | "@types/serve-static@*": 61 | version "1.13.1" 62 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.1.tgz#1d2801fa635d274cd97d4ec07e26b21b44127492" 63 | dependencies: 64 | "@types/express-serve-static-core" "*" 65 | "@types/mime" "*" 66 | 67 | "@types/zen-observable@^0.5.3": 68 | version "0.5.3" 69 | resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5" 70 | 71 | accepts@~1.3.5: 72 | version "1.3.5" 73 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" 74 | dependencies: 75 | mime-types "~2.1.18" 76 | negotiator "0.6.1" 77 | 78 | apollo-cache-control@^0.1.0: 79 | version "0.1.1" 80 | resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.1.1.tgz#173d14ceb3eb9e7cb53de7eb8b61bee6159d4171" 81 | dependencies: 82 | graphql-extensions "^0.0.x" 83 | 84 | apollo-link@^1.2.1: 85 | version "1.2.2" 86 | resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.2.tgz#54c84199b18ac1af8d63553a68ca389c05217a03" 87 | dependencies: 88 | "@types/graphql" "0.12.6" 89 | apollo-utilities "^1.0.0" 90 | zen-observable-ts "^0.8.9" 91 | 92 | apollo-server-core@^1.3.4, apollo-server-core@^1.3.5: 93 | version "1.3.5" 94 | resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.5.tgz#36da3a6ee52b92fc4bb5710d44ced6b8fafebe2d" 95 | dependencies: 96 | apollo-cache-control "^0.1.0" 97 | apollo-tracing "^0.1.0" 98 | graphql-extensions "^0.0.x" 99 | 100 | apollo-server-express@^1.3.4: 101 | version "1.3.5" 102 | resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-1.3.5.tgz#7cd010f7921356cd9b832032828aa0add36cb7ed" 103 | dependencies: 104 | apollo-server-core "^1.3.5" 105 | apollo-server-module-graphiql "^1.3.4" 106 | 107 | apollo-server-lambda@1.3.4: 108 | version "1.3.4" 109 | resolved "https://registry.yarnpkg.com/apollo-server-lambda/-/apollo-server-lambda-1.3.4.tgz#fc168891e0034e2884f259b573f6ed208abc2fc2" 110 | dependencies: 111 | apollo-server-core "^1.3.4" 112 | apollo-server-module-graphiql "^1.3.4" 113 | 114 | apollo-server-module-graphiql@^1.3.4: 115 | version "1.3.4" 116 | resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.3.4.tgz#50399b7c51b7267d0c841529f5173e5fc7304de4" 117 | 118 | apollo-tracing@^0.1.0: 119 | version "0.1.4" 120 | resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.1.4.tgz#5b8ae1b01526b160ee6e552a7f131923a9aedcc7" 121 | dependencies: 122 | graphql-extensions "~0.0.9" 123 | 124 | apollo-upload-server@^5.0.0: 125 | version "5.0.0" 126 | resolved "https://registry.yarnpkg.com/apollo-upload-server/-/apollo-upload-server-5.0.0.tgz#c953b523608313966e0c8444637f4ae8ef77d5bc" 127 | dependencies: 128 | "@babel/runtime" "^7.0.0-beta.40" 129 | busboy "^0.2.14" 130 | object-path "^0.11.4" 131 | 132 | apollo-utilities@^1.0.0, apollo-utilities@^1.0.1: 133 | version "1.0.11" 134 | resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.11.tgz#cd36bfa6e5c04eea2caf0c204a0f38a0ad550802" 135 | 136 | argparse@^1.0.7: 137 | version "1.0.10" 138 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 139 | dependencies: 140 | sprintf-js "~1.0.2" 141 | 142 | array-flatten@1.1.1: 143 | version "1.1.1" 144 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 145 | 146 | async-limiter@~1.0.0: 147 | version "1.0.0" 148 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 149 | 150 | aws-lambda@^0.1.2: 151 | version "0.1.2" 152 | resolved "https://registry.yarnpkg.com/aws-lambda/-/aws-lambda-0.1.2.tgz#19b1585075df31679597b976a5f1def61f12ccee" 153 | dependencies: 154 | aws-sdk "^*" 155 | commander "^2.5.0" 156 | dotenv "^0.4.0" 157 | 158 | aws-sdk@^*: 159 | version "2.226.1" 160 | resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.226.1.tgz#8e6e2b466aaf26cb35714905d5f934e6c8317a99" 161 | dependencies: 162 | buffer "4.9.1" 163 | events "1.1.1" 164 | ieee754 "1.1.8" 165 | jmespath "0.15.0" 166 | querystring "0.2.0" 167 | sax "1.2.1" 168 | url "0.10.3" 169 | uuid "3.1.0" 170 | xml2js "0.4.17" 171 | xmlbuilder "4.2.1" 172 | 173 | backo2@^1.0.2: 174 | version "1.0.2" 175 | resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" 176 | 177 | balanced-match@^1.0.0: 178 | version "1.0.0" 179 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 180 | 181 | base64-js@^1.0.2: 182 | version "1.3.0" 183 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" 184 | 185 | body-parser-graphql@1.0.0: 186 | version "1.0.0" 187 | resolved "https://registry.yarnpkg.com/body-parser-graphql/-/body-parser-graphql-1.0.0.tgz#997de1792ed222cbc4845d404f4549eb88ec6d37" 188 | dependencies: 189 | body-parser "^1.18.2" 190 | 191 | body-parser@1.18.2, body-parser@^1.18.2: 192 | version "1.18.2" 193 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" 194 | dependencies: 195 | bytes "3.0.0" 196 | content-type "~1.0.4" 197 | debug "2.6.9" 198 | depd "~1.1.1" 199 | http-errors "~1.6.2" 200 | iconv-lite "0.4.19" 201 | on-finished "~2.3.0" 202 | qs "6.5.1" 203 | raw-body "2.3.2" 204 | type-is "~1.6.15" 205 | 206 | brace-expansion@^1.1.7: 207 | version "1.1.11" 208 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 209 | dependencies: 210 | balanced-match "^1.0.0" 211 | concat-map "0.0.1" 212 | 213 | buffer@4.9.1: 214 | version "4.9.1" 215 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" 216 | dependencies: 217 | base64-js "^1.0.2" 218 | ieee754 "^1.1.4" 219 | isarray "^1.0.0" 220 | 221 | busboy@^0.2.14: 222 | version "0.2.14" 223 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" 224 | dependencies: 225 | dicer "0.2.5" 226 | readable-stream "1.1.x" 227 | 228 | bytes@3.0.0: 229 | version "3.0.0" 230 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 231 | 232 | commander@^2.5.0: 233 | version "2.15.1" 234 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 235 | 236 | concat-map@0.0.1: 237 | version "0.0.1" 238 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 239 | 240 | content-disposition@0.5.2: 241 | version "0.5.2" 242 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 243 | 244 | content-type@~1.0.4: 245 | version "1.0.4" 246 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 247 | 248 | cookie-signature@1.0.6: 249 | version "1.0.6" 250 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 251 | 252 | cookie@0.3.1: 253 | version "0.3.1" 254 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 255 | 256 | core-js@^2.5.3: 257 | version "2.5.5" 258 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b" 259 | 260 | core-util-is@~1.0.0: 261 | version "1.0.2" 262 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 263 | 264 | cors@^2.8.4: 265 | version "2.8.4" 266 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686" 267 | dependencies: 268 | object-assign "^4" 269 | vary "^1" 270 | 271 | cross-fetch@2.0.0: 272 | version "2.0.0" 273 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.0.0.tgz#a17475449561e0f325146cea636a8619efb9b382" 274 | dependencies: 275 | node-fetch "2.0.0" 276 | whatwg-fetch "2.0.3" 277 | 278 | debug@2.6.9: 279 | version "2.6.9" 280 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 281 | dependencies: 282 | ms "2.0.0" 283 | 284 | depd@1.1.1: 285 | version "1.1.1" 286 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 287 | 288 | depd@~1.1.1, depd@~1.1.2: 289 | version "1.1.2" 290 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 291 | 292 | deprecated-decorator@^0.1.6: 293 | version "0.1.6" 294 | resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" 295 | 296 | destroy@~1.0.4: 297 | version "1.0.4" 298 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 299 | 300 | dicer@0.2.5: 301 | version "0.2.5" 302 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" 303 | dependencies: 304 | readable-stream "1.1.x" 305 | streamsearch "0.1.2" 306 | 307 | dotenv@^0.4.0: 308 | version "0.4.0" 309 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-0.4.0.tgz#f6fb351363c2d92207245c737802c9ab5ae1495a" 310 | 311 | ee-first@1.1.1: 312 | version "1.1.1" 313 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 314 | 315 | encodeurl@~1.0.2: 316 | version "1.0.2" 317 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 318 | 319 | escape-html@~1.0.3: 320 | version "1.0.3" 321 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 322 | 323 | esprima@^4.0.0: 324 | version "4.0.1" 325 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 326 | 327 | etag@~1.8.1: 328 | version "1.8.1" 329 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 330 | 331 | eventemitter3@^2.0.3: 332 | version "2.0.3" 333 | resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" 334 | 335 | events@1.1.1: 336 | version "1.1.1" 337 | resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" 338 | 339 | express@^4.16.3: 340 | version "4.16.3" 341 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" 342 | dependencies: 343 | accepts "~1.3.5" 344 | array-flatten "1.1.1" 345 | body-parser "1.18.2" 346 | content-disposition "0.5.2" 347 | content-type "~1.0.4" 348 | cookie "0.3.1" 349 | cookie-signature "1.0.6" 350 | debug "2.6.9" 351 | depd "~1.1.2" 352 | encodeurl "~1.0.2" 353 | escape-html "~1.0.3" 354 | etag "~1.8.1" 355 | finalhandler "1.1.1" 356 | fresh "0.5.2" 357 | merge-descriptors "1.0.1" 358 | methods "~1.1.2" 359 | on-finished "~2.3.0" 360 | parseurl "~1.3.2" 361 | path-to-regexp "0.1.7" 362 | proxy-addr "~2.0.3" 363 | qs "6.5.1" 364 | range-parser "~1.2.0" 365 | safe-buffer "5.1.1" 366 | send "0.16.2" 367 | serve-static "1.13.2" 368 | setprototypeof "1.1.0" 369 | statuses "~1.4.0" 370 | type-is "~1.6.16" 371 | utils-merge "1.0.1" 372 | vary "~1.1.2" 373 | 374 | finalhandler@1.1.1: 375 | version "1.1.1" 376 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" 377 | dependencies: 378 | debug "2.6.9" 379 | encodeurl "~1.0.2" 380 | escape-html "~1.0.3" 381 | on-finished "~2.3.0" 382 | parseurl "~1.3.2" 383 | statuses "~1.4.0" 384 | unpipe "~1.0.0" 385 | 386 | forwarded@~0.1.2: 387 | version "0.1.2" 388 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 389 | 390 | fresh@0.5.2: 391 | version "0.5.2" 392 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 393 | 394 | graphql-config@2.0.0: 395 | version "2.0.0" 396 | resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-2.0.0.tgz#daf69091055c6f675d63893a2d14c48f3fec3327" 397 | dependencies: 398 | graphql-import "^0.4.0" 399 | graphql-request "^1.4.0" 400 | js-yaml "^3.10.0" 401 | lodash "^4.17.4" 402 | minimatch "^3.0.4" 403 | 404 | graphql-extensions@^0.0.x, graphql-extensions@~0.0.9: 405 | version "0.0.10" 406 | resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.0.10.tgz#34bdb2546d43f6a5bc89ab23c295ec0466c6843d" 407 | dependencies: 408 | core-js "^2.5.3" 409 | source-map-support "^0.5.1" 410 | 411 | graphql-import@^0.4.0: 412 | version "0.4.5" 413 | resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.4.5.tgz#e2f18c28d335733f46df8e0733d8deb1c6e2a645" 414 | dependencies: 415 | lodash "^4.17.4" 416 | 417 | graphql-import@^0.5.0: 418 | version "0.5.0" 419 | resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.5.0.tgz#5f678a6d4636d02a829308884aa1f2fa2197f06d" 420 | dependencies: 421 | lodash "^4.17.4" 422 | 423 | graphql-playground-html@1.5.5: 424 | version "1.5.5" 425 | resolved "https://registry.yarnpkg.com/graphql-playground-html/-/graphql-playground-html-1.5.5.tgz#e2aca543eb66b435ead495b45244b2604d6b2d48" 426 | dependencies: 427 | graphql-config "2.0.0" 428 | 429 | graphql-playground-middleware-express@1.6.1: 430 | version "1.6.1" 431 | resolved "https://registry.yarnpkg.com/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.6.1.tgz#d6287d124a1c55845a52a7d727c371da99cdf0b0" 432 | dependencies: 433 | graphql-playground-html "1.5.5" 434 | 435 | graphql-playground-middleware-lambda@1.5.0: 436 | version "1.5.0" 437 | resolved "https://registry.yarnpkg.com/graphql-playground-middleware-lambda/-/graphql-playground-middleware-lambda-1.5.0.tgz#41a06faf185103660a324257c5293e9f50839fce" 438 | dependencies: 439 | graphql-playground-html "1.5.5" 440 | 441 | graphql-request@^1.4.0: 442 | version "1.5.1" 443 | resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.5.1.tgz#cccdf5cce6432ca062b90f7b63793c77c821ff9a" 444 | dependencies: 445 | cross-fetch "2.0.0" 446 | 447 | graphql-subscriptions@^0.5.8: 448 | version "0.5.8" 449 | resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.5.8.tgz#13a6143c546bce390404657dc73ca501def30aa7" 450 | dependencies: 451 | iterall "^1.2.1" 452 | 453 | graphql-tools@^2.23.1: 454 | version "2.24.0" 455 | resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-2.24.0.tgz#bbacaad03924012a0edb8735a5e65df5d5563675" 456 | dependencies: 457 | apollo-link "^1.2.1" 458 | apollo-utilities "^1.0.1" 459 | deprecated-decorator "^0.1.6" 460 | iterall "^1.1.3" 461 | uuid "^3.1.0" 462 | 463 | graphql-yoga@latest: 464 | version "1.8.5" 465 | resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-1.8.5.tgz#e06c77fbfd6a186afeaf21baf9617bdf8636d502" 466 | dependencies: 467 | "@types/cors" "^2.8.3" 468 | "@types/express" "^4.11.1" 469 | "@types/graphql" "^0.13.0" 470 | "@types/zen-observable" "^0.5.3" 471 | apollo-server-express "^1.3.4" 472 | apollo-server-lambda "1.3.4" 473 | apollo-upload-server "^5.0.0" 474 | aws-lambda "^0.1.2" 475 | body-parser-graphql "1.0.0" 476 | cors "^2.8.4" 477 | express "^4.16.3" 478 | graphql "^0.11.0 || ^0.12.0 || ^0.13.0" 479 | graphql-import "^0.5.0" 480 | graphql-playground-middleware-express "1.6.1" 481 | graphql-playground-middleware-lambda "1.5.0" 482 | graphql-subscriptions "^0.5.8" 483 | graphql-tools "^2.23.1" 484 | subscriptions-transport-ws "^0.9.7" 485 | 486 | "graphql@^0.11.0 || ^0.12.0 || ^0.13.0": 487 | version "0.13.2" 488 | resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" 489 | dependencies: 490 | iterall "^1.2.1" 491 | 492 | http-errors@1.6.2: 493 | version "1.6.2" 494 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 495 | dependencies: 496 | depd "1.1.1" 497 | inherits "2.0.3" 498 | setprototypeof "1.0.3" 499 | statuses ">= 1.3.1 < 2" 500 | 501 | http-errors@~1.6.2: 502 | version "1.6.3" 503 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" 504 | dependencies: 505 | depd "~1.1.2" 506 | inherits "2.0.3" 507 | setprototypeof "1.1.0" 508 | statuses ">= 1.4.0 < 2" 509 | 510 | iconv-lite@0.4.19: 511 | version "0.4.19" 512 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 513 | 514 | ieee754@1.1.8: 515 | version "1.1.8" 516 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" 517 | 518 | ieee754@^1.1.4: 519 | version "1.1.11" 520 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" 521 | 522 | inherits@2.0.3, inherits@~2.0.1: 523 | version "2.0.3" 524 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 525 | 526 | ipaddr.js@1.6.0: 527 | version "1.6.0" 528 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" 529 | 530 | isarray@0.0.1: 531 | version "0.0.1" 532 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 533 | 534 | isarray@^1.0.0: 535 | version "1.0.0" 536 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 537 | 538 | iterall@^1.1.3, iterall@^1.2.1: 539 | version "1.2.2" 540 | resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" 541 | 542 | jmespath@0.15.0: 543 | version "0.15.0" 544 | resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" 545 | 546 | js-yaml@^3.10.0: 547 | version "3.14.1" 548 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" 549 | dependencies: 550 | argparse "^1.0.7" 551 | esprima "^4.0.0" 552 | 553 | lodash.assign@^4.2.0: 554 | version "4.2.0" 555 | resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 556 | 557 | lodash.isobject@^3.0.2: 558 | version "3.0.2" 559 | resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" 560 | 561 | lodash.isstring@^4.0.1: 562 | version "4.0.1" 563 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 564 | 565 | lodash@^4.0.0, lodash@^4.17.4: 566 | version "4.17.21" 567 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 568 | 569 | media-typer@0.3.0: 570 | version "0.3.0" 571 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 572 | 573 | merge-descriptors@1.0.1: 574 | version "1.0.1" 575 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 576 | 577 | methods@~1.1.2: 578 | version "1.1.2" 579 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 580 | 581 | mime-db@~1.33.0: 582 | version "1.33.0" 583 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" 584 | 585 | mime-types@~2.1.18: 586 | version "2.1.18" 587 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" 588 | dependencies: 589 | mime-db "~1.33.0" 590 | 591 | mime@1.4.1: 592 | version "1.4.1" 593 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 594 | 595 | minimatch@^3.0.4: 596 | version "3.0.4" 597 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 598 | dependencies: 599 | brace-expansion "^1.1.7" 600 | 601 | ms@2.0.0: 602 | version "2.0.0" 603 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 604 | 605 | negotiator@0.6.1: 606 | version "0.6.1" 607 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 608 | 609 | node-fetch@2.0.0: 610 | version "2.0.0" 611 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.0.0.tgz#982bba43ecd4f2922a29cc186a6bbb0bb73fcba6" 612 | 613 | object-assign@^4: 614 | version "4.1.1" 615 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 616 | 617 | object-path@^0.11.4: 618 | version "0.11.5" 619 | resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a" 620 | 621 | on-finished@~2.3.0: 622 | version "2.3.0" 623 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 624 | dependencies: 625 | ee-first "1.1.1" 626 | 627 | parseurl@~1.3.2: 628 | version "1.3.2" 629 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" 630 | 631 | path-to-regexp@0.1.7: 632 | version "0.1.7" 633 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 634 | 635 | proxy-addr@~2.0.3: 636 | version "2.0.3" 637 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" 638 | dependencies: 639 | forwarded "~0.1.2" 640 | ipaddr.js "1.6.0" 641 | 642 | punycode@1.3.2: 643 | version "1.3.2" 644 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" 645 | 646 | qs@6.5.1: 647 | version "6.5.1" 648 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" 649 | 650 | querystring@0.2.0: 651 | version "0.2.0" 652 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" 653 | 654 | range-parser@~1.2.0: 655 | version "1.2.0" 656 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 657 | 658 | raw-body@2.3.2: 659 | version "2.3.2" 660 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" 661 | dependencies: 662 | bytes "3.0.0" 663 | http-errors "1.6.2" 664 | iconv-lite "0.4.19" 665 | unpipe "1.0.0" 666 | 667 | readable-stream@1.1.x: 668 | version "1.1.14" 669 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 670 | dependencies: 671 | core-util-is "~1.0.0" 672 | inherits "~2.0.1" 673 | isarray "0.0.1" 674 | string_decoder "~0.10.x" 675 | 676 | regenerator-runtime@^0.11.1: 677 | version "0.11.1" 678 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" 679 | 680 | safe-buffer@5.1.1, safe-buffer@~5.1.0: 681 | version "5.1.1" 682 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 683 | 684 | sax@1.2.1: 685 | version "1.2.1" 686 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" 687 | 688 | sax@>=0.6.0: 689 | version "1.2.4" 690 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 691 | 692 | send@0.16.2: 693 | version "0.16.2" 694 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" 695 | dependencies: 696 | debug "2.6.9" 697 | depd "~1.1.2" 698 | destroy "~1.0.4" 699 | encodeurl "~1.0.2" 700 | escape-html "~1.0.3" 701 | etag "~1.8.1" 702 | fresh "0.5.2" 703 | http-errors "~1.6.2" 704 | mime "1.4.1" 705 | ms "2.0.0" 706 | on-finished "~2.3.0" 707 | range-parser "~1.2.0" 708 | statuses "~1.4.0" 709 | 710 | serve-static@1.13.2: 711 | version "1.13.2" 712 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" 713 | dependencies: 714 | encodeurl "~1.0.2" 715 | escape-html "~1.0.3" 716 | parseurl "~1.3.2" 717 | send "0.16.2" 718 | 719 | setprototypeof@1.0.3: 720 | version "1.0.3" 721 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 722 | 723 | setprototypeof@1.1.0: 724 | version "1.1.0" 725 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 726 | 727 | source-map-support@^0.5.1: 728 | version "0.5.4" 729 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.4.tgz#54456efa89caa9270af7cd624cc2f123e51fbae8" 730 | dependencies: 731 | source-map "^0.6.0" 732 | 733 | source-map@^0.6.0: 734 | version "0.6.1" 735 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 736 | 737 | sprintf-js@~1.0.2: 738 | version "1.0.3" 739 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 740 | 741 | "statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": 742 | version "1.5.0" 743 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 744 | 745 | statuses@~1.4.0: 746 | version "1.4.0" 747 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 748 | 749 | streamsearch@0.1.2: 750 | version "0.1.2" 751 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 752 | 753 | string_decoder@~0.10.x: 754 | version "0.10.31" 755 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 756 | 757 | subscriptions-transport-ws@^0.9.7: 758 | version "0.9.8" 759 | resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.8.tgz#3a26ab96e06f78cf4ace8d083f6227fa55970947" 760 | dependencies: 761 | backo2 "^1.0.2" 762 | eventemitter3 "^2.0.3" 763 | iterall "^1.2.1" 764 | lodash.assign "^4.2.0" 765 | lodash.isobject "^3.0.2" 766 | lodash.isstring "^4.0.1" 767 | symbol-observable "^1.0.4" 768 | ws "^3.0.0" 769 | 770 | symbol-observable@^1.0.4: 771 | version "1.2.0" 772 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" 773 | 774 | type-is@~1.6.15, type-is@~1.6.16: 775 | version "1.6.16" 776 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" 777 | dependencies: 778 | media-typer "0.3.0" 779 | mime-types "~2.1.18" 780 | 781 | ultron@~1.1.0: 782 | version "1.1.1" 783 | resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" 784 | 785 | unpipe@1.0.0, unpipe@~1.0.0: 786 | version "1.0.0" 787 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 788 | 789 | url@0.10.3: 790 | version "0.10.3" 791 | resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" 792 | dependencies: 793 | punycode "1.3.2" 794 | querystring "0.2.0" 795 | 796 | utils-merge@1.0.1: 797 | version "1.0.1" 798 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 799 | 800 | uuid@3.1.0: 801 | version "3.1.0" 802 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 803 | 804 | uuid@^3.1.0: 805 | version "3.2.1" 806 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" 807 | 808 | vary@^1, vary@~1.1.2: 809 | version "1.1.2" 810 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 811 | 812 | whatwg-fetch@2.0.3: 813 | version "2.0.3" 814 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 815 | 816 | ws@^3.0.0: 817 | version "3.3.3" 818 | resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" 819 | dependencies: 820 | async-limiter "~1.0.0" 821 | safe-buffer "~5.1.0" 822 | ultron "~1.1.0" 823 | 824 | xml2js@0.4.17: 825 | version "0.4.17" 826 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" 827 | dependencies: 828 | sax ">=0.6.0" 829 | xmlbuilder "^4.1.0" 830 | 831 | xmlbuilder@4.2.1, xmlbuilder@^4.1.0: 832 | version "4.2.1" 833 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" 834 | dependencies: 835 | lodash "^4.0.0" 836 | 837 | zen-observable-ts@^0.8.9: 838 | version "0.8.9" 839 | resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz#d3c97af08c0afdca37ebcadf7cc3ee96bda9bab1" 840 | dependencies: 841 | zen-observable "^0.8.0" 842 | 843 | zen-observable@^0.8.0: 844 | version "0.8.8" 845 | resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.8.tgz#1ea93995bf098754a58215a1e0a7309e5749ec42" 846 | -------------------------------------------------------------------------------- /examples/permissions/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Middleware - Permissions example 2 | 3 | This example illustrates how to use GraphQL Middleware to handle user permissions. Do take into consideration that this is a low level implementation with no optimizations. We recommend using `graphql-shield` for production usage. 4 | 5 | ## Code 6 | 7 | > Mind the following parts 8 | 9 | ### Import 10 | 11 | This is where we import `graphql-middleware`. 12 | 13 | ```js 14 | const { applyMiddleware } = require('graphql-middleware') 15 | ``` 16 | 17 | ### Permission function 18 | 19 | This is where we decide whether the user should or shouldn't access the information. The following implementation preassumes that the secret is passed as the query header using `Authorization: ` format. 20 | 21 | ```js 22 | const isLoggedIn = async (resolve, parent, args, ctx, info) => { 23 | // Include your agent code as Authorization: header. 24 | const permit = ctx.request.get('Authorization') === code 25 | 26 | if (!permit) { 27 | throw new Error(`Not authorised!`) 28 | } 29 | 30 | return resolve() 31 | } 32 | ``` 33 | 34 | ### Middleware 35 | 36 | The following middleware implements one field-scoped and one type-scoped middleware. We use `field` scoped middleware with `secured` field to ensure only `secured` field of `Query` requires authorisation. Furthermore, we also use `type` middleware to make sure every field of `Me` type requires authorisation. 37 | 38 | There is no need to apply permissions to `me` field of `Query` as requesting any of type `Me` fields already requires authentication. 39 | 40 | ```js 41 | const permissions = { 42 | Query: { 43 | secured: isLoggedIn, 44 | }, 45 | Me: isLoggedIn, 46 | } 47 | ``` 48 | 49 | ### Applying middleware 50 | 51 | This is the part where we modify the schema to reflect the changed middleware introduce. 52 | 53 | ```js 54 | const protectedSchema = applyMiddleware(schema, permissions) 55 | ``` 56 | 57 | ## License 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /examples/permissions/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLServer } = require('graphql-yoga') 2 | 3 | // Schema 4 | 5 | const code = 'supersecret' 6 | 7 | const typeDefs = ` 8 | type Query { 9 | open: String! 10 | secured: String! 11 | me: Me! 12 | } 13 | 14 | type Me { 15 | name: String! 16 | surname: String! 17 | age: Int! 18 | } 19 | ` 20 | 21 | const resolvers = { 22 | Query: { 23 | open: () => `Open data, everyone's welcome!`, 24 | secured: () => `Personal diary - this is for my eyes only!`, 25 | me: () => ({}), 26 | }, 27 | Me: { 28 | name: () => 'Ben', 29 | surname: () => 'Cool', 30 | age: () => 18, 31 | }, 32 | } 33 | 34 | // Middleware - Permissions 35 | 36 | const isLoggedIn = async (resolve, parent, args, ctx, info) => { 37 | // Include your agent code as Authorization: header. 38 | const permit = ctx.request.get('Authorization') === code 39 | 40 | if (!permit) { 41 | throw new Error(`Not authorised!`) 42 | } 43 | 44 | return resolve() 45 | } 46 | 47 | const permissions = { 48 | Query: { 49 | secured: isLoggedIn, 50 | }, 51 | Me: isLoggedIn, 52 | } 53 | 54 | // Server 55 | 56 | const server = new GraphQLServer({ 57 | typeDefs: typeDefs, 58 | resolvers: resolvers, 59 | middlewares: [permissions], 60 | context: req => ({ ...req }), 61 | }) 62 | 63 | server.start(() => console.log('Server is running on http://localhost:4000')) 64 | -------------------------------------------------------------------------------- /examples/permissions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "permissions", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "graphql-yoga": "latest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/permissions/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.0.0-beta.40": 6 | version "7.0.0-beta.44" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.44.tgz#ea5ad6c6fe9a2c1187b025bf42424d28050ee696" 8 | dependencies: 9 | core-js "^2.5.3" 10 | regenerator-runtime "^0.11.1" 11 | 12 | "@types/body-parser@*": 13 | version "1.16.8" 14 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.8.tgz#687ec34140624a3bec2b1a8ea9268478ae8f3be3" 15 | dependencies: 16 | "@types/express" "*" 17 | "@types/node" "*" 18 | 19 | "@types/cors@^2.8.3": 20 | version "2.8.3" 21 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.3.tgz#eaf6e476da0d36bee6b061a24d57e343ddce86d6" 22 | dependencies: 23 | "@types/express" "*" 24 | 25 | "@types/events@*": 26 | version "1.2.0" 27 | resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" 28 | 29 | "@types/express-serve-static-core@*": 30 | version "4.11.1" 31 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.11.1.tgz#f6f7212382d59b19d696677bcaa48a37280f5d45" 32 | dependencies: 33 | "@types/events" "*" 34 | "@types/node" "*" 35 | 36 | "@types/express@*", "@types/express@^4.11.1": 37 | version "4.11.1" 38 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.11.1.tgz#f99663b3ab32d04cb11db612ef5dd7933f75465b" 39 | dependencies: 40 | "@types/body-parser" "*" 41 | "@types/express-serve-static-core" "*" 42 | "@types/serve-static" "*" 43 | 44 | "@types/graphql@0.12.6": 45 | version "0.12.6" 46 | resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.12.6.tgz#3d619198585fcabe5f4e1adfb5cf5f3388c66c13" 47 | 48 | "@types/graphql@^0.13.0": 49 | version "0.13.0" 50 | resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.0.tgz#78a33a7f429a06a64714817d9130d578e0f35ecb" 51 | 52 | "@types/mime@*": 53 | version "2.0.0" 54 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" 55 | 56 | "@types/node@*": 57 | version "9.6.6" 58 | resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2" 59 | 60 | "@types/serve-static@*": 61 | version "1.13.1" 62 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.1.tgz#1d2801fa635d274cd97d4ec07e26b21b44127492" 63 | dependencies: 64 | "@types/express-serve-static-core" "*" 65 | "@types/mime" "*" 66 | 67 | "@types/zen-observable@^0.5.3": 68 | version "0.5.3" 69 | resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5" 70 | 71 | accepts@~1.3.5: 72 | version "1.3.5" 73 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" 74 | dependencies: 75 | mime-types "~2.1.18" 76 | negotiator "0.6.1" 77 | 78 | apollo-cache-control@^0.1.0: 79 | version "0.1.1" 80 | resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.1.1.tgz#173d14ceb3eb9e7cb53de7eb8b61bee6159d4171" 81 | dependencies: 82 | graphql-extensions "^0.0.x" 83 | 84 | apollo-link@^1.2.1: 85 | version "1.2.2" 86 | resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.2.tgz#54c84199b18ac1af8d63553a68ca389c05217a03" 87 | dependencies: 88 | "@types/graphql" "0.12.6" 89 | apollo-utilities "^1.0.0" 90 | zen-observable-ts "^0.8.9" 91 | 92 | apollo-server-core@^1.3.4, apollo-server-core@^1.3.5: 93 | version "1.3.5" 94 | resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.5.tgz#36da3a6ee52b92fc4bb5710d44ced6b8fafebe2d" 95 | dependencies: 96 | apollo-cache-control "^0.1.0" 97 | apollo-tracing "^0.1.0" 98 | graphql-extensions "^0.0.x" 99 | 100 | apollo-server-express@^1.3.4: 101 | version "1.3.5" 102 | resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-1.3.5.tgz#7cd010f7921356cd9b832032828aa0add36cb7ed" 103 | dependencies: 104 | apollo-server-core "^1.3.5" 105 | apollo-server-module-graphiql "^1.3.4" 106 | 107 | apollo-server-lambda@1.3.4: 108 | version "1.3.4" 109 | resolved "https://registry.yarnpkg.com/apollo-server-lambda/-/apollo-server-lambda-1.3.4.tgz#fc168891e0034e2884f259b573f6ed208abc2fc2" 110 | dependencies: 111 | apollo-server-core "^1.3.4" 112 | apollo-server-module-graphiql "^1.3.4" 113 | 114 | apollo-server-module-graphiql@^1.3.4: 115 | version "1.3.4" 116 | resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.3.4.tgz#50399b7c51b7267d0c841529f5173e5fc7304de4" 117 | 118 | apollo-tracing@^0.1.0: 119 | version "0.1.4" 120 | resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.1.4.tgz#5b8ae1b01526b160ee6e552a7f131923a9aedcc7" 121 | dependencies: 122 | graphql-extensions "~0.0.9" 123 | 124 | apollo-upload-server@^5.0.0: 125 | version "5.0.0" 126 | resolved "https://registry.yarnpkg.com/apollo-upload-server/-/apollo-upload-server-5.0.0.tgz#c953b523608313966e0c8444637f4ae8ef77d5bc" 127 | dependencies: 128 | "@babel/runtime" "^7.0.0-beta.40" 129 | busboy "^0.2.14" 130 | object-path "^0.11.4" 131 | 132 | apollo-utilities@^1.0.0, apollo-utilities@^1.0.1: 133 | version "1.0.11" 134 | resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.11.tgz#cd36bfa6e5c04eea2caf0c204a0f38a0ad550802" 135 | 136 | argparse@^1.0.7: 137 | version "1.0.10" 138 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 139 | dependencies: 140 | sprintf-js "~1.0.2" 141 | 142 | array-flatten@1.1.1: 143 | version "1.1.1" 144 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 145 | 146 | async-limiter@~1.0.0: 147 | version "1.0.0" 148 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 149 | 150 | aws-lambda@^0.1.2: 151 | version "0.1.2" 152 | resolved "https://registry.yarnpkg.com/aws-lambda/-/aws-lambda-0.1.2.tgz#19b1585075df31679597b976a5f1def61f12ccee" 153 | dependencies: 154 | aws-sdk "^*" 155 | commander "^2.5.0" 156 | dotenv "^0.4.0" 157 | 158 | aws-sdk@^*: 159 | version "2.226.1" 160 | resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.226.1.tgz#8e6e2b466aaf26cb35714905d5f934e6c8317a99" 161 | dependencies: 162 | buffer "4.9.1" 163 | events "1.1.1" 164 | ieee754 "1.1.8" 165 | jmespath "0.15.0" 166 | querystring "0.2.0" 167 | sax "1.2.1" 168 | url "0.10.3" 169 | uuid "3.1.0" 170 | xml2js "0.4.17" 171 | xmlbuilder "4.2.1" 172 | 173 | backo2@^1.0.2: 174 | version "1.0.2" 175 | resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" 176 | 177 | balanced-match@^1.0.0: 178 | version "1.0.0" 179 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 180 | 181 | base64-js@^1.0.2: 182 | version "1.3.0" 183 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" 184 | 185 | body-parser-graphql@1.0.0: 186 | version "1.0.0" 187 | resolved "https://registry.yarnpkg.com/body-parser-graphql/-/body-parser-graphql-1.0.0.tgz#997de1792ed222cbc4845d404f4549eb88ec6d37" 188 | dependencies: 189 | body-parser "^1.18.2" 190 | 191 | body-parser@1.18.2, body-parser@^1.18.2: 192 | version "1.18.2" 193 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" 194 | dependencies: 195 | bytes "3.0.0" 196 | content-type "~1.0.4" 197 | debug "2.6.9" 198 | depd "~1.1.1" 199 | http-errors "~1.6.2" 200 | iconv-lite "0.4.19" 201 | on-finished "~2.3.0" 202 | qs "6.5.1" 203 | raw-body "2.3.2" 204 | type-is "~1.6.15" 205 | 206 | brace-expansion@^1.1.7: 207 | version "1.1.11" 208 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 209 | dependencies: 210 | balanced-match "^1.0.0" 211 | concat-map "0.0.1" 212 | 213 | buffer@4.9.1: 214 | version "4.9.1" 215 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" 216 | dependencies: 217 | base64-js "^1.0.2" 218 | ieee754 "^1.1.4" 219 | isarray "^1.0.0" 220 | 221 | busboy@^0.2.14: 222 | version "0.2.14" 223 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" 224 | dependencies: 225 | dicer "0.2.5" 226 | readable-stream "1.1.x" 227 | 228 | bytes@3.0.0: 229 | version "3.0.0" 230 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 231 | 232 | commander@^2.5.0: 233 | version "2.15.1" 234 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 235 | 236 | concat-map@0.0.1: 237 | version "0.0.1" 238 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 239 | 240 | content-disposition@0.5.2: 241 | version "0.5.2" 242 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 243 | 244 | content-type@~1.0.4: 245 | version "1.0.4" 246 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 247 | 248 | cookie-signature@1.0.6: 249 | version "1.0.6" 250 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 251 | 252 | cookie@0.3.1: 253 | version "0.3.1" 254 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 255 | 256 | core-js@^2.5.3: 257 | version "2.5.5" 258 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b" 259 | 260 | core-util-is@~1.0.0: 261 | version "1.0.2" 262 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 263 | 264 | cors@^2.8.4: 265 | version "2.8.4" 266 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686" 267 | dependencies: 268 | object-assign "^4" 269 | vary "^1" 270 | 271 | cross-fetch@2.0.0: 272 | version "2.0.0" 273 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.0.0.tgz#a17475449561e0f325146cea636a8619efb9b382" 274 | dependencies: 275 | node-fetch "2.0.0" 276 | whatwg-fetch "2.0.3" 277 | 278 | debug@2.6.9: 279 | version "2.6.9" 280 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 281 | dependencies: 282 | ms "2.0.0" 283 | 284 | depd@1.1.1: 285 | version "1.1.1" 286 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 287 | 288 | depd@~1.1.1, depd@~1.1.2: 289 | version "1.1.2" 290 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 291 | 292 | deprecated-decorator@^0.1.6: 293 | version "0.1.6" 294 | resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" 295 | 296 | destroy@~1.0.4: 297 | version "1.0.4" 298 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 299 | 300 | dicer@0.2.5: 301 | version "0.2.5" 302 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" 303 | dependencies: 304 | readable-stream "1.1.x" 305 | streamsearch "0.1.2" 306 | 307 | dotenv@^0.4.0: 308 | version "0.4.0" 309 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-0.4.0.tgz#f6fb351363c2d92207245c737802c9ab5ae1495a" 310 | 311 | ee-first@1.1.1: 312 | version "1.1.1" 313 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 314 | 315 | encodeurl@~1.0.2: 316 | version "1.0.2" 317 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 318 | 319 | escape-html@~1.0.3: 320 | version "1.0.3" 321 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 322 | 323 | esprima@^4.0.0: 324 | version "4.0.1" 325 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 326 | 327 | etag@~1.8.1: 328 | version "1.8.1" 329 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 330 | 331 | eventemitter3@^2.0.3: 332 | version "2.0.3" 333 | resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" 334 | 335 | events@1.1.1: 336 | version "1.1.1" 337 | resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" 338 | 339 | express@^4.16.3: 340 | version "4.16.3" 341 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" 342 | dependencies: 343 | accepts "~1.3.5" 344 | array-flatten "1.1.1" 345 | body-parser "1.18.2" 346 | content-disposition "0.5.2" 347 | content-type "~1.0.4" 348 | cookie "0.3.1" 349 | cookie-signature "1.0.6" 350 | debug "2.6.9" 351 | depd "~1.1.2" 352 | encodeurl "~1.0.2" 353 | escape-html "~1.0.3" 354 | etag "~1.8.1" 355 | finalhandler "1.1.1" 356 | fresh "0.5.2" 357 | merge-descriptors "1.0.1" 358 | methods "~1.1.2" 359 | on-finished "~2.3.0" 360 | parseurl "~1.3.2" 361 | path-to-regexp "0.1.7" 362 | proxy-addr "~2.0.3" 363 | qs "6.5.1" 364 | range-parser "~1.2.0" 365 | safe-buffer "5.1.1" 366 | send "0.16.2" 367 | serve-static "1.13.2" 368 | setprototypeof "1.1.0" 369 | statuses "~1.4.0" 370 | type-is "~1.6.16" 371 | utils-merge "1.0.1" 372 | vary "~1.1.2" 373 | 374 | finalhandler@1.1.1: 375 | version "1.1.1" 376 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" 377 | dependencies: 378 | debug "2.6.9" 379 | encodeurl "~1.0.2" 380 | escape-html "~1.0.3" 381 | on-finished "~2.3.0" 382 | parseurl "~1.3.2" 383 | statuses "~1.4.0" 384 | unpipe "~1.0.0" 385 | 386 | forwarded@~0.1.2: 387 | version "0.1.2" 388 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 389 | 390 | fresh@0.5.2: 391 | version "0.5.2" 392 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 393 | 394 | graphql-config@2.0.0: 395 | version "2.0.0" 396 | resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-2.0.0.tgz#daf69091055c6f675d63893a2d14c48f3fec3327" 397 | dependencies: 398 | graphql-import "^0.4.0" 399 | graphql-request "^1.4.0" 400 | js-yaml "^3.10.0" 401 | lodash "^4.17.4" 402 | minimatch "^3.0.4" 403 | 404 | graphql-extensions@^0.0.x, graphql-extensions@~0.0.9: 405 | version "0.0.10" 406 | resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.0.10.tgz#34bdb2546d43f6a5bc89ab23c295ec0466c6843d" 407 | dependencies: 408 | core-js "^2.5.3" 409 | source-map-support "^0.5.1" 410 | 411 | graphql-import@^0.4.0: 412 | version "0.4.5" 413 | resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.4.5.tgz#e2f18c28d335733f46df8e0733d8deb1c6e2a645" 414 | dependencies: 415 | lodash "^4.17.4" 416 | 417 | graphql-import@^0.5.0: 418 | version "0.5.0" 419 | resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.5.0.tgz#5f678a6d4636d02a829308884aa1f2fa2197f06d" 420 | dependencies: 421 | lodash "^4.17.4" 422 | 423 | graphql-playground-html@1.5.5: 424 | version "1.5.5" 425 | resolved "https://registry.yarnpkg.com/graphql-playground-html/-/graphql-playground-html-1.5.5.tgz#e2aca543eb66b435ead495b45244b2604d6b2d48" 426 | dependencies: 427 | graphql-config "2.0.0" 428 | 429 | graphql-playground-middleware-express@1.6.1: 430 | version "1.6.1" 431 | resolved "https://registry.yarnpkg.com/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.6.1.tgz#d6287d124a1c55845a52a7d727c371da99cdf0b0" 432 | dependencies: 433 | graphql-playground-html "1.5.5" 434 | 435 | graphql-playground-middleware-lambda@1.5.0: 436 | version "1.5.0" 437 | resolved "https://registry.yarnpkg.com/graphql-playground-middleware-lambda/-/graphql-playground-middleware-lambda-1.5.0.tgz#41a06faf185103660a324257c5293e9f50839fce" 438 | dependencies: 439 | graphql-playground-html "1.5.5" 440 | 441 | graphql-request@^1.4.0: 442 | version "1.5.1" 443 | resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.5.1.tgz#cccdf5cce6432ca062b90f7b63793c77c821ff9a" 444 | dependencies: 445 | cross-fetch "2.0.0" 446 | 447 | graphql-subscriptions@^0.5.8: 448 | version "0.5.8" 449 | resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.5.8.tgz#13a6143c546bce390404657dc73ca501def30aa7" 450 | dependencies: 451 | iterall "^1.2.1" 452 | 453 | graphql-tools@^2.23.1: 454 | version "2.24.0" 455 | resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-2.24.0.tgz#bbacaad03924012a0edb8735a5e65df5d5563675" 456 | dependencies: 457 | apollo-link "^1.2.1" 458 | apollo-utilities "^1.0.1" 459 | deprecated-decorator "^0.1.6" 460 | iterall "^1.1.3" 461 | uuid "^3.1.0" 462 | 463 | graphql-yoga@latest: 464 | version "1.8.5" 465 | resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-1.8.5.tgz#e06c77fbfd6a186afeaf21baf9617bdf8636d502" 466 | dependencies: 467 | "@types/cors" "^2.8.3" 468 | "@types/express" "^4.11.1" 469 | "@types/graphql" "^0.13.0" 470 | "@types/zen-observable" "^0.5.3" 471 | apollo-server-express "^1.3.4" 472 | apollo-server-lambda "1.3.4" 473 | apollo-upload-server "^5.0.0" 474 | aws-lambda "^0.1.2" 475 | body-parser-graphql "1.0.0" 476 | cors "^2.8.4" 477 | express "^4.16.3" 478 | graphql "^0.11.0 || ^0.12.0 || ^0.13.0" 479 | graphql-import "^0.5.0" 480 | graphql-playground-middleware-express "1.6.1" 481 | graphql-playground-middleware-lambda "1.5.0" 482 | graphql-subscriptions "^0.5.8" 483 | graphql-tools "^2.23.1" 484 | subscriptions-transport-ws "^0.9.7" 485 | 486 | "graphql@^0.11.0 || ^0.12.0 || ^0.13.0": 487 | version "0.13.2" 488 | resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" 489 | dependencies: 490 | iterall "^1.2.1" 491 | 492 | http-errors@1.6.2: 493 | version "1.6.2" 494 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 495 | dependencies: 496 | depd "1.1.1" 497 | inherits "2.0.3" 498 | setprototypeof "1.0.3" 499 | statuses ">= 1.3.1 < 2" 500 | 501 | http-errors@~1.6.2: 502 | version "1.6.3" 503 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" 504 | dependencies: 505 | depd "~1.1.2" 506 | inherits "2.0.3" 507 | setprototypeof "1.1.0" 508 | statuses ">= 1.4.0 < 2" 509 | 510 | iconv-lite@0.4.19: 511 | version "0.4.19" 512 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 513 | 514 | ieee754@1.1.8: 515 | version "1.1.8" 516 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" 517 | 518 | ieee754@^1.1.4: 519 | version "1.1.11" 520 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" 521 | 522 | inherits@2.0.3, inherits@~2.0.1: 523 | version "2.0.3" 524 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 525 | 526 | ipaddr.js@1.6.0: 527 | version "1.6.0" 528 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" 529 | 530 | isarray@0.0.1: 531 | version "0.0.1" 532 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 533 | 534 | isarray@^1.0.0: 535 | version "1.0.0" 536 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 537 | 538 | iterall@^1.1.3, iterall@^1.2.1: 539 | version "1.2.2" 540 | resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" 541 | 542 | jmespath@0.15.0: 543 | version "0.15.0" 544 | resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" 545 | 546 | js-yaml@^3.10.0: 547 | version "3.14.1" 548 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" 549 | dependencies: 550 | argparse "^1.0.7" 551 | esprima "^4.0.0" 552 | 553 | lodash.assign@^4.2.0: 554 | version "4.2.0" 555 | resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 556 | 557 | lodash.isobject@^3.0.2: 558 | version "3.0.2" 559 | resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" 560 | 561 | lodash.isstring@^4.0.1: 562 | version "4.0.1" 563 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 564 | 565 | lodash@^4.0.0, lodash@^4.17.4: 566 | version "4.17.21" 567 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 568 | 569 | media-typer@0.3.0: 570 | version "0.3.0" 571 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 572 | 573 | merge-descriptors@1.0.1: 574 | version "1.0.1" 575 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 576 | 577 | methods@~1.1.2: 578 | version "1.1.2" 579 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 580 | 581 | mime-db@~1.33.0: 582 | version "1.33.0" 583 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" 584 | 585 | mime-types@~2.1.18: 586 | version "2.1.18" 587 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" 588 | dependencies: 589 | mime-db "~1.33.0" 590 | 591 | mime@1.4.1: 592 | version "1.4.1" 593 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 594 | 595 | minimatch@^3.0.4: 596 | version "3.0.4" 597 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 598 | dependencies: 599 | brace-expansion "^1.1.7" 600 | 601 | ms@2.0.0: 602 | version "2.0.0" 603 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 604 | 605 | negotiator@0.6.1: 606 | version "0.6.1" 607 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 608 | 609 | node-fetch@2.0.0: 610 | version "2.0.0" 611 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.0.0.tgz#982bba43ecd4f2922a29cc186a6bbb0bb73fcba6" 612 | 613 | object-assign@^4: 614 | version "4.1.1" 615 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 616 | 617 | object-path@^0.11.4: 618 | version "0.11.5" 619 | resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a" 620 | 621 | on-finished@~2.3.0: 622 | version "2.3.0" 623 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 624 | dependencies: 625 | ee-first "1.1.1" 626 | 627 | parseurl@~1.3.2: 628 | version "1.3.2" 629 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" 630 | 631 | path-to-regexp@0.1.7: 632 | version "0.1.7" 633 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 634 | 635 | proxy-addr@~2.0.3: 636 | version "2.0.3" 637 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" 638 | dependencies: 639 | forwarded "~0.1.2" 640 | ipaddr.js "1.6.0" 641 | 642 | punycode@1.3.2: 643 | version "1.3.2" 644 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" 645 | 646 | qs@6.5.1: 647 | version "6.5.1" 648 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" 649 | 650 | querystring@0.2.0: 651 | version "0.2.0" 652 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" 653 | 654 | range-parser@~1.2.0: 655 | version "1.2.0" 656 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 657 | 658 | raw-body@2.3.2: 659 | version "2.3.2" 660 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" 661 | dependencies: 662 | bytes "3.0.0" 663 | http-errors "1.6.2" 664 | iconv-lite "0.4.19" 665 | unpipe "1.0.0" 666 | 667 | readable-stream@1.1.x: 668 | version "1.1.14" 669 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 670 | dependencies: 671 | core-util-is "~1.0.0" 672 | inherits "~2.0.1" 673 | isarray "0.0.1" 674 | string_decoder "~0.10.x" 675 | 676 | regenerator-runtime@^0.11.1: 677 | version "0.11.1" 678 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" 679 | 680 | safe-buffer@5.1.1, safe-buffer@~5.1.0: 681 | version "5.1.1" 682 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 683 | 684 | sax@1.2.1: 685 | version "1.2.1" 686 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" 687 | 688 | sax@>=0.6.0: 689 | version "1.2.4" 690 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 691 | 692 | send@0.16.2: 693 | version "0.16.2" 694 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" 695 | dependencies: 696 | debug "2.6.9" 697 | depd "~1.1.2" 698 | destroy "~1.0.4" 699 | encodeurl "~1.0.2" 700 | escape-html "~1.0.3" 701 | etag "~1.8.1" 702 | fresh "0.5.2" 703 | http-errors "~1.6.2" 704 | mime "1.4.1" 705 | ms "2.0.0" 706 | on-finished "~2.3.0" 707 | range-parser "~1.2.0" 708 | statuses "~1.4.0" 709 | 710 | serve-static@1.13.2: 711 | version "1.13.2" 712 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" 713 | dependencies: 714 | encodeurl "~1.0.2" 715 | escape-html "~1.0.3" 716 | parseurl "~1.3.2" 717 | send "0.16.2" 718 | 719 | setprototypeof@1.0.3: 720 | version "1.0.3" 721 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 722 | 723 | setprototypeof@1.1.0: 724 | version "1.1.0" 725 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 726 | 727 | source-map-support@^0.5.1: 728 | version "0.5.4" 729 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.4.tgz#54456efa89caa9270af7cd624cc2f123e51fbae8" 730 | dependencies: 731 | source-map "^0.6.0" 732 | 733 | source-map@^0.6.0: 734 | version "0.6.1" 735 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 736 | 737 | sprintf-js@~1.0.2: 738 | version "1.0.3" 739 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 740 | 741 | "statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": 742 | version "1.5.0" 743 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 744 | 745 | statuses@~1.4.0: 746 | version "1.4.0" 747 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 748 | 749 | streamsearch@0.1.2: 750 | version "0.1.2" 751 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 752 | 753 | string_decoder@~0.10.x: 754 | version "0.10.31" 755 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 756 | 757 | subscriptions-transport-ws@^0.9.7: 758 | version "0.9.8" 759 | resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.8.tgz#3a26ab96e06f78cf4ace8d083f6227fa55970947" 760 | dependencies: 761 | backo2 "^1.0.2" 762 | eventemitter3 "^2.0.3" 763 | iterall "^1.2.1" 764 | lodash.assign "^4.2.0" 765 | lodash.isobject "^3.0.2" 766 | lodash.isstring "^4.0.1" 767 | symbol-observable "^1.0.4" 768 | ws "^3.0.0" 769 | 770 | symbol-observable@^1.0.4: 771 | version "1.2.0" 772 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" 773 | 774 | type-is@~1.6.15, type-is@~1.6.16: 775 | version "1.6.16" 776 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" 777 | dependencies: 778 | media-typer "0.3.0" 779 | mime-types "~2.1.18" 780 | 781 | ultron@~1.1.0: 782 | version "1.1.1" 783 | resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" 784 | 785 | unpipe@1.0.0, unpipe@~1.0.0: 786 | version "1.0.0" 787 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 788 | 789 | url@0.10.3: 790 | version "0.10.3" 791 | resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" 792 | dependencies: 793 | punycode "1.3.2" 794 | querystring "0.2.0" 795 | 796 | utils-merge@1.0.1: 797 | version "1.0.1" 798 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 799 | 800 | uuid@3.1.0: 801 | version "3.1.0" 802 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 803 | 804 | uuid@^3.1.0: 805 | version "3.2.1" 806 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" 807 | 808 | vary@^1, vary@~1.1.2: 809 | version "1.1.2" 810 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 811 | 812 | whatwg-fetch@2.0.3: 813 | version "2.0.3" 814 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 815 | 816 | ws@^3.0.0: 817 | version "3.3.3" 818 | resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" 819 | dependencies: 820 | async-limiter "~1.0.0" 821 | safe-buffer "~5.1.0" 822 | ultron "~1.1.0" 823 | 824 | xml2js@0.4.17: 825 | version "0.4.17" 826 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" 827 | dependencies: 828 | sax ">=0.6.0" 829 | xmlbuilder "^4.1.0" 830 | 831 | xmlbuilder@4.2.1, xmlbuilder@^4.1.0: 832 | version "4.2.1" 833 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" 834 | dependencies: 835 | lodash "^4.0.0" 836 | 837 | zen-observable-ts@^0.8.9: 838 | version "0.8.9" 839 | resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz#d3c97af08c0afdca37ebcadf7cc3ee96bda9bab1" 840 | dependencies: 841 | zen-observable "^0.8.0" 842 | 843 | zen-observable@^0.8.0: 844 | version "0.8.8" 845 | resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.8.tgz#1ea93995bf098754a58215a1e0a7309e5749ec42" 846 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/tests', '/src'], 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest', 6 | }, 7 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 8 | testPathIgnorePatterns: ['/node_modules/', '/__fixtures__/'], 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 10 | collectCoverage: true, 11 | collectCoverageFrom: [ 12 | '**/*.{ts,tsx}', 13 | '!**/node_modules/**', 14 | '!**/vendor/**', 15 | '!**/generated/**', 16 | ], 17 | verbose: true, 18 | coverageDirectory: './coverage', 19 | coverageReporters: ['json', 'lcov', 'text', 'clover', 'html'], 20 | globals: { 21 | 'ts-jest': { 22 | tsconfig: 'tsconfig.test.json', 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /media/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maticzav/graphql-middleware/ed63d0da5e45883c0dae1cfcf19f3afdd3777385/media/idea.png -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maticzav/graphql-middleware/ed63d0da5e45883c0dae1cfcf19f3afdd3777385/media/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-middleware", 3 | "description": "GraphQL Middleware done right!", 4 | "version": "0.0.0-semantic-release", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "typings": "dist/index.d.ts", 8 | "typescript": { 9 | "definition": "dist/index.d.ts" 10 | }, 11 | "exports": { 12 | ".": { 13 | "require": "./dist/index.js", 14 | "import": "./dist/index.mjs", 15 | "types": "./dist/index.d.ts" 16 | }, 17 | "./*": { 18 | "require": "./dist/*.js", 19 | "import": "./dist/*.mjs", 20 | "types": "./dist/*.d.ts" 21 | } 22 | }, 23 | "publishConfig": { 24 | "directory": "dist" 25 | }, 26 | "author": "Matic Zavadlal ", 27 | "dependencies": { 28 | "@graphql-tools/delegate": "^8.8.1", 29 | "@graphql-tools/schema": "^8.5.1" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^26.0.24", 33 | "@types/lodash": "^4.14.182", 34 | "@types/node":"^18.8.3", 35 | "apollo-server": "^3.10.2", 36 | "axios": "^0.27.2", 37 | "bob-esbuild": "^1.3.0", 38 | "bob-esbuild-cli": "^1.0.1", 39 | "codecov": "^3.8.3", 40 | "graphql": "^16.6.0", 41 | "husky": "^6.0.0", 42 | "iterall": "^1.3.0", 43 | "jest": "^26.6.3", 44 | "prettier": "^2.7.1", 45 | "pretty-quick": "^3.1.3", 46 | "rimraf": "^3.0.2", 47 | "semantic-release": "^17.4.7", 48 | "ts-jest": "^26.5.6", 49 | "ts-node": "^9.1.1", 50 | "typescript": "^4.7.4" 51 | }, 52 | "peerDependencies": { 53 | "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" 54 | }, 55 | "scripts": { 56 | "clean": "rimraf dist", 57 | "compile": "bob-esbuild build", 58 | "coverage": "codecov", 59 | "test": "jest", 60 | "prepublishOnly": "npm run compile" 61 | }, 62 | "files": [ 63 | "dist" 64 | ], 65 | "release": { 66 | "branch": "master" 67 | }, 68 | "homepage": "https://github.com/maticzav/graphql-middleware", 69 | "repository": { 70 | "type": "git", 71 | "url": "https://github.com/maticzav/graphql-middleware.git" 72 | }, 73 | "bugs": { 74 | "url": "https://github.com/maticzav/graphql-middleware/issues" 75 | }, 76 | "keywords": [ 77 | "graphql", 78 | "middleware", 79 | "schema", 80 | "resolvers", 81 | "server", 82 | "yoga" 83 | ], 84 | "license": "MIT" 85 | } 86 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | } 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", "docker:disable", ":automergeMinor"], 3 | "rangeStrategy": "bump", 4 | "pathRules": [ 5 | { 6 | "paths": ["examples/**"], 7 | "extends": [":semanticCommitTypeAll(chore)", ":automergeMinor"], 8 | "branchName": "{{branchPrefix}}examples-{{depNameSanitized}}-{{newVersionMajor}}.x" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/applicator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLFieldResolver, 4 | GraphQLField, 5 | GraphQLSchema, 6 | defaultFieldResolver, 7 | isIntrospectionType, 8 | GraphQLArgument, 9 | } from 'graphql' 10 | import { 11 | IMiddlewareFunction, 12 | IMiddlewareResolver, 13 | IMiddlewareFieldMap, 14 | IApplyOptions, 15 | IMiddleware, 16 | IResolvers, 17 | IResolverOptions, 18 | } from './types' 19 | import { 20 | isMiddlewareFunction, 21 | isGraphQLObjectType, 22 | isMiddlewareResolver, 23 | isMiddlewareWithFragment, 24 | } from './utils' 25 | 26 | // Applicator 27 | 28 | function wrapResolverInMiddleware( 29 | resolver: GraphQLFieldResolver, 30 | middleware: IMiddlewareResolver, 31 | ): GraphQLFieldResolver { 32 | return (parent, args, ctx, info) => 33 | middleware( 34 | (_parent = parent, _args = args, _ctx = ctx, _info = info) => 35 | resolver(_parent, _args, _ctx, _info), 36 | parent, 37 | args, 38 | ctx, 39 | info, 40 | ) 41 | } 42 | 43 | function parseField( 44 | field: GraphQLField & { isDeprecated?: boolean }, 45 | ) { 46 | const { isDeprecated, ...restData } = field 47 | const argsMap = field.args.reduce( 48 | (acc, cur) => { 49 | acc[cur.name] = cur; 50 | return acc; 51 | }, {} as Record, 52 | ) 53 | return { 54 | ...restData, 55 | args: argsMap, 56 | } 57 | } 58 | 59 | function applyMiddlewareToField( 60 | field: GraphQLField, 61 | options: IApplyOptions, 62 | middleware: IMiddlewareFunction, 63 | ): IResolverOptions { 64 | const parsedField = parseField(field) 65 | if ( 66 | isMiddlewareWithFragment(middleware) && 67 | parsedField.resolve && 68 | parsedField.resolve !== defaultFieldResolver 69 | ) { 70 | return { 71 | ...parsedField, 72 | fragment: middleware.fragment, 73 | fragments: middleware.fragments, 74 | resolve: wrapResolverInMiddleware( 75 | parsedField.resolve, 76 | middleware.resolve, 77 | ), 78 | } 79 | } else if (isMiddlewareWithFragment(middleware) && parsedField.subscribe) { 80 | return { 81 | ...parsedField, 82 | fragment: middleware.fragment, 83 | fragments: middleware.fragments, 84 | subscribe: wrapResolverInMiddleware( 85 | parsedField.subscribe, 86 | middleware.resolve, 87 | ), 88 | } 89 | } else if ( 90 | isMiddlewareResolver(middleware) && 91 | parsedField.resolve && 92 | parsedField.resolve !== defaultFieldResolver 93 | ) { 94 | return { 95 | ...parsedField, 96 | resolve: wrapResolverInMiddleware(parsedField.resolve, middleware), 97 | } 98 | } else if (isMiddlewareResolver(middleware) && parsedField.subscribe) { 99 | return { 100 | ...parsedField, 101 | subscribe: wrapResolverInMiddleware(parsedField.subscribe, middleware), 102 | } 103 | } else if ( 104 | isMiddlewareWithFragment(middleware) && 105 | !options.onlyDeclaredResolvers 106 | ) { 107 | return { 108 | ...parsedField, 109 | fragment: middleware.fragment, 110 | fragments: middleware.fragments, 111 | resolve: wrapResolverInMiddleware( 112 | defaultFieldResolver, 113 | middleware.resolve, 114 | ), 115 | } 116 | } else if ( 117 | isMiddlewareResolver(middleware) && 118 | !options.onlyDeclaredResolvers 119 | ) { 120 | return { 121 | ...parsedField, 122 | resolve: wrapResolverInMiddleware(defaultFieldResolver, middleware), 123 | } 124 | } else { 125 | return { ...parsedField, resolve: defaultFieldResolver } 126 | } 127 | } 128 | 129 | function applyMiddlewareToType( 130 | type: GraphQLObjectType, 131 | options: IApplyOptions, 132 | middleware: 133 | | IMiddlewareFunction 134 | | IMiddlewareFieldMap, 135 | ): IResolvers { 136 | const fieldMap = type.getFields() 137 | 138 | if (isMiddlewareFunction(middleware)) { 139 | const resolvers = Object.keys(fieldMap).reduce( 140 | (resolvers, fieldName) => { 141 | resolvers[fieldName] = applyMiddlewareToField( 142 | fieldMap[fieldName], 143 | options, 144 | middleware as IMiddlewareFunction, 145 | ); 146 | return resolvers; 147 | }, 148 | {}, 149 | ) 150 | 151 | return resolvers 152 | } else { 153 | const resolvers = Object.keys(middleware).reduce( 154 | (resolvers, fieldName) => { 155 | resolvers[fieldName] = applyMiddlewareToField( 156 | fieldMap[fieldName], 157 | options, 158 | middleware[fieldName], 159 | ); 160 | return resolvers; 161 | }, 162 | {}, 163 | ) 164 | 165 | return resolvers 166 | } 167 | } 168 | 169 | function applyMiddlewareToSchema( 170 | schema: GraphQLSchema, 171 | options: IApplyOptions, 172 | middleware: IMiddlewareFunction, 173 | ): IResolvers { 174 | const typeMap = schema.getTypeMap() 175 | 176 | const resolvers = Object.keys(typeMap) 177 | .filter( 178 | (type) => 179 | isGraphQLObjectType(typeMap[type]) && 180 | !isIntrospectionType(typeMap[type]), 181 | ) 182 | .reduce( 183 | (resolvers, type) => { 184 | resolvers[type] = applyMiddlewareToType( 185 | typeMap[type] as GraphQLObjectType, 186 | options, 187 | middleware, 188 | ); 189 | return resolvers; 190 | }, 191 | {}, 192 | ) 193 | 194 | return resolvers 195 | } 196 | 197 | // Generator 198 | 199 | export function generateResolverFromSchemaAndMiddleware< 200 | TSource, 201 | TContext, 202 | TArgs, 203 | >( 204 | schema: GraphQLSchema, 205 | options: IApplyOptions, 206 | middleware: IMiddleware, 207 | ): IResolvers { 208 | if (isMiddlewareFunction(middleware)) { 209 | return applyMiddlewareToSchema( 210 | schema, 211 | options, 212 | middleware as IMiddlewareFunction, 213 | ) 214 | } else { 215 | const typeMap = schema.getTypeMap() 216 | 217 | const resolvers = Object.keys(middleware).reduce( 218 | (resolvers, type) => { 219 | resolvers[type] = applyMiddlewareToType( 220 | typeMap[type] as GraphQLObjectType, 221 | options, 222 | middleware[type], 223 | ); 224 | return resolvers; 225 | }, 226 | {}, 227 | ) 228 | 229 | return resolvers 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/constructors.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareGenerator } from './generator' 2 | import { IMiddlewareGenerator, IMiddlewareGeneratorConstructor } from './types' 3 | 4 | export function middleware( 5 | generator: IMiddlewareGeneratorConstructor, 6 | ): IMiddlewareGenerator { 7 | return new MiddlewareGenerator(generator) 8 | } 9 | -------------------------------------------------------------------------------- /src/fragments.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Kind, 3 | parse, 4 | InlineFragmentNode, 5 | OperationDefinitionNode, 6 | print, 7 | } from 'graphql' 8 | import { FragmentReplacement, IResolvers } from './types' 9 | 10 | export function extractFragmentReplacements( 11 | resolvers: IResolvers, 12 | ): FragmentReplacement[] { 13 | const allFragmentReplacements: FragmentReplacement[] = [] 14 | 15 | /* Collect fragments. */ 16 | 17 | for (const typeName in resolvers) { 18 | const fieldResolvers = resolvers[typeName] 19 | for (const fieldName in fieldResolvers) { 20 | const fieldResolver = fieldResolvers[fieldName] 21 | if (typeof fieldResolver === 'object' && fieldResolver.fragment) { 22 | allFragmentReplacements.push({ 23 | field: fieldName, 24 | fragment: fieldResolver.fragment, 25 | }) 26 | } 27 | if (typeof fieldResolver === 'object' && fieldResolver.fragments) { 28 | for (const fragment of fieldResolver.fragments) { 29 | allFragmentReplacements.push({ 30 | field: fieldName, 31 | fragment: fragment, 32 | }) 33 | } 34 | } 35 | } 36 | } 37 | 38 | /* Filter and map circular dependencies. */ 39 | 40 | const fragmentReplacements = allFragmentReplacements 41 | .filter(fragment => Boolean(fragment)) 42 | .map(fragmentReplacement => { 43 | const fragment = parseFragmentToInlineFragment( 44 | fragmentReplacement.fragment, 45 | ) 46 | 47 | const newSelections = fragment.selectionSet.selections.filter(node => { 48 | switch (node.kind) { 49 | case Kind.FIELD: { 50 | return node.name.value !== fragmentReplacement.field 51 | } 52 | default: { 53 | return true 54 | } 55 | } 56 | }) 57 | 58 | if (newSelections.length === 0) { 59 | return null 60 | } 61 | 62 | const newFragment: InlineFragmentNode = { 63 | ...fragment, 64 | selectionSet: { 65 | kind: fragment.selectionSet.kind, 66 | loc: fragment.selectionSet.loc, 67 | selections: newSelections, 68 | }, 69 | } 70 | 71 | const parsedFragment = print(newFragment) 72 | 73 | return { 74 | field: fragmentReplacement.field, 75 | fragment: parsedFragment, 76 | } 77 | }) 78 | .filter(fr => fr !== null) 79 | 80 | return fragmentReplacements 81 | 82 | /* Helper functions */ 83 | 84 | function parseFragmentToInlineFragment( 85 | definitions: string, 86 | ): InlineFragmentNode { 87 | if (definitions.trim().startsWith('fragment')) { 88 | const document = parse(definitions) 89 | for (const definition of document.definitions) { 90 | if (definition.kind === Kind.FRAGMENT_DEFINITION) { 91 | return { 92 | kind: Kind.INLINE_FRAGMENT, 93 | typeCondition: definition.typeCondition, 94 | selectionSet: definition.selectionSet, 95 | } 96 | } 97 | } 98 | } 99 | 100 | const query = parse(`{${definitions}}`) 101 | .definitions[0] as OperationDefinitionNode 102 | for (const selection of query.selectionSet.selections) { 103 | if (selection.kind === Kind.INLINE_FRAGMENT) { 104 | return selection 105 | } 106 | } 107 | 108 | throw new Error('Could not parse fragment') 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IMiddlewareGeneratorConstructor, 3 | IMiddleware, 4 | IMiddlewareGenerator, 5 | } from './types' 6 | import { GraphQLSchema } from 'graphql' 7 | 8 | export class MiddlewareGenerator 9 | implements IMiddlewareGenerator { 10 | private generator: IMiddlewareGeneratorConstructor 11 | 12 | constructor( 13 | generator: IMiddlewareGeneratorConstructor, 14 | ) { 15 | this.generator = generator 16 | } 17 | 18 | generate(schema: GraphQLSchema): IMiddleware { 19 | return this.generator(schema) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | FragmentReplacement, 3 | IMiddleware, 4 | IMiddlewareTypeMap, 5 | IMiddlewareFieldMap, 6 | IMiddlewareFunction, 7 | IMiddlewareGenerator, 8 | IMiddlewareGeneratorConstructor, 9 | } from './types' 10 | export { 11 | applyMiddleware, 12 | applyMiddlewareToDeclaredResolvers, 13 | } from './middleware' 14 | export { middleware } from './constructors' 15 | export { MiddlewareError } from './validation' 16 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql' 2 | import { addResolversToSchema } from '@graphql-tools/schema' 3 | import { 4 | IApplyOptions, 5 | IMiddleware, 6 | FragmentReplacement, 7 | IMiddlewareGenerator, 8 | GraphQLSchemaWithFragmentReplacements, 9 | } from './types' 10 | import { generateResolverFromSchemaAndMiddleware } from './applicator' 11 | import { validateMiddleware } from './validation' 12 | import { extractFragmentReplacements } from './fragments' 13 | import { isMiddlewareGenerator } from './utils' 14 | 15 | /** 16 | * 17 | * @param schema 18 | * @param options 19 | * @param middleware 20 | * 21 | * Validates middleware and generates resolvers map for provided middleware. 22 | * Applies middleware to the current schema and returns the modified one. 23 | * 24 | */ 25 | export function addMiddlewareToSchema( 26 | schema: GraphQLSchema, 27 | options: IApplyOptions, 28 | middleware: IMiddleware, 29 | ): { 30 | schema: GraphQLSchema 31 | fragmentReplacements: FragmentReplacement[] 32 | } { 33 | const validMiddleware = validateMiddleware(schema, middleware) 34 | const resolvers = generateResolverFromSchemaAndMiddleware( 35 | schema, 36 | options, 37 | validMiddleware, 38 | ) 39 | 40 | const fragmentReplacements = extractFragmentReplacements(resolvers) 41 | 42 | const newSchema = addResolversToSchema({ 43 | schema, 44 | resolvers, 45 | updateResolversInPlace: false, 46 | resolverValidationOptions: { 47 | requireResolversForResolveType: 'ignore', 48 | }, 49 | }) 50 | 51 | return { schema: newSchema, fragmentReplacements } 52 | } 53 | 54 | /** 55 | * 56 | * @param schema 57 | * @param options 58 | * @param middlewares 59 | * 60 | * Generates middleware from middleware generators and applies middleware to 61 | * resolvers. Returns generated schema with all provided middleware. 62 | * 63 | */ 64 | function applyMiddlewareWithOptions( 65 | schema: GraphQLSchema, 66 | options: IApplyOptions, 67 | ...middlewares: ( 68 | | IMiddleware 69 | | IMiddlewareGenerator 70 | )[] 71 | ): GraphQLSchemaWithFragmentReplacements { 72 | const normalisedMiddlewares = middlewares.map((middleware) => { 73 | if (isMiddlewareGenerator(middleware)) { 74 | return middleware.generate(schema) 75 | } else { 76 | return middleware 77 | } 78 | }) 79 | 80 | const schemaWithMiddlewareAndFragmentReplacements = normalisedMiddlewares.reduceRight( 81 | ( 82 | { 83 | schema: currentSchema, 84 | fragmentReplacements: currentFragmentReplacements, 85 | }, 86 | middleware, 87 | ) => { 88 | const { 89 | schema: newSchema, 90 | fragmentReplacements: newFragmentReplacements, 91 | } = addMiddlewareToSchema(currentSchema, options, middleware) 92 | 93 | return { 94 | schema: newSchema, 95 | fragmentReplacements: [ 96 | ...currentFragmentReplacements, 97 | ...newFragmentReplacements, 98 | ], 99 | } 100 | }, 101 | { schema, fragmentReplacements: [] }, 102 | ) 103 | 104 | const schemaWithMiddleware: GraphQLSchemaWithFragmentReplacements = 105 | schemaWithMiddlewareAndFragmentReplacements.schema 106 | 107 | schemaWithMiddleware.schema = 108 | schemaWithMiddlewareAndFragmentReplacements.schema 109 | schemaWithMiddleware.fragmentReplacements = 110 | schemaWithMiddlewareAndFragmentReplacements.fragmentReplacements 111 | 112 | return schemaWithMiddleware 113 | } 114 | 115 | // Exposed functions 116 | 117 | /** 118 | * 119 | * @param schema 120 | * @param middlewares 121 | * 122 | * Apply middleware to resolvers and return generated schema. 123 | * 124 | */ 125 | export function applyMiddleware( 126 | schema: GraphQLSchema, 127 | ...middlewares: ( 128 | | IMiddleware 129 | | IMiddlewareGenerator 130 | )[] 131 | ): GraphQLSchemaWithFragmentReplacements { 132 | return applyMiddlewareWithOptions( 133 | schema, 134 | { onlyDeclaredResolvers: false }, 135 | ...middlewares, 136 | ) 137 | } 138 | 139 | /** 140 | * 141 | * @param schema 142 | * @param middlewares 143 | * 144 | * Apply middleware to declared resolvers and return new schema. 145 | * 146 | */ 147 | export function applyMiddlewareToDeclaredResolvers< 148 | TSource = any, 149 | TContext = any, 150 | TArgs = any 151 | >( 152 | schema: GraphQLSchema, 153 | ...middlewares: ( 154 | | IMiddleware 155 | | IMiddlewareGenerator 156 | )[] 157 | ): GraphQLSchemaWithFragmentReplacements { 158 | return applyMiddlewareWithOptions( 159 | schema, 160 | { onlyDeclaredResolvers: true }, 161 | ...middlewares, 162 | ) 163 | } 164 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLResolveInfo, 3 | GraphQLSchema, 4 | GraphQLTypeResolver, 5 | GraphQLIsTypeOfFn, 6 | } from 'graphql' 7 | import { StitchingInfo } from '@graphql-tools/delegate' 8 | 9 | // Middleware Tree 10 | 11 | export declare type IMiddlewareFragment = string 12 | 13 | export declare type IMiddlewareResolver< 14 | TSource = any, 15 | TContext = any, 16 | TArgs = any 17 | > = ( 18 | resolve: ( 19 | source?: TSource, 20 | args?: TArgs, 21 | context?: TContext, 22 | info?: GraphQLResolveInfo & { stitchingInfo?: StitchingInfo }, 23 | ) => any, 24 | parent: TSource, 25 | args: TArgs, 26 | context: TContext, 27 | info: GraphQLResolveInfo, 28 | ) => Promise 29 | 30 | export interface IMiddlewareWithOptions< 31 | TSource = any, 32 | TContext = any, 33 | TArgs = any 34 | > { 35 | fragment?: IMiddlewareFragment 36 | fragments?: IMiddlewareFragment[] 37 | resolve?: IMiddlewareResolver 38 | } 39 | 40 | export type IMiddlewareFunction = 41 | | IMiddlewareWithOptions 42 | | IMiddlewareResolver 43 | 44 | export interface IMiddlewareTypeMap< 45 | TSource = any, 46 | TContext = any, 47 | TArgs = any 48 | > { 49 | [key: string]: 50 | | IMiddlewareFunction 51 | | IMiddlewareFieldMap 52 | } 53 | 54 | export interface IMiddlewareFieldMap< 55 | TSource = any, 56 | TContext = any, 57 | TArgs = any 58 | > { 59 | [key: string]: IMiddlewareFunction 60 | } 61 | 62 | // Middleware Generator 63 | 64 | export declare class IMiddlewareGenerator { 65 | constructor( 66 | generator: IMiddlewareGeneratorConstructor, 67 | ) 68 | generate(schema: GraphQLSchema): IMiddleware 69 | } 70 | 71 | export declare type IMiddlewareGeneratorConstructor< 72 | TSource = any, 73 | TContext = any, 74 | TArgs = any 75 | > = (schema: GraphQLSchema) => IMiddleware 76 | 77 | export declare type IMiddleware = 78 | | IMiddlewareFunction 79 | | IMiddlewareTypeMap 80 | 81 | // Middleware 82 | 83 | export declare type IApplyOptions = { 84 | onlyDeclaredResolvers: boolean 85 | } 86 | 87 | export declare type GraphQLSchemaWithFragmentReplacements = GraphQLSchema & { 88 | schema?: GraphQLSchema 89 | fragmentReplacements?: FragmentReplacement[] 90 | } 91 | 92 | // Fragments (inspired by graphql-tools) 93 | 94 | export interface FragmentReplacement { 95 | field: string 96 | fragment: string 97 | } 98 | 99 | export interface IResolvers { 100 | [key: string]: IResolverObject 101 | } 102 | 103 | export interface IResolverObject { 104 | [key: string]: 105 | | IFieldResolver 106 | | IResolverOptions 107 | } 108 | 109 | export interface IResolverOptions { 110 | fragment?: string 111 | fragments?: string[] 112 | resolve?: IFieldResolver 113 | subscribe?: IFieldResolver 114 | __resolveType?: GraphQLTypeResolver 115 | __isTypeOf?: GraphQLIsTypeOfFn 116 | } 117 | 118 | export type IFieldResolver = ( 119 | source: TSource, 120 | args: { [argument: string]: any }, 121 | context: TContext, 122 | info: GraphQLResolveInfo & { stitchingInfo?: StitchingInfo }, 123 | ) => any 124 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLInterfaceType } from 'graphql' 2 | import { 3 | IMiddlewareResolver, 4 | IMiddlewareWithOptions, 5 | IMiddlewareFunction, 6 | IMiddlewareGenerator, 7 | } from './types' 8 | import { MiddlewareGenerator } from './generator' 9 | 10 | // Type checkers 11 | 12 | export function isMiddlewareResolver( 13 | obj: any, 14 | ): obj is IMiddlewareResolver { 15 | return ( 16 | typeof obj === 'function' || 17 | (typeof obj === 'object' && obj.then !== undefined) 18 | ) 19 | } 20 | 21 | export function isMiddlewareWithFragment( 22 | obj: any, 23 | ): obj is IMiddlewareWithOptions { 24 | return ( 25 | (typeof obj.fragment === 'string' || typeof obj.fragments === 'object') && 26 | isMiddlewareResolver(obj.resolve) 27 | ) 28 | } 29 | 30 | export function isMiddlewareFunction( 31 | obj: any, 32 | ): obj is IMiddlewareFunction { 33 | return isMiddlewareWithFragment(obj) || isMiddlewareResolver(obj) 34 | } 35 | 36 | export function isMiddlewareGenerator( 37 | x: any, 38 | ): x is IMiddlewareGenerator { 39 | return x instanceof MiddlewareGenerator 40 | } 41 | 42 | export function isGraphQLObjectType( 43 | obj: any, 44 | ): obj is GraphQLObjectType | GraphQLInterfaceType { 45 | return obj instanceof GraphQLObjectType || obj instanceof GraphQLInterfaceType 46 | } 47 | -------------------------------------------------------------------------------- /src/validation.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType } from 'graphql' 2 | import { IMiddleware } from './types' 3 | import { isMiddlewareFunction } from './utils' 4 | 5 | export function validateMiddleware( 6 | schema: GraphQLSchema, 7 | middleware: IMiddleware, 8 | ): IMiddleware { 9 | if (isMiddlewareFunction(middleware)) { 10 | return middleware 11 | } 12 | 13 | const types = schema.getTypeMap() 14 | 15 | Object.keys(middleware).forEach(type => { 16 | if (!Object.keys(types).includes(type)) { 17 | throw new MiddlewareError( 18 | `Type ${type} exists in middleware but is missing in Schema.`, 19 | ) 20 | } 21 | 22 | if (!isMiddlewareFunction(middleware[type])) { 23 | const fields = (types[type] as 24 | | GraphQLObjectType 25 | | GraphQLInterfaceType).getFields() 26 | 27 | Object.keys(middleware[type]).forEach(field => { 28 | if (!Object.keys(fields).includes(field)) { 29 | throw new MiddlewareError( 30 | `Field ${type}.${field} exists in middleware but is missing in Schema.`, 31 | ) 32 | } 33 | 34 | if (!isMiddlewareFunction(middleware[type][field])) { 35 | throw new MiddlewareError( 36 | `Expected ${type}.${field} to be a function but found ` + 37 | typeof middleware[type][field], 38 | ) 39 | } 40 | }) 41 | } 42 | }) 43 | 44 | return middleware 45 | } 46 | 47 | export class MiddlewareError extends Error {} 48 | -------------------------------------------------------------------------------- /tests/core.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | import { ExecutionResult, graphql, subscribe, parse } from 'graphql' 3 | import { $$asyncIterator } from 'iterall' 4 | 5 | import { applyMiddleware } from '../src' 6 | import { 7 | IResolvers, 8 | IMiddlewareTypeMap, 9 | IMiddlewareFunction, 10 | } from '../src/types' 11 | 12 | describe('core:', () => { 13 | /* Schema. */ 14 | 15 | const typeDefs = ` 16 | type Query { 17 | before(arg: String!): String! 18 | beforeNothing(arg: String!): String! 19 | after: String! 20 | afterNothing: String! 21 | null: String 22 | nested: Nothing! 23 | resolverless: Resolverless! 24 | } 25 | 26 | type Subscription { 27 | sub: String 28 | } 29 | 30 | type Nothing { 31 | nothing: String! 32 | } 33 | 34 | type Resolverless { 35 | someData: String! 36 | } 37 | 38 | schema { 39 | query: Query, 40 | subscription: Subscription 41 | } 42 | ` 43 | 44 | const resolvers: IResolvers = { 45 | Query: { 46 | before: (parent, { arg }, ctx, info) => arg, 47 | beforeNothing: (parent, { arg }, ctx, info) => arg, 48 | after: () => 'after', 49 | afterNothing: () => 'after', 50 | null: () => null, 51 | nested: () => ({}), 52 | resolverless: () => ({ someData: 'data' }), 53 | }, 54 | Subscription: { 55 | sub: { 56 | subscribe: async (parent, { arg }, ctx, info) => { 57 | const iterator = { 58 | next: () => Promise.resolve({ done: false, value: { sub: arg } }), 59 | return: () => { 60 | return 61 | }, 62 | throw: () => { 63 | return 64 | }, 65 | [$$asyncIterator]: () => iterator, 66 | } 67 | return iterator 68 | }, 69 | }, 70 | }, 71 | Nothing: { 72 | nothing: () => 'nothing', 73 | }, 74 | } 75 | 76 | const getSchema = () => makeExecutableSchema({ typeDefs, resolvers }) 77 | 78 | // Field Middleware 79 | 80 | // Type Middleware 81 | 82 | const typeMiddlewareBefore: IMiddlewareTypeMap = { 83 | Query: async (resolve, parent, args, context, info) => { 84 | const _args = { arg: 'changed' } 85 | return resolve(parent, _args) 86 | }, 87 | Subscription: async (resolve, parent, args, context, info) => { 88 | const _args = { arg: 'changed' } 89 | return resolve(parent, _args) 90 | }, 91 | } 92 | 93 | const typeMiddlewareAfter: IMiddlewareTypeMap = { 94 | Query: async (resolve, parent, args, context, info) => { 95 | const res = resolve() 96 | return 'changed' 97 | }, 98 | } 99 | 100 | // Schema Middleware 101 | 102 | const schemaMiddlewareBefore: IMiddlewareFunction = async ( 103 | resolve, 104 | parent, 105 | args, 106 | context, 107 | info, 108 | ) => { 109 | const _args = { arg: 'changed' } 110 | return resolve(parent, _args, context, info) 111 | } 112 | 113 | const schemaMiddlewareAfter: IMiddlewareFunction = async ( 114 | resolve, 115 | parent, 116 | args, 117 | context, 118 | info, 119 | ) => { 120 | const res = resolve() 121 | return 'changed' 122 | } 123 | 124 | const emptyStringMiddleware: IMiddlewareFunction = async ( 125 | resolve, 126 | parent, 127 | args, 128 | context, 129 | info, 130 | ) => { 131 | if (/^String!?$/.test(String(info.returnType))) { 132 | return '' 133 | } else { 134 | return resolve() 135 | } 136 | } 137 | 138 | test('field middleware', async () => { 139 | const schema = getSchema() 140 | const fieldMiddleware: IMiddlewareTypeMap = { 141 | Query: { 142 | before: async (resolve, parent) => { 143 | const _args = { arg: 'changed' } 144 | return resolve(parent, _args) 145 | }, 146 | after: async (resolve) => { 147 | return 'changed' 148 | }, 149 | }, 150 | } 151 | const schemaWithMiddleware = applyMiddleware(schema, fieldMiddleware) 152 | 153 | const query = ` 154 | query { 155 | before(arg: "before") 156 | beforeNothing(arg: "before") 157 | after 158 | afterNothing 159 | null 160 | nested { nothing } 161 | } 162 | ` 163 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 164 | 165 | /* Tests. */ 166 | 167 | expect(res).toEqual({ 168 | data: { 169 | before: 'changed', 170 | beforeNothing: 'before', 171 | after: 'changed', 172 | afterNothing: 'after', 173 | null: null, 174 | nested: { nothing: 'nothing' }, 175 | }, 176 | }) 177 | }) 178 | 179 | test('field middleware subscriptions', async () => { 180 | const schema = getSchema() 181 | 182 | const fieldMiddleware: IMiddlewareTypeMap = { 183 | Subscription: { 184 | sub: async (resolve, parent, args, context, info) => { 185 | const _args = { arg: 'changed' } 186 | return resolve(parent, _args) 187 | }, 188 | }, 189 | } 190 | const schemaWithMiddleware = applyMiddleware(schema, fieldMiddleware) 191 | 192 | const query = ` 193 | subscription { 194 | sub 195 | } 196 | ` 197 | const iterator = await subscribe({ 198 | schema: schemaWithMiddleware, 199 | document: parse(query), 200 | }) 201 | const res = await (iterator as AsyncIterator).next() 202 | 203 | /* Tests. */ 204 | 205 | expect(res).toEqual({ 206 | done: false, 207 | value: { 208 | data: { 209 | sub: 'changed', 210 | }, 211 | }, 212 | }) 213 | }) 214 | 215 | test('type middleware before', async () => { 216 | const schema = getSchema() 217 | const schemaWithMiddleware = applyMiddleware(schema, typeMiddlewareBefore) 218 | 219 | const query = ` 220 | query { 221 | before(arg: "before") 222 | beforeNothing(arg: "before") 223 | after 224 | afterNothing 225 | null 226 | nested { nothing } 227 | } 228 | ` 229 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 230 | 231 | /* Tests. */ 232 | 233 | expect(res).toEqual({ 234 | data: { 235 | before: 'changed', 236 | beforeNothing: 'changed', 237 | after: 'after', 238 | afterNothing: 'after', 239 | null: null, 240 | nested: { nothing: 'nothing' }, 241 | }, 242 | }) 243 | }) 244 | 245 | test('type middleware after', async () => { 246 | const schema = getSchema() 247 | const schemaWithMiddleware = applyMiddleware(schema, typeMiddlewareAfter) 248 | 249 | const query = ` 250 | query { 251 | before(arg: "before") 252 | beforeNothing(arg: "before") 253 | after 254 | afterNothing 255 | null 256 | nested { nothing } 257 | } 258 | ` 259 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 260 | 261 | /* Tests. */ 262 | 263 | expect(res).toEqual({ 264 | data: { 265 | before: 'changed', 266 | beforeNothing: 'changed', 267 | after: 'changed', 268 | afterNothing: 'changed', 269 | null: 'changed', 270 | nested: { nothing: 'nothing' }, 271 | }, 272 | }) 273 | }) 274 | 275 | test('type middleware subscriptions', async () => { 276 | const schema = getSchema() 277 | const schemaWithMiddleware = applyMiddleware(schema, typeMiddlewareBefore) 278 | 279 | const query = ` 280 | subscription { 281 | sub 282 | } 283 | ` 284 | const iterator = await subscribe({ 285 | schema: schemaWithMiddleware, 286 | document: parse(query), 287 | }) 288 | const res = await (iterator as AsyncIterator).next() 289 | 290 | expect(res).toEqual({ 291 | done: false, 292 | value: { 293 | data: { 294 | sub: 'changed', 295 | }, 296 | }, 297 | }) 298 | }) 299 | 300 | test('schema middleware before', async () => { 301 | const schema = getSchema() 302 | const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore) 303 | 304 | const query = ` 305 | query { 306 | before(arg: "before") 307 | beforeNothing(arg: "before") 308 | after 309 | afterNothing 310 | null 311 | nested { nothing } 312 | } 313 | ` 314 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 315 | 316 | /* Tests. */ 317 | 318 | expect(res).toEqual({ 319 | data: { 320 | before: 'changed', 321 | beforeNothing: 'changed', 322 | after: 'after', 323 | afterNothing: 'after', 324 | null: null, 325 | nested: { nothing: 'nothing' }, 326 | }, 327 | }) 328 | }) 329 | 330 | test('schema middleware after', async () => { 331 | const schema = getSchema() 332 | const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareAfter) 333 | 334 | const query = ` 335 | query { 336 | before(arg: "before") 337 | beforeNothing(arg: "before") 338 | after 339 | afterNothing 340 | null 341 | nested { nothing } 342 | } 343 | ` 344 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 345 | 346 | /* Tests. */ 347 | 348 | expect(res).toEqual({ 349 | data: { 350 | before: 'changed', 351 | beforeNothing: 'changed', 352 | after: 'changed', 353 | afterNothing: 'changed', 354 | null: 'changed', 355 | nested: { nothing: 'changed' }, 356 | }, 357 | }) 358 | }) 359 | 360 | test('schema middleware before', async () => { 361 | const schema = getSchema() 362 | const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore) 363 | 364 | const query = ` 365 | query { 366 | before(arg: "before") 367 | beforeNothing(arg: "before") 368 | after 369 | afterNothing 370 | null 371 | nested { nothing } 372 | } 373 | ` 374 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 375 | 376 | /* Tests. */ 377 | 378 | expect(res).toEqual({ 379 | data: { 380 | before: 'changed', 381 | beforeNothing: 'changed', 382 | after: 'after', 383 | afterNothing: 'after', 384 | null: null, 385 | nested: { nothing: 'nothing' }, 386 | }, 387 | }) 388 | }) 389 | 390 | test('schema middleware subscription', async () => { 391 | const schema = getSchema() 392 | const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore) 393 | 394 | const query = ` 395 | subscription { 396 | sub 397 | } 398 | ` 399 | const iterator = await subscribe({ 400 | schema: schemaWithMiddleware, 401 | document: parse(query), 402 | }) 403 | const res = await (iterator as AsyncIterator).next() 404 | 405 | expect(res).toEqual({ 406 | done: false, 407 | value: { 408 | data: { 409 | sub: 'changed', 410 | }, 411 | }, 412 | }) 413 | }) 414 | 415 | test('schema middleware uses default field resolver', async () => { 416 | const schema = getSchema() 417 | const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore) 418 | 419 | const query = ` 420 | query { 421 | resolverless { 422 | someData 423 | } 424 | } 425 | ` 426 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 427 | 428 | expect(res).toEqual({ 429 | data: { 430 | resolverless: { 431 | someData: 'data', 432 | }, 433 | }, 434 | }) 435 | }) 436 | }) 437 | -------------------------------------------------------------------------------- /tests/execution.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | import { graphql } from 'graphql' 3 | import { 4 | applyMiddleware, 5 | applyMiddlewareToDeclaredResolvers, 6 | IMiddlewareFunction, 7 | } from '../src' 8 | import { IResolvers } from '../src/types' 9 | 10 | describe('execution:', () => { 11 | test('follows correct order', async () => { 12 | // Schema 13 | 14 | const typeDefs = ` 15 | type Query { 16 | test: String! 17 | } 18 | ` 19 | 20 | const resolvers = { 21 | Query: { 22 | test: () => 'pass', 23 | }, 24 | } 25 | 26 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 27 | 28 | /* Middleware tests. */ 29 | 30 | let sequence: string[] = [] 31 | 32 | const firstMiddleware: IMiddlewareFunction = async (resolve) => { 33 | sequence.push('first') 34 | return resolve() 35 | } 36 | 37 | const secondMiddleware: IMiddlewareFunction = async (resolve) => { 38 | sequence.push('second') 39 | return resolve() 40 | } 41 | 42 | const schemaWithMiddleware = applyMiddleware( 43 | schema, 44 | firstMiddleware, 45 | secondMiddleware, 46 | ) 47 | 48 | const query = ` 49 | query { 50 | test 51 | } 52 | ` 53 | await graphql({ 54 | schema: schemaWithMiddleware, 55 | source: query, 56 | rootValue: null, 57 | contextValue: {}, 58 | }) 59 | 60 | /* Tests */ 61 | 62 | expect(JSON.stringify(sequence)).toBe(JSON.stringify(['first', 'second'])) 63 | }) 64 | 65 | test('forwards arguments correctly', async () => { 66 | /* Schema. */ 67 | 68 | const typeDefs = ` 69 | type Query { 70 | test(arg: String): String! 71 | } 72 | ` 73 | 74 | const resolvers: IResolvers = { 75 | Query: { 76 | test: (parent, { arg }) => arg, 77 | }, 78 | } 79 | 80 | const schema = makeExecutableSchema({ resolvers, typeDefs }) 81 | 82 | /* Middleware. */ 83 | 84 | const randomTestString = Math.random().toString() 85 | 86 | const middleware: IMiddlewareFunction = (resolve, parent) => { 87 | return resolve(parent, { arg: randomTestString }) 88 | } 89 | 90 | const schemaWithMiddleware = applyMiddleware(schema, middleware) 91 | 92 | const query = ` 93 | query { 94 | test(arg: "none") 95 | } 96 | ` 97 | 98 | const res = await graphql({ schema: schemaWithMiddleware, source: query }) 99 | 100 | /* Tests. */ 101 | 102 | expect(res).toEqual({ 103 | data: { 104 | test: randomTestString, 105 | }, 106 | }) 107 | }) 108 | 109 | test('applies multiple middlwares only to declared resolvers', async () => { 110 | /* Schema. */ 111 | 112 | const typeDefs = ` 113 | type Object { 114 | id: String, 115 | name: String 116 | } 117 | type Query { 118 | test(arg: String): Object! 119 | } 120 | ` 121 | 122 | const resolvers: IResolvers = { 123 | Query: { 124 | test: (parent, { arg }) => ({ id: arg, name: 'name' }), 125 | }, 126 | } 127 | 128 | const schema = makeExecutableSchema({ resolvers, typeDefs }) 129 | 130 | /* Middleware. */ 131 | 132 | const randomTestString = Math.random().toString() 133 | 134 | const firstMiddleware: IMiddlewareFunction = jest.fn((resolve, parent) => { 135 | return resolve(parent, { arg: randomTestString }) 136 | }) 137 | const secondMiddleware: IMiddlewareFunction = jest.fn((resolve, parent) => { 138 | return resolve(parent, { arg: randomTestString }) 139 | }) 140 | 141 | const schemaWithMiddleware = applyMiddlewareToDeclaredResolvers( 142 | schema, 143 | firstMiddleware, 144 | secondMiddleware, 145 | ) 146 | 147 | const query = ` 148 | query { 149 | test(arg: "id") { 150 | id 151 | name 152 | } 153 | } 154 | ` 155 | 156 | await graphql({ schema: schemaWithMiddleware, source: query }) 157 | 158 | /* Tests. */ 159 | expect(firstMiddleware).toHaveBeenCalledTimes(1) 160 | expect(secondMiddleware).toHaveBeenCalledTimes(1) 161 | }) 162 | }) 163 | -------------------------------------------------------------------------------- /tests/fragments.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | import { $$asyncIterator } from 'iterall' 3 | import { 4 | applyMiddleware, 5 | applyMiddlewareToDeclaredResolvers, 6 | IMiddlewareFunction, 7 | IMiddlewareTypeMap, 8 | } from '../src' 9 | import { IResolvers } from '../src/types' 10 | 11 | /** 12 | * Tests whether graphql-middleware-tool correctly applies middleware to fields it 13 | * ought to impact based on the width of the middleware specification. 14 | */ 15 | describe('fragments:', () => { 16 | test('schema-wide middleware', async () => { 17 | /* Schema. */ 18 | const typeDefs = ` 19 | type Query { 20 | book: Book! 21 | } 22 | 23 | type Book { 24 | id: ID! 25 | name: String! 26 | content: String! 27 | author: String! 28 | } 29 | ` 30 | 31 | const resolvers = { 32 | Query: { 33 | book() { 34 | return { 35 | id: 'id', 36 | name: 'name', 37 | content: 'content', 38 | author: 'author', 39 | } 40 | }, 41 | }, 42 | } 43 | 44 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 45 | 46 | /* Middleware. */ 47 | 48 | const schemaMiddlewareWithFragment: IMiddlewareFunction = { 49 | fragment: `fragment NodeID on Node { id }`, 50 | resolve: (resolve) => resolve(), 51 | } 52 | 53 | const { fragmentReplacements } = applyMiddleware( 54 | schema, 55 | schemaMiddlewareWithFragment, 56 | ) 57 | 58 | /* Tests. */ 59 | 60 | expect(fragmentReplacements).toEqual([ 61 | { field: 'book', fragment: '... on Node {\n id\n}' }, 62 | { field: 'name', fragment: '... on Node {\n id\n}' }, 63 | { field: 'content', fragment: '... on Node {\n id\n}' }, 64 | { field: 'author', fragment: '... on Node {\n id\n}' }, 65 | ]) 66 | }) 67 | 68 | test('type-wide middleware', async () => { 69 | /* Schema. */ 70 | const typeDefs = ` 71 | type Query { 72 | book: Book! 73 | author: Author! 74 | } 75 | 76 | type Book { 77 | id: ID! 78 | content: String! 79 | author: String! 80 | } 81 | 82 | type Author { 83 | id: ID! 84 | name: String! 85 | } 86 | ` 87 | 88 | const resolvers = { 89 | Query: { 90 | book() { 91 | return { 92 | id: 'id', 93 | content: 'content', 94 | author: 'author', 95 | } 96 | }, 97 | author() { 98 | return { 99 | name: 'name', 100 | } 101 | }, 102 | }, 103 | } 104 | 105 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 106 | 107 | // Middleware 108 | 109 | const typeMiddlewareWithFragment: IMiddlewareTypeMap = { 110 | Book: { 111 | fragment: `fragment BookId on Book { id }`, 112 | resolve: (resolve) => resolve(), 113 | }, 114 | Author: { 115 | fragments: [`... on Author { id }`, `... on Author { name }`], 116 | resolve: (resolve) => resolve(), 117 | }, 118 | } 119 | 120 | const { fragmentReplacements } = applyMiddleware( 121 | schema, 122 | typeMiddlewareWithFragment, 123 | ) 124 | 125 | /* Tests. */ 126 | 127 | expect(fragmentReplacements).toEqual([ 128 | { 129 | field: 'content', 130 | fragment: '... on Book {\n id\n}', 131 | }, 132 | { 133 | field: 'author', 134 | fragment: '... on Book {\n id\n}', 135 | }, 136 | { 137 | field: 'id', 138 | fragment: '... on Author {\n name\n}', 139 | }, 140 | { 141 | field: 'name', 142 | fragment: '... on Author {\n id\n}', 143 | }, 144 | ]) 145 | }) 146 | 147 | test('field-specific middleware', async () => { 148 | const typeDefs = ` 149 | type Query { 150 | book: Book! 151 | } 152 | 153 | type Book { 154 | id: ID! 155 | name: String! 156 | content: String! 157 | author: String! 158 | } 159 | ` 160 | 161 | const resolvers = { 162 | Query: { 163 | book() { 164 | return { 165 | id: 'id', 166 | name: 'name', 167 | content: 'content', 168 | author: 'author', 169 | } 170 | }, 171 | }, 172 | } 173 | 174 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 175 | 176 | // Middleware 177 | 178 | const fieldMiddlewareWithFragment: IMiddlewareTypeMap = { 179 | Book: { 180 | content: { 181 | fragment: `fragment BookId on Book { id ... on Book { name } }`, 182 | resolve: (resolve) => resolve(), 183 | }, 184 | author: { 185 | fragments: [ 186 | `fragment BookId on Book { id }`, 187 | `fragment BookContent on Book { content }`, 188 | ], 189 | resolve: (resolve) => resolve(), 190 | }, 191 | }, 192 | } 193 | 194 | const { fragmentReplacements } = applyMiddleware( 195 | schema, 196 | fieldMiddlewareWithFragment, 197 | ) 198 | 199 | /* Tests. */ 200 | 201 | expect(fragmentReplacements).toEqual([ 202 | { 203 | field: 'content', 204 | fragment: '... on Book {\n id\n ... on Book {\n name\n }\n}', 205 | }, 206 | { 207 | field: 'author', 208 | fragment: '... on Book {\n id\n}', 209 | }, 210 | { 211 | field: 'author', 212 | fragment: '... on Book {\n content\n}', 213 | }, 214 | ]) 215 | }) 216 | 217 | test('subscription fragment', async () => { 218 | /* Schema. */ 219 | const typeDefs = ` 220 | type Query { 221 | book(id: ID!): Book! 222 | } 223 | 224 | type Subscription { 225 | book(id: ID!): Book! 226 | } 227 | 228 | type Book { 229 | id: ID! 230 | name: String! 231 | } 232 | 233 | schema { 234 | query: Query, 235 | subscription: Subscription 236 | } 237 | ` 238 | 239 | const resolvers: IResolvers = { 240 | Query: { 241 | book() { 242 | return { 243 | id: 'id', 244 | name: 'name', 245 | } 246 | }, 247 | }, 248 | Subscription: { 249 | book: { 250 | subscribe: async (parent, { id }) => { 251 | const iterator = { 252 | next: () => Promise.resolve({ done: false, value: { sub: id } }), 253 | return: () => { 254 | return 255 | }, 256 | throw: () => { 257 | return 258 | }, 259 | [$$asyncIterator]: () => iterator, 260 | } 261 | return iterator 262 | }, 263 | }, 264 | }, 265 | } 266 | 267 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 268 | 269 | /* Middleware. */ 270 | 271 | const fieldMiddlewareWithFragment: IMiddlewareTypeMap = { 272 | Subscription: { 273 | book: { 274 | fragment: `fragment Ignored on Book { ignore }`, 275 | resolve: (resolve) => resolve(), 276 | }, 277 | }, 278 | } 279 | 280 | const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers( 281 | schema, 282 | fieldMiddlewareWithFragment, 283 | ) 284 | 285 | /* Tests. */ 286 | 287 | expect(fragmentReplacements).toEqual([ 288 | { 289 | field: 'book', 290 | fragment: '... on Book {\n ignore\n}', 291 | }, 292 | ]) 293 | }) 294 | }) 295 | 296 | describe('fragments on declared resolvers:', () => { 297 | test('schema-wide middleware', async () => { 298 | /* Schema. */ 299 | const typeDefs = ` 300 | type Query { 301 | book: Book! 302 | } 303 | 304 | type Book { 305 | id: ID! 306 | name: String! 307 | content: String! 308 | author: String! 309 | } 310 | ` 311 | 312 | const resolvers = { 313 | Query: { 314 | book() { 315 | return { 316 | id: 'id', 317 | name: 'name', 318 | content: 'content', 319 | author: 'author', 320 | } 321 | }, 322 | }, 323 | } 324 | 325 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 326 | 327 | /* Middleware. */ 328 | 329 | const schemaMiddlewareWithFragment: IMiddlewareFunction = { 330 | fragment: `fragment NodeId on Node { id }`, 331 | resolve: (resolve) => resolve(), 332 | } 333 | 334 | const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers( 335 | schema, 336 | schemaMiddlewareWithFragment, 337 | ) 338 | 339 | /* Tests. */ 340 | 341 | expect(fragmentReplacements).toEqual([ 342 | { field: 'book', fragment: '... on Node {\n id\n}' }, 343 | ]) 344 | }) 345 | 346 | test('type-wide middleware', async () => { 347 | /* Schema. */ 348 | const typeDefs = ` 349 | type Query { 350 | book: Book! 351 | } 352 | 353 | type Book { 354 | id: ID! 355 | name: String! 356 | content: String! 357 | author: String! 358 | } 359 | ` 360 | 361 | const resolvers = { 362 | Query: { 363 | book() { 364 | return { 365 | id: 'id', 366 | name: 'name', 367 | content: 'content', 368 | author: 'author', 369 | } 370 | }, 371 | }, 372 | } 373 | 374 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 375 | 376 | /* Middleware. */ 377 | 378 | const typeMiddlewareWithFragment: IMiddlewareTypeMap = { 379 | Query: { 380 | fragments: [`fragment QueryViewer on Query { viewer }`], 381 | resolve: (resolve) => resolve(), 382 | }, 383 | Book: { 384 | fragment: `... on Book { id }`, 385 | resolve: (resolve) => resolve(), 386 | }, 387 | } 388 | 389 | const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers( 390 | schema, 391 | typeMiddlewareWithFragment, 392 | ) 393 | 394 | /* Tests. */ 395 | 396 | expect(fragmentReplacements).toEqual([ 397 | { 398 | field: 'book', 399 | fragment: '... on Query {\n viewer\n}', 400 | }, 401 | ]) 402 | }) 403 | 404 | test('field-specific middleware', async () => { 405 | /* Schema. */ 406 | const typeDefs = ` 407 | type Query { 408 | book: Book! 409 | } 410 | 411 | type Book { 412 | id: ID! 413 | name: String! 414 | content: String! 415 | author: String! 416 | } 417 | ` 418 | 419 | const resolvers = { 420 | Query: { 421 | book() { 422 | return {} 423 | }, 424 | }, 425 | Book: { 426 | id: () => 'id', 427 | name: () => 'name', 428 | content: () => 'content', 429 | author: () => 'author', 430 | }, 431 | } 432 | 433 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 434 | 435 | /* Middleware. */ 436 | 437 | const fieldMiddlewareWithFragment: IMiddlewareTypeMap = { 438 | Book: { 439 | id: { 440 | fragment: `fragment Ignored on Book { ignore }`, 441 | resolve: (resolve) => resolve(), 442 | }, 443 | content: { 444 | fragment: `fragment BookId on Book { id }`, 445 | resolve: (resolve) => resolve(), 446 | }, 447 | author: { 448 | fragments: [ 449 | `fragment AuthorId on Author { id }`, 450 | `fragment AuthorName on Author { name }`, 451 | ], 452 | resolve: (resolve) => resolve(), 453 | }, 454 | }, 455 | } 456 | 457 | const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers( 458 | schema, 459 | fieldMiddlewareWithFragment, 460 | ) 461 | 462 | /* Tests. */ 463 | 464 | expect(fragmentReplacements).toEqual([ 465 | { 466 | field: 'id', 467 | fragment: '... on Book {\n ignore\n}', 468 | }, 469 | { 470 | field: 'content', 471 | fragment: '... on Book {\n id\n}', 472 | }, 473 | { 474 | field: 'author', 475 | fragment: '... on Author {\n id\n}', 476 | }, 477 | { 478 | field: 'author', 479 | fragment: '... on Author {\n name\n}', 480 | }, 481 | ]) 482 | }) 483 | }) 484 | 485 | test('imparsable fragment', async () => { 486 | /* Schema. */ 487 | const typeDefs = ` 488 | type Query { 489 | book: String! 490 | } 491 | ` 492 | 493 | const resolvers = { 494 | Query: { 495 | book() { 496 | return 'book' 497 | }, 498 | }, 499 | } 500 | 501 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 502 | 503 | /* Middleware. */ 504 | 505 | const fieldMiddlewareWithFragment: IMiddlewareFunction = { 506 | fragment: 'foo', 507 | resolve: (resolve) => resolve(), 508 | } 509 | 510 | /* Tests. */ 511 | 512 | expect(() => { 513 | applyMiddlewareToDeclaredResolvers(schema, fieldMiddlewareWithFragment) 514 | }).toThrow('Could not parse fragment') 515 | }) 516 | -------------------------------------------------------------------------------- /tests/generator.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | import { GraphQLSchema } from 'graphql' 3 | import { applyMiddleware, middleware, IMiddlewareGenerator } from '../src' 4 | 5 | test('middleware generator integration', async () => { 6 | /* Schema. */ 7 | const typeDefs = ` 8 | type Query { 9 | test: String! 10 | } 11 | ` 12 | 13 | const resolvers = { 14 | Query: { 15 | test: () => 'fail', 16 | }, 17 | } 18 | 19 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 20 | 21 | let generatorSchema: GraphQLSchema 22 | 23 | /* Middleware. */ 24 | 25 | const testMiddleware: IMiddlewareGenerator = middleware( 26 | (_schema) => { 27 | generatorSchema = _schema 28 | return async () => 'pass' 29 | }, 30 | ) 31 | 32 | applyMiddleware(schema, testMiddleware) 33 | 34 | /* Tests. */ 35 | 36 | expect(generatorSchema).toEqual(schema) 37 | }) 38 | -------------------------------------------------------------------------------- /tests/immutability.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | import { graphql } from 'graphql' 3 | import { applyMiddleware, IMiddleware } from '../src' 4 | 5 | type Context = { 6 | middlewareCalled: boolean 7 | } 8 | 9 | test('immutable', async () => { 10 | const typeDefs = ` 11 | type Query { 12 | test: Boolean! 13 | } 14 | ` 15 | const resolvers = { 16 | Query: { 17 | test: (_: unknown, __: unknown, ctx: Context) => ctx.middlewareCalled, 18 | }, 19 | } 20 | 21 | const middleware: IMiddleware = ( 22 | resolve, 23 | parent, 24 | args, 25 | ctx: Context, 26 | info, 27 | ) => { 28 | ctx.middlewareCalled = true 29 | return resolve(parent, args, { ...ctx, middlewareCalled: true }, info) 30 | } 31 | const middlewares = { 32 | Query: { 33 | test: middleware, 34 | }, 35 | } 36 | 37 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 38 | const schemaWithMiddlewares = applyMiddleware(schema, middlewares) 39 | 40 | const query = ` 41 | query TestQuery { 42 | test 43 | } 44 | ` 45 | 46 | const responseWithMiddleware = await graphql({ 47 | schema: schemaWithMiddlewares, 48 | source: query, 49 | contextValue: { middlewareCalled: false }, 50 | }) 51 | expect(responseWithMiddleware.errors).toBeUndefined() 52 | expect(responseWithMiddleware.data!.test).toEqual(true) 53 | 54 | const responseWihoutMiddleware = await graphql({ 55 | schema, 56 | source: query, 57 | rootValue: {}, 58 | contextValue: { middlewareCalled: false }, 59 | variableValues: {}, 60 | }) 61 | expect(responseWihoutMiddleware.errors).toBeUndefined() 62 | expect(responseWihoutMiddleware.data!.test).toEqual(false) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/integration.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | // import { GraphQLServer as YogaServer } from 'graphql-yoga' 3 | import { ApolloServer } from 'apollo-server' 4 | import Axios from 'axios' 5 | // import { AddressInfo } from 'ws' 6 | import { applyMiddleware } from '../src' 7 | 8 | describe('integrations', () => { 9 | /* GraphQL Yoga */ 10 | 11 | // https://github.com/prisma-labs/graphql-yoga/issues/449 12 | // We might bring back support for GraphQL Yoga if they start 13 | // supporting new GraphQL versions. 14 | // 15 | // test('GraphQL Yoga', async () => { 16 | // const typeDefs = ` 17 | // type Query { 18 | // test: String! 19 | // } 20 | // ` 21 | 22 | // const resolvers = { 23 | // Query: { 24 | // test: () => 'test', 25 | // }, 26 | // } 27 | 28 | // const schema = makeExecutableSchema({ typeDefs, resolvers }) 29 | 30 | // const schemaWithMiddleware = applyMiddleware(schema, async (resolve) => { 31 | // const res = await resolve() 32 | // return `pass-${res}` 33 | // }) 34 | 35 | // const server = new YogaServer({ 36 | // schema: schemaWithMiddleware, 37 | // }) 38 | // const http = await server.start({ port: 0 }) 39 | // try { 40 | // const { port } = http.address() as AddressInfo 41 | // const uri = `http://localhost:${port}/` 42 | 43 | // /* Tests */ 44 | 45 | // const query = ` 46 | // query { 47 | // test 48 | // } 49 | // ` 50 | 51 | // const body = await Axios.post(uri, { 52 | // query, 53 | // }) 54 | 55 | // /* Tests. */ 56 | 57 | // expect(body.data).toEqual({ 58 | // data: { 59 | // test: 'pass-test', 60 | // }, 61 | // }) 62 | // } finally { 63 | // http.close() 64 | // } 65 | // }) 66 | 67 | /* Apollo Server */ 68 | 69 | test('ApolloServer', async () => { 70 | /* Schema. */ 71 | const typeDefs = ` 72 | type Query { 73 | test: String! 74 | } 75 | ` 76 | 77 | const resolvers = { 78 | Query: { 79 | test: () => 'test', 80 | }, 81 | } 82 | 83 | const schema = makeExecutableSchema({ typeDefs, resolvers }) 84 | 85 | const schemaWithMiddleware = applyMiddleware(schema, async (resolve) => { 86 | const res = await resolve() 87 | return `pass-${res}` 88 | }) 89 | 90 | const server = new ApolloServer({ 91 | schema: schemaWithMiddleware, 92 | }) 93 | 94 | await server.listen({ port: 8008 }) 95 | 96 | const uri = `http://localhost:8008/` 97 | 98 | /* Tests */ 99 | 100 | const query = ` 101 | query { 102 | test 103 | } 104 | ` 105 | try { 106 | const body = await Axios.post(uri, { query }) 107 | /* Tests. */ 108 | 109 | expect(body.data).toEqual({ 110 | data: { 111 | test: 'pass-test', 112 | }, 113 | }) 114 | } finally { 115 | await server.stop() 116 | } 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /tests/validation.test.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from '@graphql-tools/schema' 2 | import { validateMiddleware, MiddlewareError } from '../src/validation' 3 | import { IMiddlewareFieldMap, IMiddlewareTypeMap } from '../src' 4 | 5 | describe('validation:', () => { 6 | test('warns about the unknown type', async () => { 7 | /* Schema. */ 8 | const typeDefs = ` 9 | type Query { 10 | pass: String! 11 | } 12 | ` 13 | 14 | const resolvers = { 15 | Query: { 16 | pass: () => 'pass', 17 | }, 18 | } 19 | 20 | const schema = makeExecutableSchema({ resolvers, typeDefs }) 21 | 22 | /* Middleware. */ 23 | 24 | const middlewareWithUndefinedType: IMiddlewareFieldMap = { 25 | Test: async () => ({}), 26 | } 27 | 28 | /* Tests. */ 29 | 30 | expect(() => { 31 | validateMiddleware(schema, middlewareWithUndefinedType) 32 | }).toThrow( 33 | new MiddlewareError( 34 | `Type Test exists in middleware but is missing in Schema.`, 35 | ), 36 | ) 37 | }) 38 | 39 | test('warns about the unknown field', async () => { 40 | /* Schema. */ 41 | const typeDefs = ` 42 | type Query { 43 | pass: String! 44 | } 45 | ` 46 | 47 | const resolvers = { 48 | Query: { 49 | pass: () => 'pass', 50 | }, 51 | } 52 | 53 | const schema = makeExecutableSchema({ resolvers, typeDefs }) 54 | 55 | /* Middleware. */ 56 | 57 | const middlewareWithUndefinedField: IMiddlewareTypeMap = { 58 | Query: { 59 | test: async () => ({}), 60 | }, 61 | } 62 | 63 | /* Tests. */ 64 | 65 | expect(() => { 66 | validateMiddleware(schema, middlewareWithUndefinedField) 67 | }).toThrow( 68 | new MiddlewareError( 69 | `Field Query.test exists in middleware but is missing in Schema.`, 70 | ), 71 | ) 72 | }) 73 | 74 | test('warns that middleware leafs are not functions', async () => { 75 | /* Schema. */ 76 | const typeDefs = ` 77 | type Query { 78 | test: String! 79 | } 80 | ` 81 | 82 | const resolvers = { 83 | Query: { 84 | test: () => 'pass', 85 | }, 86 | } 87 | 88 | const schema = makeExecutableSchema({ resolvers, typeDefs }) 89 | 90 | /* Middleware. */ 91 | 92 | const middlewareWithObjectField: any = { 93 | Query: { 94 | test: false, 95 | }, 96 | } 97 | 98 | /* Tests. */ 99 | 100 | expect(() => { 101 | validateMiddleware(schema, middlewareWithObjectField as any) 102 | }).toThrow( 103 | new MiddlewareError( 104 | `Expected Query.test to be a function but found boolean`, 105 | ), 106 | ) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "moduleResolution": "node", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "rootDir": "src", 8 | "outDir": "dist", 9 | "lib": ["esnext"], 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "emitDeclarationOnly": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "dist"] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "moduleResolution": "node", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "rootDir": "src", 8 | "outDir": "dist", 9 | "lib": ["esnext"], 10 | "skipLibCheck": true 11 | }, 12 | "include": ["tests/**/*"], 13 | "exclude": ["node_modules", "dist"] 14 | } 15 | --------------------------------------------------------------------------------