├── .editorconfig ├── .eslintrc ├── .gitignore ├── .lintstagedrc.js ├── .npmignore ├── .nycrc ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── Logger.ts ├── factories │ ├── createLazyLoadMiddleware.ts │ └── index.ts └── index.ts ├── test ├── .eslintrc ├── graphql-lazyloader │ ├── createLazyLoadMiddleware.ts │ └── graphqlServer.ts └── helpers │ ├── createGraphqlServer.ts │ └── createSchemaWithMiddleware.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "canonical", 4 | "canonical/typescript" 5 | ], 6 | "parserOptions": { 7 | "project": "./tsconfig.json" 8 | }, 9 | "root": true, 10 | "rules": { 11 | "@typescript-eslint/no-explicit-any": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | node_modules 4 | *.log 5 | .* 6 | !.babelrc 7 | !.editorconfig 8 | !.eslintignore 9 | !.eslintrc 10 | !.gitignore 11 | !.lintstagedrc.js 12 | !.npmignore 13 | !.nycrc 14 | !.travis.yml 15 | /package-lock.json 16 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{ts}': ['eslint --fix'], 3 | }; 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | /coverage 3 | .* 4 | *.log 5 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "all": true 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: focal 3 | arch: arm64-graviton2 4 | language: node_js 5 | node_js: 6 | - node 7 | - 10 8 | script: 9 | - npm run lint 10 | - npm run test 11 | - nyc --silent npm run test 12 | - nyc report --reporter=text-lcov | coveralls 13 | - nyc check-coverage --lines 60 14 | after_success: 15 | - NODE_ENV=production npm run build 16 | - semantic-release 17 | notifications: 18 | email: false 19 | sudo: false 20 | cache: npm 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Gajus Kuizinas (http://gajus.com/) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-lazyloader 🛋 2 | 3 | [![Travis build status](https://img.shields.io/travis/com/gajus/graphql-lazyloader?style=flat-square)](https://travis-ci.com/gajus/graphql-lazyloader) 4 | [![Coveralls](https://img.shields.io/coveralls/gajus/graphql-lazyloader.svg?style=flat-square)](https://coveralls.io/github/gajus/graphql-lazyloader) 5 | [![NPM version](http://img.shields.io/npm/v/graphql-lazyloader.svg?style=flat-square)](https://www.npmjs.org/package/graphql-lazyloader) 6 | [![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical) 7 | [![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas) 8 | 9 | GraphQL directive that adds Object-level data resolvers. 10 | 11 | * [Motivation](#motivation) 12 | * [Usage](#usage) 13 | * [Usage Example](#usage-example) 14 | 15 | ## Motivation 16 | 17 | Several years ago I read [GraphQL Resolvers: Best Practices](https://medium.com/paypal-engineering/graphql-resolvers-best-practices-cd36fdbcef55) (2018), an article written by PayPal team, that changed my view about where / when data resolution should happen. 18 | 19 | Let's start with an example GraphQL schema: 20 | 21 | ```graphql 22 | type Query { 23 | person(id: ID) Person! 24 | } 25 | 26 | type Person { 27 | id: ID! 28 | givenName: String! 29 | familyName: String! 30 | } 31 | 32 | ``` 33 | 34 | A typical GraphQL server uses "top-heavy" (parent-to-child) resolvers, i.e. in the above example, `Query.person` is responsible for fetching data for `Person` object. It may look something like this: 35 | 36 | ```js 37 | { 38 | Query: { 39 | person: (root, args) => { 40 | return getPerson(args.id); 41 | }, 42 | }, 43 | }; 44 | 45 | ``` 46 | 47 | PayPal team argues that this pattern is prone to data over-fetching. Instead, they propose to move data fetching logic to _every_ field of `Person`, e.g. 48 | 49 | ```js 50 | { 51 | Query: { 52 | person: (root, args) => { 53 | return { 54 | id: args.id, 55 | }; 56 | }, 57 | }, 58 | Person: { 59 | givenName: async ({id}) => { 60 | const { 61 | givenName, 62 | } = await getPerson(id); 63 | 64 | return givenName; 65 | }, 66 | familyName: async ({id}) => { 67 | const { 68 | familyName, 69 | } = await getPerson(id); 70 | 71 | return givenName; 72 | }, 73 | }, 74 | }; 75 | 76 | ``` 77 | 78 | It is important to note that the above example assume that `getPerson` is implemented using a [DataLoader](https://github.com/graphql/dataloader) pattern, i.e. data is fetched only once. 79 | 80 | According to the original authors, this pattern is better because: 81 | 82 | > * This code is easy to reason about. You know exactly where [givenName] is fetched. This makes for easy debugging. 83 | > * This code is more testable. You don't have to test the [person] resolver when you really just wanted to test the [givenName] resolver. 84 | > 85 | > To some, the [getPerson] duplication might look like a code smell. But, having code that is simple, easy to reason about, and is more testable is worth a little bit of duplication. 86 | 87 | For this and other reasons, I became a fan ❤️ of this pattern and have since implemented it in multiple projects. However, the particular implementation proposed by PayPal is pretty verbose. `graphql-lazyloader` abstracts the above logic into a single [GraphQL middleware](https://github.com/maticzav/graphql-middleware) (see [Usage Example](#usage-example)). 88 | 89 | ## Usage 90 | 91 | `graphql-lazyloader` is added using `graphql-middleware` 92 | 93 | ### Usage Example 94 | 95 | ```js 96 | import { 97 | ApolloServer, 98 | gql, 99 | } from 'apollo-server'; 100 | import { 101 | makeExecutableSchema, 102 | } from '@graphql-tools/schema'; 103 | import { 104 | applyMiddleware, 105 | } from 'graphql-middleware'; 106 | import { 107 | createLazyLoadMiddleware, 108 | } from 'graphql-lazyloader'; 109 | 110 | const lazyLoadMiddleware = createLazyLoadMiddleware({ 111 | Person: ({id}) => { 112 | return getPerson(id); 113 | }, 114 | }); 115 | 116 | const typeDefs = gql` 117 | type Query { 118 | person(id: ID!): Person! 119 | } 120 | 121 | type Person { 122 | id: ID! 123 | givenName: String! 124 | familyName: String! 125 | } 126 | `; 127 | 128 | const resolvers = { 129 | Query: { 130 | person: () => { 131 | return { 132 | id: '1', 133 | }; 134 | }, 135 | }, 136 | }; 137 | 138 | const schema = makeExecutableSchema({ 139 | resolvers, 140 | typeDefs, 141 | }); 142 | 143 | const schemaWithMiddleware = applyMiddleware( 144 | schema, 145 | lazyLoadMiddleware, 146 | ); 147 | 148 | const server = new ApolloServer({ 149 | schema: schemaWithMiddleware, 150 | }); 151 | 152 | ``` 153 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "gajus@gajus.com", 4 | "name": "Gajus Kuizinas", 5 | "url": "http://gajus.com" 6 | }, 7 | "ava": { 8 | "extensions": [ 9 | "ts" 10 | ], 11 | "files": [ 12 | "test/graphql-lazyloader/**/*" 13 | ], 14 | "require": [ 15 | "ts-node/register/transpile-only" 16 | ] 17 | }, 18 | "dependencies": { 19 | "@types/roarr": "^2.14.3", 20 | "roarr": "^7.7.0" 21 | }, 22 | "description": "GraphQL directive that adds Object-level data resolvers.", 23 | "devDependencies": { 24 | "@graphql-tools/schema": "^8.3.1", 25 | "@graphql-tools/utils": "^8.5.3", 26 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 27 | "@types/sinon": "^10.0.6", 28 | "apollo-server-core": "^3.5.0", 29 | "apollo-server-express": "^3.5.0", 30 | "ava": "^3.15.0", 31 | "coveralls": "^3.1.1", 32 | "dataloader": "^2.0.0", 33 | "del-cli": "^4.0.1", 34 | "eslint": "^8.2.0", 35 | "eslint-config-canonical": "^32.36.0", 36 | "express": "^4.17.1", 37 | "graphql": "^15.7.2", 38 | "graphql-middleware": "^6.1.12", 39 | "graphql-request": "^3.6.1", 40 | "graphql-tag": "^2.12.6", 41 | "husky": "^7.0.4", 42 | "lint-staged": "^12.0.2", 43 | "nyc": "^15.1.0", 44 | "semantic-release": "^18.0.0", 45 | "sinon": "^12.0.1", 46 | "ts-node": "^10.4.0", 47 | "typescript": "^4.5.2" 48 | }, 49 | "engines": { 50 | "node": ">=10" 51 | }, 52 | "husky": { 53 | "hooks": { 54 | "pre-commit": "lint-staged" 55 | } 56 | }, 57 | "keywords": [ 58 | "graphql", 59 | "directive" 60 | ], 61 | "license": "BSD-3-Clause", 62 | "main": "./dist/src/index.js", 63 | "name": "graphql-lazyloader", 64 | "peerDependencies": { 65 | "@graphql-tools/utils": "^8.5.3", 66 | "graphql": "^15.7.2" 67 | }, 68 | "repository": { 69 | "type": "git", 70 | "url": "https://github.com/gajus/graphql-lazyloader" 71 | }, 72 | "scripts": { 73 | "build": "del-cli ./dist && tsc", 74 | "lint": "eslint ./src ./test && tsc --noEmit", 75 | "test": "ava --verbose --serial" 76 | }, 77 | "typings": "./dist/src/index.d.ts", 78 | "version": "1.0.0" 79 | } 80 | -------------------------------------------------------------------------------- /src/Logger.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Roarr, 3 | } from 'roarr'; 4 | 5 | export default Roarr.child({ 6 | package: 'graphql-lazyloader', 7 | }); 8 | -------------------------------------------------------------------------------- /src/factories/createLazyLoadMiddleware.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | IMiddlewareResolver, 3 | } from 'graphql-middleware/dist/types'; 4 | 5 | type LazyLoadMap = { 6 | [key: string]: (source: any, context: TContext) => any, 7 | }; 8 | 9 | const lazyLoadedSymbol = Symbol('LAZY_LOADED'); 10 | 11 | export default ( 12 | lazyLoadMap: LazyLoadMap, 13 | ): IMiddlewareResolver => { 14 | return async (resolve, node, args, context, info) => { 15 | const lazyLoad = lazyLoadMap[info.parentType.name]; 16 | 17 | if (!lazyLoad) { 18 | return resolve(node, args, context, info); 19 | } 20 | 21 | const currentValue = node[info.fieldName]; 22 | 23 | if (currentValue !== undefined) { 24 | return resolve(node, args, context, info); 25 | } 26 | 27 | if (!node[lazyLoadedSymbol]) { 28 | node[lazyLoadedSymbol] = lazyLoad(node, context); 29 | } 30 | 31 | const newNode = await node[lazyLoadedSymbol]; 32 | 33 | return resolve(newNode, args, context, info); 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/factories/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as createLazyLoadMiddleware, 3 | } from './createLazyLoadMiddleware'; 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createLazyLoadMiddleware, 3 | } from './factories'; 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "canonical/ava" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/graphql-lazyloader/createLazyLoadMiddleware.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import DataLoader from 'dataloader'; 3 | import request from 'graphql-request'; 4 | import { 5 | gql, 6 | } from 'graphql-tag'; 7 | import * as sinon from 'sinon'; 8 | import createGraphqlServer from '../helpers/createGraphqlServer'; 9 | import createSchemaWithMiddleware from '../helpers/createSchemaWithMiddleware'; 10 | 11 | test('uses lazyLoadMap to lazy load the result', async (t) => { 12 | const schema = createSchemaWithMiddleware({ 13 | lazyLoadMap: { 14 | Foo: () => { 15 | return { 16 | id: '1', 17 | name: 'foo', 18 | }; 19 | }, 20 | }, 21 | resolvers: { 22 | Query: { 23 | foo: () => { 24 | return { 25 | id: '1', 26 | }; 27 | }, 28 | }, 29 | }, 30 | typeDefs: gql` 31 | type Foo { 32 | id: ID! 33 | name: String! 34 | } 35 | 36 | type Query { 37 | foo: Foo! 38 | } 39 | `, 40 | }); 41 | 42 | const graphqlServer = await createGraphqlServer({ 43 | schema, 44 | }); 45 | 46 | const response = await request(graphqlServer.url, gql` 47 | { 48 | foo { 49 | id 50 | name 51 | } 52 | } 53 | `); 54 | 55 | t.deepEqual(response, { 56 | foo: { 57 | id: '1', 58 | name: 'foo', 59 | }, 60 | }); 61 | 62 | await graphqlServer.stop(); 63 | }); 64 | 65 | test('caches fetched data', async (t) => { 66 | const lazyLoad = sinon 67 | .stub() 68 | .returns({ 69 | id: '1', 70 | name: 'foo', 71 | }); 72 | 73 | const schema = createSchemaWithMiddleware({ 74 | lazyLoadMap: { 75 | Foo: lazyLoad, 76 | }, 77 | resolvers: { 78 | Query: { 79 | foo: () => { 80 | return { 81 | id: '1', 82 | }; 83 | }, 84 | }, 85 | }, 86 | typeDefs: gql` 87 | type Foo { 88 | id: ID! 89 | name: String! 90 | } 91 | 92 | type Query { 93 | foo: Foo! 94 | } 95 | `, 96 | }); 97 | 98 | const graphqlServer = await createGraphqlServer({ 99 | schema, 100 | }); 101 | 102 | const response = await request(graphqlServer.url, gql` 103 | { 104 | foo { 105 | id 106 | name 107 | } 108 | foo { 109 | id 110 | name 111 | } 112 | } 113 | `); 114 | 115 | t.deepEqual(response, { 116 | foo: { 117 | id: '1', 118 | name: 'foo', 119 | }, 120 | }); 121 | 122 | await graphqlServer.stop(); 123 | 124 | t.is(lazyLoad.callCount, 1); 125 | }); 126 | 127 | test('respects the original resolver', async (t) => { 128 | const lazyLoad = sinon 129 | .stub() 130 | .returns({ 131 | id: '1', 132 | name: 'foo', 133 | }); 134 | 135 | const schema = createSchemaWithMiddleware({ 136 | lazyLoadMap: { 137 | Foo: lazyLoad, 138 | }, 139 | resolvers: { 140 | Foo: { 141 | name: (node: any) => { 142 | return node.name.toUpperCase(); 143 | }, 144 | }, 145 | Query: { 146 | foo: () => { 147 | return { 148 | id: '1', 149 | }; 150 | }, 151 | }, 152 | }, 153 | typeDefs: gql` 154 | type Foo { 155 | id: ID! 156 | name: String! 157 | } 158 | 159 | type Query { 160 | foo: Foo! 161 | } 162 | `, 163 | }); 164 | 165 | const graphqlServer = await createGraphqlServer({ 166 | schema, 167 | }); 168 | 169 | const response = await request(graphqlServer.url, gql` 170 | { 171 | foo { 172 | id 173 | name 174 | } 175 | } 176 | `); 177 | 178 | t.deepEqual(response, { 179 | foo: { 180 | id: '1', 181 | name: 'FOO', 182 | }, 183 | }); 184 | 185 | await graphqlServer.stop(); 186 | }); 187 | 188 | test('does not fetch already available data', async (t) => { 189 | const lazyLoad = sinon 190 | .stub() 191 | .throws(); 192 | 193 | const schema = createSchemaWithMiddleware({ 194 | lazyLoadMap: { 195 | Foo: lazyLoad, 196 | }, 197 | resolvers: { 198 | Query: { 199 | foo: () => { 200 | return { 201 | id: '1', 202 | name: 'foo', 203 | }; 204 | }, 205 | }, 206 | }, 207 | typeDefs: gql` 208 | type Foo { 209 | id: ID! 210 | name: String! 211 | } 212 | 213 | type Query { 214 | foo: Foo! 215 | } 216 | `, 217 | }); 218 | 219 | const graphqlServer = await createGraphqlServer({ 220 | schema, 221 | }); 222 | 223 | const response = await request(graphqlServer.url, gql` 224 | { 225 | foo { 226 | id 227 | name 228 | } 229 | } 230 | `); 231 | 232 | t.deepEqual(response, { 233 | foo: { 234 | id: '1', 235 | name: 'foo', 236 | }, 237 | }); 238 | 239 | await graphqlServer.stop(); 240 | 241 | t.is(lazyLoad.callCount, 0); 242 | }); 243 | 244 | test('batches multiple requests', async (t) => { 245 | const lazyLoad = sinon 246 | .stub() 247 | .resolves([ 248 | { 249 | id: '1', 250 | name: 'foo', 251 | }, 252 | { 253 | id: '2', 254 | name: 'bar', 255 | }, 256 | ]); 257 | 258 | const fooLoader = new DataLoader(lazyLoad); 259 | 260 | const schema = createSchemaWithMiddleware({ 261 | lazyLoadMap: { 262 | Foo: ({id}: any) => { 263 | return fooLoader.load(id); 264 | }, 265 | }, 266 | resolvers: { 267 | Query: { 268 | foos: () => { 269 | return [ 270 | { 271 | id: '1', 272 | }, 273 | { 274 | id: '2', 275 | }, 276 | ]; 277 | }, 278 | }, 279 | }, 280 | typeDefs: gql` 281 | type Foo { 282 | id: ID! 283 | name: String! 284 | } 285 | 286 | type Query { 287 | foos: [Foo!]! 288 | } 289 | `, 290 | }); 291 | 292 | const graphqlServer = await createGraphqlServer({ 293 | schema, 294 | }); 295 | 296 | const response = await request(graphqlServer.url, gql` 297 | { 298 | foos { 299 | id 300 | name 301 | } 302 | } 303 | `); 304 | 305 | t.deepEqual(response, { 306 | foos: [ 307 | { 308 | id: '1', 309 | name: 'foo', 310 | }, 311 | { 312 | id: '2', 313 | name: 'bar', 314 | }, 315 | ], 316 | }); 317 | 318 | await graphqlServer.stop(); 319 | 320 | t.deepEqual(lazyLoad.firstCall.firstArg, [ 321 | '1', 322 | '2', 323 | ]); 324 | 325 | t.is(lazyLoad.callCount, 1); 326 | }); 327 | -------------------------------------------------------------------------------- /test/graphql-lazyloader/graphqlServer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | makeExecutableSchema, 3 | } from '@graphql-tools/schema'; 4 | import test from 'ava'; 5 | import request from 'graphql-request'; 6 | import { 7 | gql, 8 | } from 'graphql-tag'; 9 | import createGraphqlServer from '../helpers/createGraphqlServer'; 10 | 11 | test('test GraphQL server', async (t) => { 12 | const schema = makeExecutableSchema({ 13 | resolvers: { 14 | Query: { 15 | foo: () => { 16 | return 'bar'; 17 | }, 18 | }, 19 | }, 20 | typeDefs: gql` 21 | type Query { 22 | foo: String 23 | } 24 | `, 25 | }); 26 | 27 | const graphqlServer = await createGraphqlServer({ 28 | schema, 29 | }); 30 | 31 | const response = await request(graphqlServer.url, gql` 32 | { 33 | foo 34 | } 35 | `); 36 | 37 | t.deepEqual(response, { 38 | foo: 'bar', 39 | }); 40 | 41 | await graphqlServer.stop(); 42 | }); 43 | -------------------------------------------------------------------------------- /test/helpers/createGraphqlServer.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import { 3 | ApolloServerPluginDrainHttpServer, 4 | } from 'apollo-server-core'; 5 | import { 6 | ApolloServer, 7 | } from 'apollo-server-express'; 8 | import express from 'express'; 9 | 10 | export default async ({schema}: any) => { 11 | const app = express(); 12 | const httpServer = http.createServer(app); 13 | const server = new ApolloServer({ 14 | plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], 15 | schema, 16 | }); 17 | 18 | await server.start(); 19 | 20 | server.applyMiddleware({ 21 | app, 22 | }); 23 | 24 | await new Promise((resolve) => { 25 | httpServer.listen(resolve); 26 | }); 27 | 28 | // @ts-expect-error lazy 29 | const {port} = httpServer.address(); 30 | 31 | return { 32 | stop: () => { 33 | return server.stop(); 34 | }, 35 | url: 'http://127.0.0.1:' + port + server.graphqlPath, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /test/helpers/createSchemaWithMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { 2 | makeExecutableSchema, 3 | } from '@graphql-tools/schema'; 4 | import { 5 | applyMiddleware, 6 | } from 'graphql-middleware'; 7 | import { 8 | createLazyLoadMiddleware, 9 | } from '../../src'; 10 | 11 | export default ({lazyLoadMap, typeDefs, resolvers}: any): any => { 12 | const originalSchema = makeExecutableSchema({ 13 | resolvers, 14 | typeDefs, 15 | }); 16 | 17 | const lazyLoadMiddleware = createLazyLoadMiddleware(lazyLoadMap); 18 | 19 | return applyMiddleware( 20 | originalSchema, 21 | lazyLoadMiddleware, 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noImplicitReturns": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "outDir": "dist", 12 | "skipLibCheck": true, 13 | "strict": true, 14 | "target": "es2018" 15 | }, 16 | "exclude": [ 17 | "dist", 18 | "node_modules" 19 | ], 20 | "include": [ 21 | "src", 22 | "test" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------