├── .editorconfig ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yaml ├── .gitignore ├── .prettierignore ├── .vscode ├── debug-ts.js ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── README.md ├── index.js ├── package.json └── pnpm-lock.yaml ├── package.json ├── pnpm-lock.yaml ├── src ├── index.ts └── lib │ ├── batch-request-cache.ts │ ├── field-directive.spec.ts │ ├── field-directive.ts │ ├── get-graphql-rate-limiter.spec.ts │ ├── get-graphql-rate-limiter.ts │ ├── in-memory-store.spec.ts │ ├── in-memory-store.ts │ ├── integration.spec.ts │ ├── rate-limit-error.spec.ts │ ├── rate-limit-error.ts │ ├── rate-limit-shield-rule.spec.ts │ ├── rate-limit-shield-rule.ts │ ├── redis-store.spec.ts │ ├── redis-store.ts │ ├── store.ts │ └── types.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | node: true, 5 | }, 6 | extends: [ 7 | 'airbnb-base', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:prettier/recommended', 10 | 'prettier', 11 | 'plugin:ava/recommended', 12 | ], 13 | parser: '@typescript-eslint/parser', 14 | plugins: ['@typescript-eslint/eslint-plugin', 'prettier'], 15 | globals: { 16 | Atomics: 'readonly', 17 | SharedArrayBuffer: 'readonly', 18 | }, 19 | parserOptions: { 20 | ecmaVersion: 2018, 21 | sourceType: 'module', 22 | }, 23 | rules: { 24 | 'import/extensions': 'off', 25 | 'import/prefer-default-export': 'off', 26 | '@typescript-eslint/no-explicit-any': 'off', 27 | '@typescript-eslint/explicit-function-return-type': 'off', 28 | }, 29 | settings: { 30 | 'import/resolver': { 31 | node: { 32 | paths: ['src'], 33 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 34 | }, 35 | }, 36 | }, 37 | overrides: [ 38 | { 39 | files: ['src/**/*.{test,spec}.{js,jsx,ts,tsx}'], 40 | rules: { 41 | 'import/no-extraneous-dependencies': 'off', 42 | }, 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | * **Summary** 8 | 9 | 10 | 11 | * **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | 4 | 5 | * **What is the current behavior?** (You can also link to an open issue here) 6 | 7 | 8 | 9 | * **What is the new behavior (if this is a feature change)?** 10 | 11 | 12 | 13 | * **Other information**: 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [12.x, 14.x, 16.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Install node v${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | 23 | - name: Restore pnpm's global store 24 | uses: actions/cache@v2 25 | with: 26 | path: ~/runner/.pnpm-store/v3 27 | key: ${{ runner.os }}-node-${{ hashFiles('**/pnpm-lock.yaml') }} 28 | restore-keys: | 29 | ${{ runner.os }}-node- 30 | 31 | - uses: pnpm/action-setup@v2.0.0 32 | with: 33 | version: 6.0.2 34 | run_install: | 35 | - args: [--frozen-lockfile, --reporter=silent] 36 | 37 | - name: Lint 38 | run: pnpm run lint 39 | 40 | - name: Build dist 41 | run: pnpm run build 42 | 43 | - name: Run tests 44 | run: pnpm test 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | test 5 | src/**.js 6 | .idea/* 7 | *.tgz 8 | 9 | coverage 10 | .nyc_output 11 | *.log 12 | 13 | package-lock.json 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json -------------------------------------------------------------------------------- /.vscode/debug-ts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const meow = require('meow'); 3 | const path = require('path'); 4 | 5 | const tsFile = getTSFile(); 6 | const jsFile = TS2JS(tsFile); 7 | 8 | replaceCLIArg(tsFile, jsFile); 9 | 10 | // Ava debugger 11 | require('ava/profile'); 12 | 13 | /** 14 | * get ts file path from CLI args 15 | * 16 | * @return string path 17 | */ 18 | function getTSFile() { 19 | const cli = meow(); 20 | return cli.input[0]; 21 | } 22 | 23 | /** 24 | * get associated compiled js file path 25 | * 26 | * @param tsFile path 27 | * @return string path 28 | */ 29 | function TS2JS(tsFile) { 30 | const srcFolder = path.join(__dirname, '..', 'src'); 31 | const distFolder = path.join(__dirname, '..', 'build', 'main'); 32 | 33 | const tsPathObj = path.parse(tsFile); 34 | 35 | return path.format({ 36 | dir: tsPathObj.dir.replace(srcFolder, distFolder), 37 | ext: '.js', 38 | name: tsPathObj.name, 39 | root: tsPathObj.root 40 | }); 41 | } 42 | 43 | /** 44 | * replace a value in CLI args 45 | * 46 | * @param search value to search 47 | * @param replace value to replace 48 | * @return void 49 | */ 50 | function replaceCLIArg(search, replace) { 51 | process.argv[process.argv.indexOf(search)] = replace; 52 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [{ 4 | "type": "node", 5 | "request": "launch", 6 | "name": "Debug Project", 7 | // we test in `build` to make cleanup fast and easy 8 | "cwd": "${workspaceFolder}/build", 9 | // Replace this with your project root. If there are multiple, you can 10 | // automatically run the currently visible file with: "program": ${file}" 11 | "program": "${workspaceFolder}/src/cli/cli.ts", 12 | // "args": ["--no-install"], 13 | "outFiles": ["${workspaceFolder}/build/main/**/*.js"], 14 | "skipFiles": [ 15 | "/**/*.js", 16 | "${workspaceFolder}/node_modules/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: build", 19 | "stopOnEntry": true, 20 | "smartStep": true, 21 | "runtimeArgs": ["--nolazy"], 22 | "env": { 23 | "TYPESCRIPT_STARTER_REPO_URL": "${workspaceFolder}" 24 | }, 25 | "console": "externalTerminal" 26 | }, 27 | { 28 | "type": "node", 29 | "request": "launch", 30 | "name": "Debug Spec", 31 | "program": "${workspaceRoot}/.vscode/debug-ts.js", 32 | "args": ["${file}"], 33 | "skipFiles": ["/**/*.js"], 34 | // Consider using `npm run watch` or `yarn watch` for faster debugging 35 | // "preLaunchTask": "npm: build", 36 | // "smartStep": true, 37 | "runtimeArgs": ["--nolazy"] 38 | }] 39 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "tslint.enable": true, 4 | "tslint.autoFixOnSave": true 5 | // "typescript.implementationsCodeLens.enabled": true 6 | // "typescript.referencesCodeLens.enabled": true 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### [3.1.0](https://github.com/teamplanes/graphql-rate-limit/pull/183) (2021-05-28) 4 | 5 | Adds the create error config function. 6 | 7 | ### [3.0.0](https://github.com/teamplanes/graphql-rate-limit/commit/df3837f4002d0f15c30f6ef4ef4e4a23e18d271b) (2021-04-29) 8 | 9 | Removes `graphql-middleware` as a dependency, and adds it as a peer dependency. 10 | 11 | 12 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 13 | 14 | ### [2.0.1](https://github.com/teamplanes/graphql-rate-limit/compare/v2.0.0...v2.0.1) (2019-07-03) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Henry 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 |

💂‍♀️ GraphQL Rate Limit 💂‍♂️

3 | 4 |

5 | A GraphQL Rate Limiter to add basic but granular rate limiting to your Queries or Mutations. 6 |

7 | 8 | --- 9 | 10 | ## Features 11 | 12 | - 💂‍♀️ Add rate limits to queries or mutations 13 | - 🤝 Works with any Node.js GraphQL setup (@directive, graphql-shield rule and a base rate limiter function for every other use case) 14 | - 🔑 Add filters to rate limits based on the query or mutation args 15 | - ❌ Custom error messaging 16 | - ⏰ Configure using a simple `max` per `window` arguments 17 | - 💼 Custom stores, use Redis, Postgres, Mongo... it defaults to in-memory 18 | - 💪 Written in TypeScript 19 | 20 | 21 | ## Install 22 | 23 | ```sh 24 | yarn add graphql-rate-limit 25 | ``` 26 | 27 | ## Examples 28 | 29 | #### Option 1: Using the @directive 30 | 31 | ```ts 32 | import { createRateLimitDirective } from 'graphql-rate-limit'; 33 | 34 | // Step 1: get rate limit directive instance 35 | const rateLimitDirective = createRateLimitDirective({ identifyContext: (ctx) => ctx.id }); 36 | 37 | const schema = makeExecutableSchema({ 38 | schemaDirectives: { 39 | rateLimit: rateLimitDirective 40 | }, 41 | resolvers: { 42 | Query: { 43 | getItems: () => [{ id: '1' }] 44 | } 45 | }, 46 | typeDefs: gql` 47 | directive @rateLimit( 48 | max: Int, 49 | window: String, 50 | message: String, 51 | identityArgs: [String], 52 | arrayLengthField: String 53 | ) on FIELD_DEFINITION 54 | 55 | type Query { 56 | # Step 2: Apply the rate limit instance to the field with config 57 | getItems: [Item] @rateLimit(window: "1s", max: 5, message: "You are doing that too often.") 58 | } 59 | ` 60 | }); 61 | ``` 62 | 63 | #### Option 2: Using the graphql-shield 64 | 65 | ```ts 66 | import { createRateLimitRule } from 'graphql-rate-limit'; 67 | 68 | // Step 1: get rate limit shield instance rule 69 | const rateLimitRule = createRateLimitRule({ identifyContext: (ctx) => ctx.id }); 70 | 71 | const permissions = shield({ 72 | Query: { 73 | // Step 2: Apply the rate limit rule instance to the field with config 74 | getItems: rateLimitRule({ window: "1s", max: 5 }) 75 | } 76 | }); 77 | 78 | const schema = applyMiddleware( 79 | makeExecutableSchema({ 80 | typeDefs: gql` 81 | type Query { 82 | getItems: [Item] 83 | } 84 | `, 85 | resolvers: { 86 | Query: { 87 | getItems: () => [{ id: '1' }] 88 | } 89 | } 90 | }), 91 | permissions 92 | ) 93 | ``` 94 | 95 | #### Option 3: Using the base rate limiter function 96 | 97 | ```ts 98 | import { getGraphQLRateLimiter } from 'graphql-rate-limit'; 99 | 100 | // Step 1: get rate limit directive instance 101 | const rateLimiter = getGraphQLRateLimiter({ identifyContext: (ctx) => ctx.id }); 102 | 103 | const schema = makeExecutableSchema({ 104 | typeDefs: ` 105 | type Query { 106 | getItems: [Item] 107 | } 108 | `, 109 | resolvers: { 110 | Query: { 111 | getItems: async (parent, args, context, info) => { 112 | // Step 2: Apply the rate limit logic instance to the field with config 113 | const errorMessage = await rateLimiter( 114 | { parent, args, context, info }, 115 | { max: 5, window: '10s' } 116 | ); 117 | if (errorMessage) throw new Error(errorMessage); 118 | return [{ id: '1' }] 119 | } 120 | } 121 | } 122 | }) 123 | ``` 124 | 125 | ## Configuration 126 | 127 | You'll notice that each usage example has two steps, step 1 we get an instace of a rate limiter and step 2 we apply the rate limit to one or more fields. When creating the initial instance we pass 'Instance Config' (e.g. `identifyContext` or a `store` instance), this instance will likely be the only instance you'd create for your entire GraphQL backend and can be applied to multiple fields. 128 | 129 | Once you have your rate limiting instance you'll apply it to all the fields that require rate limiting, at this point you'll pass field level rate limiting config (e.g. `window` and `max`). 130 | 131 | And so... we have the same 'Instance Config' and 'Field Config' options which ever way you use this library. 132 | 133 | ### Instance Config 134 | 135 | #### `identifyContext` 136 | 137 | A required key and used to identify the user/client. The most likely cases are either using the context's request.ip, or the user ID on the context. A function that accepts the context and returns a string that is used to identify the user. 138 | 139 | ```js 140 | identifyContext: (ctx) => ctx.user.id 141 | ``` 142 | 143 | #### `store` 144 | 145 | An optional key as it defaults to an InMemoryStore. See the implementation of InMemoryStore if you'd like to implement your own with your own database. 146 | 147 | 148 | ```js 149 | store: new MyCustomStore() 150 | ``` 151 | 152 | #### `formatError` 153 | 154 | Generate a custom error message. Note that the `message` passed in to the field config will be used if its set. 155 | 156 | ```js 157 | formatError: ({ fieldName }) => `Woah there, you are doing way too much ${fieldName}` 158 | ``` 159 | 160 | 161 | #### `createError` 162 | 163 | Generate a custom error. By default, a [`RateLimitError`](https://github.com/teamplanes/graphql-rate-limit/blob/master/src/lib/rate-limit-error.ts) instance is created when a request is blocked. To return an instance of a different error class, you can return your own error using this field. 164 | 165 | ```js 166 | createError: (message: string) => new ApolloError(message, '429'); 167 | ``` 168 | 169 | #### `enableBatchRequestCache` 170 | 171 | This enables a per-request synchronous cache to properly rate limit batch queries. Defaults to `false` to preserve backwards compatibility. 172 | 173 | ```js 174 | enableBatchRequestCache: false | true 175 | ``` 176 | 177 | ### Field Config 178 | 179 | #### `window` 180 | 181 | Specify a time interval window that the `max` number of requests can access the field. We use Zeit's `ms` to parse the `window` arg, [docs here](https://github.com/zeit/ms). 182 | 183 | #### `max` 184 | 185 | Define the max number of calls to the given field per `window`. 186 | 187 | #### `identityArgs` 188 | 189 | If you wanted to limit the requests to a field per id, per user, use `identityArgs` to define how the request should be identified. For example you'd provide just `["id"]` if you wanted to rate limit the access to a field by `id`. We use Lodash's `get` to access nested identity args, [docs here](https://lodash.com/docs/4.17.11#get). 190 | 191 | #### `message` 192 | 193 | A custom message per field. Note you can also use `formatError` to customise the default error message if you don't want to define a single message per rate limited field. 194 | 195 | #### `arrayLengthField` 196 | 197 | Limit calls to the field, using the length of the array as the number of calls to the field. 198 | 199 | 200 | ## Redis Store Usage 201 | 202 | It is recommended to use a persistent store rather than the default InMemoryStore. GraphQLRateLimit currently supports Redis as an alternative. You'll need to install Redis in your project first. 203 | 204 | ```js 205 | import { createRateLimitDirective, RedisStore } from 'graphql-rate-limit'; 206 | 207 | const GraphQLRateLimit = createRateLimitDirective({ 208 | identifyContext: ctx => ctx.user.id, 209 | /** 210 | * Import the class from graphql-rate-limit and pass in an instance of redis client to the constructor 211 | */ 212 | store: new RedisStore(redis.createClient()) 213 | }); 214 | ``` 215 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | src/**.js 4 | .idea/* 5 | *.log 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ### Install 2 | ```sh 3 | pnpm i 4 | ``` 5 | 6 | ### Run 7 | 8 | ```sh 9 | pnpm start 10 | ``` 11 | 12 | ### Develop 13 | 14 | ```sh 15 | pnpm dev 16 | ``` 17 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, makeExecutableSchema } from 'apollo-server'; 2 | import { shield } from 'graphql-shield'; 3 | import { applyMiddleware } from 'graphql-middleware'; 4 | import { 5 | createRateLimitDirective, 6 | RedisStore, 7 | getGraphQLRateLimiter, 8 | createRateLimitRule, 9 | } from 'graphql-rate-limit'; 10 | import redis from 'redis'; 11 | 12 | // Option 1: Use a directive (applied in the schema below) 13 | const rateLimitDirective = createRateLimitDirective({ 14 | identifyContext: (context) => { 15 | return context.req.ip; 16 | }, 17 | store: new RedisStore(redis.createClient()), 18 | }); 19 | 20 | // Option 2: User graphql-shield (applied in the `shield` below) 21 | const rateLimit = createRateLimitRule({ 22 | formatError: () => { 23 | return 'Stop doing that so often.'; 24 | }, 25 | identifyContext: (context) => { 26 | return context.req.ip; 27 | }, 28 | }); 29 | 30 | const permissions = shield({ 31 | Query: { 32 | myId: rateLimit({ 33 | max: 2, 34 | window: '10s', 35 | }), 36 | }, 37 | }); 38 | 39 | // Option 3: Manually use the rate limiter in resolvers 40 | const rateLimiter = getGraphQLRateLimiter({ 41 | formatError: () => { 42 | return 'Stop doing that.'; 43 | }, 44 | identifyContext: (context) => { 45 | return context.req.ip; 46 | }, 47 | }); 48 | 49 | const books = [ 50 | { 51 | title: 'Harry Potter and the Chamber of Secrets', 52 | author: 'J.K. Rowling', 53 | }, 54 | { 55 | title: 'Jurassic Park', 56 | author: 'Michael Crichton', 57 | }, 58 | ]; 59 | 60 | const typeDefs = gql` 61 | directive @rateLimit( 62 | message: String 63 | identityArgs: [String] 64 | arrayLengthField: String 65 | max: Int 66 | window: String 67 | ) on FIELD_DEFINITION 68 | 69 | type Book { 70 | title: String 71 | author: String 72 | } 73 | 74 | type Query { 75 | myId: ID! 76 | books: [Book] 77 | @rateLimit( 78 | message: "You are requesting books too often" 79 | max: 2 80 | window: "5s" 81 | ) 82 | } 83 | 84 | type Mutation { 85 | updateBook(id: Int!, title: String, author: String): Book # NOTE: This is rate limited using option 2 86 | createBook(title: String!, author: String!): Book 87 | @rateLimit(identityArgs: ["title"], max: 2, window: "10s") 88 | deleteBooks(titles: [String]!): Book 89 | @rateLimit( 90 | identityArgs: ["title"] 91 | arrayLengthField: "titles" 92 | max: 4 93 | window: "10s" 94 | ) 95 | } 96 | `; 97 | 98 | const resolvers = { 99 | Query: { 100 | books: () => books, 101 | myId: () => '1', 102 | }, 103 | Mutation: { 104 | // This uses the manual rate limiter (Option 3.) 105 | updateBook: async (parent, args, context, info) => { 106 | const errorMessage = await rateLimiter( 107 | { 108 | parent, 109 | args, 110 | context, 111 | info, 112 | }, 113 | { 114 | max: 2, 115 | window: '10s', 116 | } 117 | ); 118 | if (errorMessage) throw new Error(errorMessage); 119 | books[args.id] = { 120 | ...books[args.id], 121 | title: args.title || books[args.id].title, 122 | author: args.title || books[args.id].author, 123 | }; 124 | return books[args.id]; 125 | }, 126 | createBook: (_, args) => { 127 | books.push(args); 128 | return args; 129 | }, 130 | deleteBooks: () => { 131 | return books[0]; 132 | }, 133 | }, 134 | }; 135 | 136 | const server = new ApolloServer({ 137 | context: (ctx) => ctx, 138 | 139 | schemaDirectives: { 140 | rateLimit: rateLimitDirective, 141 | }, 142 | schema: applyMiddleware( 143 | makeExecutableSchema({ 144 | typeDefs, 145 | resolvers, 146 | }), 147 | permissions 148 | ), 149 | }); 150 | 151 | server.listen().then(({ url }) => { 152 | // eslint-disable-next-line no-console 153 | console.log(`🚀 Server ready at ${url}`); 154 | }); 155 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "type": "module", 7 | "dependencies": { 8 | "apollo-server": "^2.21.2", 9 | "graphql-middleware": "^6.0.4", 10 | "graphql-rate-limit": "../", 11 | "graphql-shield": "^7.5.0", 12 | "redis": "^3.0.2" 13 | }, 14 | "devDependencies": { 15 | "nodemon": "^2.0.7" 16 | }, 17 | "scripts": { 18 | "start": "node index.js", 19 | "dev": "nodemon index.js --watch index.js --watch ../dist" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | apollo-server: ^2.21.2 5 | graphql-middleware: ^6.0.4 6 | graphql-rate-limit: ../ 7 | graphql-shield: ^7.5.0 8 | nodemon: ^2.0.7 9 | redis: ^3.0.2 10 | 11 | dependencies: 12 | apollo-server: 2.21.2 13 | graphql-middleware: 6.0.4 14 | graphql-rate-limit: link:.. 15 | graphql-shield: 7.5.0_graphql-middleware@6.0.4 16 | redis: 3.0.2 17 | 18 | devDependencies: 19 | nodemon: 2.0.7 20 | 21 | packages: 22 | 23 | /@apollo/protobufjs/1.0.5: 24 | resolution: {integrity: sha512-ZtyaBH1icCgqwIGb3zrtopV2D5Q8yxibkJzlaViM08eOhTQc7rACdYu0pfORFfhllvdMZ3aq69vifYHszY4gNA==} 25 | hasBin: true 26 | requiresBuild: true 27 | dependencies: 28 | '@protobufjs/aspromise': 1.1.2 29 | '@protobufjs/base64': 1.1.2 30 | '@protobufjs/codegen': 2.0.4 31 | '@protobufjs/eventemitter': 1.1.0 32 | '@protobufjs/fetch': 1.1.0 33 | '@protobufjs/float': 1.0.2 34 | '@protobufjs/inquire': 1.1.0 35 | '@protobufjs/path': 1.1.2 36 | '@protobufjs/pool': 1.1.0 37 | '@protobufjs/utf8': 1.1.0 38 | '@types/long': 4.0.1 39 | '@types/node': 10.17.55 40 | long: 4.0.0 41 | dev: false 42 | 43 | /@apollographql/apollo-tools/0.4.9: 44 | resolution: {integrity: sha512-M50pk8oo3CGTu4waGOklIX3YtTZoPfWG9K/G9WB8NpyQGA1OwYTiBFv94XqUtKElTDoFwoMXpMQd3Wy5dINvxA==} 45 | engines: {node: '>=8', npm: '>=6'} 46 | dependencies: 47 | apollo-env: 0.6.6 48 | dev: false 49 | 50 | /@apollographql/graphql-playground-html/1.6.27: 51 | resolution: {integrity: sha512-tea2LweZvn6y6xFV11K0KC8ETjmm52mQrW+ezgB2O/aTQf8JGyFmMcRPFgUaQZeHbWdm8iisDC6EjOKsXu0nfw==} 52 | dependencies: 53 | xss: 1.0.8 54 | dev: false 55 | 56 | /@apollographql/graphql-upload-8-fork/8.1.3: 57 | resolution: {integrity: sha512-ssOPUT7euLqDXcdVv3Qs4LoL4BPtfermW1IOouaqEmj36TpHYDmYDIbKoSQxikd9vtMumFnP87OybH7sC9fJ6g==} 58 | engines: {node: '>=8.5'} 59 | peerDependencies: 60 | graphql: 0.13.1 - 15 61 | dependencies: 62 | '@types/express': 4.17.11 63 | '@types/fs-capacitor': 2.0.0 64 | '@types/koa': 2.13.1 65 | busboy: 0.3.1 66 | fs-capacitor: 2.0.4 67 | http-errors: 1.8.0 68 | object-path: 0.11.5 69 | dev: false 70 | 71 | /@ardatan/aggregate-error/0.0.6: 72 | resolution: {integrity: sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==} 73 | engines: {node: '>=8'} 74 | dependencies: 75 | tslib: 2.0.3 76 | dev: false 77 | 78 | /@babel/runtime/7.13.10: 79 | resolution: {integrity: sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==} 80 | dependencies: 81 | regenerator-runtime: 0.13.7 82 | dev: false 83 | 84 | /@graphql-tools/batch-execute/7.0.0: 85 | resolution: {integrity: sha512-+ywPfK6N2Ddna6oOa5Qb1Mv7EA8LOwRNOAPP9dL37FEhksJM9pYqPSceUcqMqg7S9b0+Cgr78s408rgvurV3/Q==} 86 | peerDependencies: 87 | graphql: ^14.0.0 || ^15.0.0 88 | dependencies: 89 | '@graphql-tools/utils': 7.6.0 90 | dataloader: 2.0.0 91 | is-promise: 4.0.0 92 | tslib: 2.0.3 93 | dev: false 94 | 95 | /@graphql-tools/delegate/7.0.10: 96 | resolution: {integrity: sha512-6Di9ia5ohoDvrHuhj2cak1nJGhIefJmUsd3WKZcJ2nu2yZAFawWMxGvQImqv3N7iyaWKiVhrrK8Roi/JrYhdKg==} 97 | peerDependencies: 98 | graphql: ^14.0.0 || ^15.0.0 99 | dependencies: 100 | '@ardatan/aggregate-error': 0.0.6 101 | '@graphql-tools/batch-execute': 7.0.0 102 | '@graphql-tools/schema': 7.1.3 103 | '@graphql-tools/utils': 7.6.0 104 | dataloader: 2.0.0 105 | is-promise: 4.0.0 106 | tslib: 2.1.0 107 | dev: false 108 | 109 | /@graphql-tools/schema/7.1.3: 110 | resolution: {integrity: sha512-ZY76hmcJlF1iyg3Im0sQ3ASRkiShjgv102vLTVcH22lEGJeCaCyyS/GF1eUHom418S60bS8Th6+autRUxfBiBg==} 111 | peerDependencies: 112 | graphql: ^14.0.0 || ^15.0.0 113 | dependencies: 114 | '@graphql-tools/utils': 7.6.0 115 | tslib: 2.1.0 116 | dev: false 117 | 118 | /@graphql-tools/utils/7.6.0: 119 | resolution: {integrity: sha512-YCZDDdhfb4Yhie0IH031eGdvQG8C73apDuNg6lqBNbauNw45OG/b8wi3+vuMiDnJTJN32GQUb1Gt9gxDKoRDKw==} 120 | peerDependencies: 121 | graphql: ^14.0.0 || ^15.0.0 122 | dependencies: 123 | '@ardatan/aggregate-error': 0.0.6 124 | camel-case: 4.1.2 125 | tslib: 2.1.0 126 | dev: false 127 | 128 | /@protobufjs/aspromise/1.1.2: 129 | resolution: {integrity: sha1-m4sMxmPWaafY9vXQiToU00jzD78=} 130 | dev: false 131 | 132 | /@protobufjs/base64/1.1.2: 133 | resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} 134 | dev: false 135 | 136 | /@protobufjs/codegen/2.0.4: 137 | resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} 138 | dev: false 139 | 140 | /@protobufjs/eventemitter/1.1.0: 141 | resolution: {integrity: sha1-NVy8mLr61ZePntCV85diHx0Ga3A=} 142 | dev: false 143 | 144 | /@protobufjs/fetch/1.1.0: 145 | resolution: {integrity: sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=} 146 | dependencies: 147 | '@protobufjs/aspromise': 1.1.2 148 | '@protobufjs/inquire': 1.1.0 149 | dev: false 150 | 151 | /@protobufjs/float/1.0.2: 152 | resolution: {integrity: sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=} 153 | dev: false 154 | 155 | /@protobufjs/inquire/1.1.0: 156 | resolution: {integrity: sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=} 157 | dev: false 158 | 159 | /@protobufjs/path/1.1.2: 160 | resolution: {integrity: sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=} 161 | dev: false 162 | 163 | /@protobufjs/pool/1.1.0: 164 | resolution: {integrity: sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=} 165 | dev: false 166 | 167 | /@protobufjs/utf8/1.1.0: 168 | resolution: {integrity: sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=} 169 | dev: false 170 | 171 | /@sindresorhus/is/0.14.0: 172 | resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} 173 | engines: {node: '>=6'} 174 | dev: true 175 | 176 | /@szmarczak/http-timer/1.1.2: 177 | resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} 178 | engines: {node: '>=6'} 179 | dependencies: 180 | defer-to-connect: 1.1.3 181 | dev: true 182 | 183 | /@types/accepts/1.3.5: 184 | resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} 185 | dependencies: 186 | '@types/node': 14.14.35 187 | dev: false 188 | 189 | /@types/body-parser/1.19.0: 190 | resolution: {integrity: sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==} 191 | dependencies: 192 | '@types/connect': 3.4.34 193 | '@types/node': 14.14.35 194 | dev: false 195 | 196 | /@types/connect/3.4.34: 197 | resolution: {integrity: sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==} 198 | dependencies: 199 | '@types/node': 14.14.35 200 | dev: false 201 | 202 | /@types/content-disposition/0.5.3: 203 | resolution: {integrity: sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg==} 204 | dev: false 205 | 206 | /@types/cookies/0.7.6: 207 | resolution: {integrity: sha512-FK4U5Qyn7/Sc5ih233OuHO0qAkOpEcD/eG6584yEiLKizTFRny86qHLe/rej3HFQrkBuUjF4whFliAdODbVN/w==} 208 | dependencies: 209 | '@types/connect': 3.4.34 210 | '@types/express': 4.17.11 211 | '@types/keygrip': 1.0.2 212 | '@types/node': 14.14.35 213 | dev: false 214 | 215 | /@types/cors/2.8.8: 216 | resolution: {integrity: sha512-fO3gf3DxU2Trcbr75O7obVndW/X5k8rJNZkLXlQWStTHhP71PkRqjwPIEI0yMnJdg9R9OasjU+Bsr+Hr1xy/0w==} 217 | dependencies: 218 | '@types/express': 4.17.11 219 | dev: false 220 | 221 | /@types/express-serve-static-core/4.17.18: 222 | resolution: {integrity: sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA==} 223 | dependencies: 224 | '@types/node': 14.14.35 225 | '@types/qs': 6.9.6 226 | '@types/range-parser': 1.2.3 227 | dev: false 228 | 229 | /@types/express/4.17.11: 230 | resolution: {integrity: sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==} 231 | dependencies: 232 | '@types/body-parser': 1.19.0 233 | '@types/express-serve-static-core': 4.17.18 234 | '@types/qs': 6.9.6 235 | '@types/serve-static': 1.13.9 236 | dev: false 237 | 238 | /@types/fs-capacitor/2.0.0: 239 | resolution: {integrity: sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ==} 240 | dependencies: 241 | '@types/node': 14.14.35 242 | dev: false 243 | 244 | /@types/http-assert/1.5.1: 245 | resolution: {integrity: sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==} 246 | dev: false 247 | 248 | /@types/http-errors/1.8.0: 249 | resolution: {integrity: sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==} 250 | dev: false 251 | 252 | /@types/keygrip/1.0.2: 253 | resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} 254 | dev: false 255 | 256 | /@types/koa-compose/3.2.5: 257 | resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} 258 | dependencies: 259 | '@types/koa': 2.13.1 260 | dev: false 261 | 262 | /@types/koa/2.13.1: 263 | resolution: {integrity: sha512-Qbno7FWom9nNqu0yHZ6A0+RWt4mrYBhw3wpBAQ3+IuzGcLlfeYkzZrnMq5wsxulN2np8M4KKeUpTodsOsSad5Q==} 264 | dependencies: 265 | '@types/accepts': 1.3.5 266 | '@types/content-disposition': 0.5.3 267 | '@types/cookies': 0.7.6 268 | '@types/http-assert': 1.5.1 269 | '@types/http-errors': 1.8.0 270 | '@types/keygrip': 1.0.2 271 | '@types/koa-compose': 3.2.5 272 | '@types/node': 14.14.35 273 | dev: false 274 | 275 | /@types/long/4.0.1: 276 | resolution: {integrity: sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==} 277 | dev: false 278 | 279 | /@types/mime/1.3.2: 280 | resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} 281 | dev: false 282 | 283 | /@types/node-fetch/2.5.7: 284 | resolution: {integrity: sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==} 285 | dependencies: 286 | '@types/node': 14.14.35 287 | form-data: 3.0.1 288 | dev: false 289 | 290 | /@types/node/10.17.55: 291 | resolution: {integrity: sha512-koZJ89uLZufDvToeWO5BrC4CR4OUfHnUz2qoPs/daQH6qq3IN62QFxCTZ+bKaCE0xaoCAJYE4AXre8AbghCrhg==} 292 | dev: false 293 | 294 | /@types/node/14.14.35: 295 | resolution: {integrity: sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==} 296 | dev: false 297 | 298 | /@types/qs/6.9.6: 299 | resolution: {integrity: sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==} 300 | dev: false 301 | 302 | /@types/range-parser/1.2.3: 303 | resolution: {integrity: sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==} 304 | dev: false 305 | 306 | /@types/serve-static/1.13.9: 307 | resolution: {integrity: sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==} 308 | dependencies: 309 | '@types/mime': 1.3.2 310 | '@types/node': 14.14.35 311 | dev: false 312 | 313 | /@types/ws/7.4.0: 314 | resolution: {integrity: sha512-Y29uQ3Uy+58bZrFLhX36hcI3Np37nqWE7ky5tjiDoy1GDZnIwVxS0CgF+s+1bXMzjKBFy+fqaRfb708iNzdinw==} 315 | dependencies: 316 | '@types/node': 14.14.35 317 | dev: false 318 | 319 | /@types/yup/0.29.11: 320 | resolution: {integrity: sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g==} 321 | dev: false 322 | 323 | /@wry/equality/0.1.11: 324 | resolution: {integrity: sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==} 325 | dependencies: 326 | tslib: 1.14.1 327 | dev: false 328 | 329 | /abbrev/1.1.1: 330 | resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} 331 | dev: true 332 | 333 | /accepts/1.3.7: 334 | resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==} 335 | engines: {node: '>= 0.6'} 336 | dependencies: 337 | mime-types: 2.1.29 338 | negotiator: 0.6.2 339 | dev: false 340 | 341 | /ansi-align/3.0.0: 342 | resolution: {integrity: sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==} 343 | dependencies: 344 | string-width: 3.1.0 345 | dev: true 346 | 347 | /ansi-regex/4.1.0: 348 | resolution: {integrity: sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==} 349 | engines: {node: '>=6'} 350 | dev: true 351 | 352 | /ansi-regex/5.0.0: 353 | resolution: {integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==} 354 | engines: {node: '>=8'} 355 | dev: true 356 | 357 | /ansi-styles/4.3.0: 358 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 359 | engines: {node: '>=8'} 360 | dependencies: 361 | color-convert: 2.0.1 362 | dev: true 363 | 364 | /anymatch/3.1.1: 365 | resolution: {integrity: sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==} 366 | engines: {node: '>= 8'} 367 | dependencies: 368 | normalize-path: 3.0.0 369 | picomatch: 2.2.2 370 | dev: true 371 | 372 | /apollo-cache-control/0.11.6: 373 | resolution: {integrity: sha512-YZ+uuIG+fPy+mkpBS2qKF0v1qlzZ3PW6xZVaDukeK3ed3iAs4L/2YnkTqau3OmoF/VPzX2FmSkocX/OVd59YSw==} 374 | engines: {node: '>=6.0'} 375 | peerDependencies: 376 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 377 | dependencies: 378 | apollo-server-env: 3.0.0 379 | apollo-server-plugin-base: 0.10.4 380 | dev: false 381 | 382 | /apollo-datasource/0.7.3: 383 | resolution: {integrity: sha512-PE0ucdZYjHjUyXrFWRwT02yLcx2DACsZ0jm1Mp/0m/I9nZu/fEkvJxfsryXB6JndpmQO77gQHixf/xGCN976kA==} 384 | engines: {node: '>=6'} 385 | dependencies: 386 | apollo-server-caching: 0.5.3 387 | apollo-server-env: 3.0.0 388 | dev: false 389 | 390 | /apollo-env/0.6.6: 391 | resolution: {integrity: sha512-hXI9PjJtzmD34XviBU+4sPMOxnifYrHVmxpjykqI/dUD2G3yTiuRaiQqwRwB2RCdwC1Ug/jBfoQ/NHDTnnjndQ==} 392 | engines: {node: '>=8'} 393 | dependencies: 394 | '@types/node-fetch': 2.5.7 395 | core-js: 3.9.1 396 | node-fetch: 2.6.1 397 | sha.js: 2.4.11 398 | dev: false 399 | 400 | /apollo-graphql/0.6.1: 401 | resolution: {integrity: sha512-ZRXAV+k+hboCVS+FW86FW/QgnDR7gm/xMUwJPGXEbV53OLGuQQdIT0NCYK7AzzVkCfsbb7NJ3mmEclkZY9uuxQ==} 402 | engines: {node: '>=6'} 403 | peerDependencies: 404 | graphql: ^14.2.1 || ^15.0.0 405 | dependencies: 406 | apollo-env: 0.6.6 407 | lodash.sortby: 4.7.0 408 | dev: false 409 | 410 | /apollo-link/1.2.14: 411 | resolution: {integrity: sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==} 412 | peerDependencies: 413 | graphql: ^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0 414 | dependencies: 415 | apollo-utilities: 1.3.4 416 | ts-invariant: 0.4.4 417 | tslib: 1.14.1 418 | zen-observable-ts: 0.8.21 419 | dev: false 420 | 421 | /apollo-reporting-protobuf/0.6.2: 422 | resolution: {integrity: sha512-WJTJxLM+MRHNUxt1RTl4zD0HrLdH44F2mDzMweBj1yHL0kSt8I1WwoiF/wiGVSpnG48LZrBegCaOJeuVbJTbtw==} 423 | dependencies: 424 | '@apollo/protobufjs': 1.0.5 425 | dev: false 426 | 427 | /apollo-server-caching/0.5.3: 428 | resolution: {integrity: sha512-iMi3087iphDAI0U2iSBE9qtx9kQoMMEWr6w+LwXruBD95ek9DWyj7OeC2U/ngLjRsXM43DoBDXlu7R+uMjahrQ==} 429 | engines: {node: '>=6'} 430 | dependencies: 431 | lru-cache: 6.0.0 432 | dev: false 433 | 434 | /apollo-server-core/2.21.2: 435 | resolution: {integrity: sha512-jIXMVQPOUzIOl4El/mzSixxJ5IDrqSk3L9uJ1U+ncwiQj0IjtkkyDSuYngcgyEi+KfO2lAzxeOiAy9fIjjkC2A==} 436 | engines: {node: '>=6'} 437 | peerDependencies: 438 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 439 | dependencies: 440 | '@apollographql/apollo-tools': 0.4.9 441 | '@apollographql/graphql-playground-html': 1.6.27 442 | '@apollographql/graphql-upload-8-fork': 8.1.3 443 | '@types/ws': 7.4.0 444 | apollo-cache-control: 0.11.6 445 | apollo-datasource: 0.7.3 446 | apollo-graphql: 0.6.1 447 | apollo-reporting-protobuf: 0.6.2 448 | apollo-server-caching: 0.5.3 449 | apollo-server-env: 3.0.0 450 | apollo-server-errors: 2.4.2 451 | apollo-server-plugin-base: 0.10.4 452 | apollo-server-types: 0.6.3 453 | apollo-tracing: 0.12.2 454 | async-retry: 1.3.1 455 | fast-json-stable-stringify: 2.1.0 456 | graphql-extensions: 0.12.8 457 | graphql-tag: 2.11.0 458 | graphql-tools: 4.0.8 459 | loglevel: 1.7.1 460 | lru-cache: 6.0.0 461 | sha.js: 2.4.11 462 | subscriptions-transport-ws: 0.9.18 463 | uuid: 8.3.2 464 | ws: 6.2.1 465 | dev: false 466 | 467 | /apollo-server-env/3.0.0: 468 | resolution: {integrity: sha512-tPSN+VttnPsoQAl/SBVUpGbLA97MXG990XIwq6YUnJyAixrrsjW1xYG7RlaOqetxm80y5mBZKLrRDiiSsW/vog==} 469 | engines: {node: '>=6'} 470 | dependencies: 471 | node-fetch: 2.6.1 472 | util.promisify: 1.1.1 473 | dev: false 474 | 475 | /apollo-server-errors/2.4.2: 476 | resolution: {integrity: sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ==} 477 | engines: {node: '>=6'} 478 | peerDependencies: 479 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 480 | dev: false 481 | 482 | /apollo-server-express/2.21.2: 483 | resolution: {integrity: sha512-5aNE1i/My8c9eBJg5uRyZSvE/NThm1X/fewcs4U5WpuEAMdmg/vf0oZnSZ8Nvmj7H8R2E5xiMkTm6Mv+MKOl9A==} 484 | engines: {node: '>=6'} 485 | peerDependencies: 486 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 487 | dependencies: 488 | '@apollographql/graphql-playground-html': 1.6.27 489 | '@types/accepts': 1.3.5 490 | '@types/body-parser': 1.19.0 491 | '@types/cors': 2.8.8 492 | '@types/express': 4.17.11 493 | '@types/express-serve-static-core': 4.17.18 494 | accepts: 1.3.7 495 | apollo-server-core: 2.21.2 496 | apollo-server-types: 0.6.3 497 | body-parser: 1.19.0 498 | cors: 2.8.5 499 | express: 4.17.1 500 | graphql-subscriptions: 1.2.1 501 | graphql-tools: 4.0.8 502 | parseurl: 1.3.3 503 | subscriptions-transport-ws: 0.9.18 504 | type-is: 1.6.18 505 | dev: false 506 | 507 | /apollo-server-plugin-base/0.10.4: 508 | resolution: {integrity: sha512-HRhbyHgHFTLP0ImubQObYhSgpmVH4Rk1BinnceZmwudIVLKrqayIVOELdyext/QnSmmzg5W7vF3NLGBcVGMqDg==} 509 | engines: {node: '>=6'} 510 | peerDependencies: 511 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 512 | dependencies: 513 | apollo-server-types: 0.6.3 514 | dev: false 515 | 516 | /apollo-server-types/0.6.3: 517 | resolution: {integrity: sha512-aVR7SlSGGY41E1f11YYz5bvwA89uGmkVUtzMiklDhZ7IgRJhysT5Dflt5IuwDxp+NdQkIhVCErUXakopocFLAg==} 518 | engines: {node: '>=6'} 519 | peerDependencies: 520 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 521 | dependencies: 522 | apollo-reporting-protobuf: 0.6.2 523 | apollo-server-caching: 0.5.3 524 | apollo-server-env: 3.0.0 525 | dev: false 526 | 527 | /apollo-server/2.21.2: 528 | resolution: {integrity: sha512-HLUeIiNDyz+KyKyqlXvZov4dj1V0/QbySqcfPpVGyCwdpf+ZAU7CVhZASNbfwmA4hh436jLhQnoIIKhfBD1kWw==} 529 | peerDependencies: 530 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 531 | dependencies: 532 | apollo-server-core: 2.21.2 533 | apollo-server-express: 2.21.2 534 | express: 4.17.1 535 | graphql-subscriptions: 1.2.1 536 | graphql-tools: 4.0.8 537 | stoppable: 1.1.0 538 | dev: false 539 | 540 | /apollo-tracing/0.12.2: 541 | resolution: {integrity: sha512-SYN4o0C0wR1fyS3+P0FthyvsQVHFopdmN3IU64IaspR/RZScPxZ3Ae8uu++fTvkQflAkglnFM0aX6DkZERBp6w==} 542 | engines: {node: '>=4.0'} 543 | peerDependencies: 544 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 545 | dependencies: 546 | apollo-server-env: 3.0.0 547 | apollo-server-plugin-base: 0.10.4 548 | dev: false 549 | 550 | /apollo-utilities/1.3.4: 551 | resolution: {integrity: sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==} 552 | peerDependencies: 553 | graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 554 | dependencies: 555 | '@wry/equality': 0.1.11 556 | fast-json-stable-stringify: 2.1.0 557 | ts-invariant: 0.4.4 558 | tslib: 1.14.1 559 | dev: false 560 | 561 | /array-flatten/1.1.1: 562 | resolution: {integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=} 563 | dev: false 564 | 565 | /async-limiter/1.0.1: 566 | resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} 567 | dev: false 568 | 569 | /async-retry/1.3.1: 570 | resolution: {integrity: sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==} 571 | dependencies: 572 | retry: 0.12.0 573 | dev: false 574 | 575 | /asynckit/0.4.0: 576 | resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} 577 | dev: false 578 | 579 | /backo2/1.0.2: 580 | resolution: {integrity: sha1-MasayLEpNjRj41s+u2n038+6eUc=} 581 | dev: false 582 | 583 | /balanced-match/1.0.0: 584 | resolution: {integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c=} 585 | dev: true 586 | 587 | /binary-extensions/2.2.0: 588 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 589 | engines: {node: '>=8'} 590 | dev: true 591 | 592 | /body-parser/1.19.0: 593 | resolution: {integrity: sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==} 594 | engines: {node: '>= 0.8'} 595 | dependencies: 596 | bytes: 3.1.0 597 | content-type: 1.0.4 598 | debug: 2.6.9 599 | depd: 1.1.2 600 | http-errors: 1.7.2 601 | iconv-lite: 0.4.24 602 | on-finished: 2.3.0 603 | qs: 6.7.0 604 | raw-body: 2.4.0 605 | type-is: 1.6.18 606 | dev: false 607 | 608 | /boxen/4.2.0: 609 | resolution: {integrity: sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==} 610 | engines: {node: '>=8'} 611 | dependencies: 612 | ansi-align: 3.0.0 613 | camelcase: 5.3.1 614 | chalk: 3.0.0 615 | cli-boxes: 2.2.1 616 | string-width: 4.2.2 617 | term-size: 2.2.1 618 | type-fest: 0.8.1 619 | widest-line: 3.1.0 620 | dev: true 621 | 622 | /brace-expansion/1.1.11: 623 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 624 | dependencies: 625 | balanced-match: 1.0.0 626 | concat-map: 0.0.1 627 | dev: true 628 | 629 | /braces/3.0.2: 630 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 631 | engines: {node: '>=8'} 632 | dependencies: 633 | fill-range: 7.0.1 634 | dev: true 635 | 636 | /busboy/0.3.1: 637 | resolution: {integrity: sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==} 638 | engines: {node: '>=4.5.0'} 639 | dependencies: 640 | dicer: 0.3.0 641 | dev: false 642 | 643 | /bytes/3.1.0: 644 | resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} 645 | engines: {node: '>= 0.8'} 646 | dev: false 647 | 648 | /cacheable-request/6.1.0: 649 | resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} 650 | engines: {node: '>=8'} 651 | dependencies: 652 | clone-response: 1.0.2 653 | get-stream: 5.2.0 654 | http-cache-semantics: 4.1.0 655 | keyv: 3.1.0 656 | lowercase-keys: 2.0.0 657 | normalize-url: 4.5.0 658 | responselike: 1.0.2 659 | dev: true 660 | 661 | /call-bind/1.0.2: 662 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} 663 | dependencies: 664 | function-bind: 1.1.1 665 | get-intrinsic: 1.1.1 666 | dev: false 667 | 668 | /camel-case/4.1.2: 669 | resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} 670 | dependencies: 671 | pascal-case: 3.1.2 672 | tslib: 2.1.0 673 | dev: false 674 | 675 | /camelcase/5.3.1: 676 | resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} 677 | engines: {node: '>=6'} 678 | dev: true 679 | 680 | /chalk/3.0.0: 681 | resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} 682 | engines: {node: '>=8'} 683 | dependencies: 684 | ansi-styles: 4.3.0 685 | supports-color: 7.2.0 686 | dev: true 687 | 688 | /chokidar/3.5.1: 689 | resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==} 690 | engines: {node: '>= 8.10.0'} 691 | dependencies: 692 | anymatch: 3.1.1 693 | braces: 3.0.2 694 | glob-parent: 5.1.2 695 | is-binary-path: 2.1.0 696 | is-glob: 4.0.1 697 | normalize-path: 3.0.0 698 | readdirp: 3.5.0 699 | optionalDependencies: 700 | fsevents: 2.3.2 701 | dev: true 702 | 703 | /ci-info/2.0.0: 704 | resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} 705 | dev: true 706 | 707 | /cli-boxes/2.2.1: 708 | resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} 709 | engines: {node: '>=6'} 710 | dev: true 711 | 712 | /clone-response/1.0.2: 713 | resolution: {integrity: sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=} 714 | dependencies: 715 | mimic-response: 1.0.1 716 | dev: true 717 | 718 | /color-convert/2.0.1: 719 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 720 | engines: {node: '>=7.0.0'} 721 | dependencies: 722 | color-name: 1.1.4 723 | dev: true 724 | 725 | /color-name/1.1.4: 726 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 727 | dev: true 728 | 729 | /combined-stream/1.0.8: 730 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 731 | engines: {node: '>= 0.8'} 732 | dependencies: 733 | delayed-stream: 1.0.0 734 | dev: false 735 | 736 | /commander/2.20.3: 737 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 738 | dev: false 739 | 740 | /concat-map/0.0.1: 741 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 742 | dev: true 743 | 744 | /configstore/5.0.1: 745 | resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} 746 | engines: {node: '>=8'} 747 | dependencies: 748 | dot-prop: 5.3.0 749 | graceful-fs: 4.2.6 750 | make-dir: 3.1.0 751 | unique-string: 2.0.0 752 | write-file-atomic: 3.0.3 753 | xdg-basedir: 4.0.0 754 | dev: true 755 | 756 | /content-disposition/0.5.3: 757 | resolution: {integrity: sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==} 758 | engines: {node: '>= 0.6'} 759 | dependencies: 760 | safe-buffer: 5.1.2 761 | dev: false 762 | 763 | /content-type/1.0.4: 764 | resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} 765 | engines: {node: '>= 0.6'} 766 | dev: false 767 | 768 | /cookie-signature/1.0.6: 769 | resolution: {integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw=} 770 | dev: false 771 | 772 | /cookie/0.4.0: 773 | resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==} 774 | engines: {node: '>= 0.6'} 775 | dev: false 776 | 777 | /core-js/3.9.1: 778 | resolution: {integrity: sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==} 779 | requiresBuild: true 780 | dev: false 781 | 782 | /cors/2.8.5: 783 | resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} 784 | engines: {node: '>= 0.10'} 785 | dependencies: 786 | object-assign: 4.1.1 787 | vary: 1.1.2 788 | dev: false 789 | 790 | /crypto-random-string/2.0.0: 791 | resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} 792 | engines: {node: '>=8'} 793 | dev: true 794 | 795 | /cssfilter/0.0.10: 796 | resolution: {integrity: sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=} 797 | dev: false 798 | 799 | /dataloader/2.0.0: 800 | resolution: {integrity: sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==} 801 | dev: false 802 | 803 | /debug/2.6.9: 804 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 805 | dependencies: 806 | ms: 2.0.0 807 | 808 | /debug/3.2.7: 809 | resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} 810 | dependencies: 811 | ms: 2.1.3 812 | dev: true 813 | 814 | /decompress-response/3.3.0: 815 | resolution: {integrity: sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=} 816 | engines: {node: '>=4'} 817 | dependencies: 818 | mimic-response: 1.0.1 819 | dev: true 820 | 821 | /deep-extend/0.6.0: 822 | resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 823 | engines: {node: '>=4.0.0'} 824 | dev: true 825 | 826 | /defer-to-connect/1.1.3: 827 | resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} 828 | dev: true 829 | 830 | /define-properties/1.1.3: 831 | resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} 832 | engines: {node: '>= 0.4'} 833 | dependencies: 834 | object-keys: 1.1.1 835 | dev: false 836 | 837 | /delayed-stream/1.0.0: 838 | resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} 839 | engines: {node: '>=0.4.0'} 840 | dev: false 841 | 842 | /denque/1.5.0: 843 | resolution: {integrity: sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==} 844 | engines: {node: '>=0.10'} 845 | dev: false 846 | 847 | /depd/1.1.2: 848 | resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} 849 | engines: {node: '>= 0.6'} 850 | dev: false 851 | 852 | /deprecated-decorator/0.1.6: 853 | resolution: {integrity: sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=} 854 | dev: false 855 | 856 | /destroy/1.0.4: 857 | resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=} 858 | dev: false 859 | 860 | /dicer/0.3.0: 861 | resolution: {integrity: sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==} 862 | engines: {node: '>=4.5.0'} 863 | dependencies: 864 | streamsearch: 0.1.2 865 | dev: false 866 | 867 | /dot-prop/5.3.0: 868 | resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} 869 | engines: {node: '>=8'} 870 | dependencies: 871 | is-obj: 2.0.0 872 | dev: true 873 | 874 | /duplexer3/0.1.4: 875 | resolution: {integrity: sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=} 876 | dev: true 877 | 878 | /ee-first/1.1.1: 879 | resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} 880 | dev: false 881 | 882 | /emoji-regex/7.0.3: 883 | resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} 884 | dev: true 885 | 886 | /emoji-regex/8.0.0: 887 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 888 | dev: true 889 | 890 | /encodeurl/1.0.2: 891 | resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} 892 | engines: {node: '>= 0.8'} 893 | dev: false 894 | 895 | /end-of-stream/1.4.4: 896 | resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 897 | dependencies: 898 | once: 1.4.0 899 | dev: true 900 | 901 | /es-abstract/1.18.0: 902 | resolution: {integrity: sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==} 903 | engines: {node: '>= 0.4'} 904 | dependencies: 905 | call-bind: 1.0.2 906 | es-to-primitive: 1.2.1 907 | function-bind: 1.1.1 908 | get-intrinsic: 1.1.1 909 | has: 1.0.3 910 | has-symbols: 1.0.2 911 | is-callable: 1.2.3 912 | is-negative-zero: 2.0.1 913 | is-regex: 1.1.2 914 | is-string: 1.0.5 915 | object-inspect: 1.9.0 916 | object-keys: 1.1.1 917 | object.assign: 4.1.2 918 | string.prototype.trimend: 1.0.4 919 | string.prototype.trimstart: 1.0.4 920 | unbox-primitive: 1.0.0 921 | dev: false 922 | 923 | /es-to-primitive/1.2.1: 924 | resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 925 | engines: {node: '>= 0.4'} 926 | dependencies: 927 | is-callable: 1.2.3 928 | is-date-object: 1.0.2 929 | is-symbol: 1.0.3 930 | dev: false 931 | 932 | /escape-goat/2.1.1: 933 | resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} 934 | engines: {node: '>=8'} 935 | dev: true 936 | 937 | /escape-html/1.0.3: 938 | resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} 939 | dev: false 940 | 941 | /etag/1.8.1: 942 | resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} 943 | engines: {node: '>= 0.6'} 944 | dev: false 945 | 946 | /eventemitter3/3.1.2: 947 | resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} 948 | dev: false 949 | 950 | /express/4.17.1: 951 | resolution: {integrity: sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==} 952 | engines: {node: '>= 0.10.0'} 953 | dependencies: 954 | accepts: 1.3.7 955 | array-flatten: 1.1.1 956 | body-parser: 1.19.0 957 | content-disposition: 0.5.3 958 | content-type: 1.0.4 959 | cookie: 0.4.0 960 | cookie-signature: 1.0.6 961 | debug: 2.6.9 962 | depd: 1.1.2 963 | encodeurl: 1.0.2 964 | escape-html: 1.0.3 965 | etag: 1.8.1 966 | finalhandler: 1.1.2 967 | fresh: 0.5.2 968 | merge-descriptors: 1.0.1 969 | methods: 1.1.2 970 | on-finished: 2.3.0 971 | parseurl: 1.3.3 972 | path-to-regexp: 0.1.7 973 | proxy-addr: 2.0.6 974 | qs: 6.7.0 975 | range-parser: 1.2.1 976 | safe-buffer: 5.1.2 977 | send: 0.17.1 978 | serve-static: 1.14.1 979 | setprototypeof: 1.1.1 980 | statuses: 1.5.0 981 | type-is: 1.6.18 982 | utils-merge: 1.0.1 983 | vary: 1.1.2 984 | dev: false 985 | 986 | /fast-json-stable-stringify/2.1.0: 987 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 988 | dev: false 989 | 990 | /fill-range/7.0.1: 991 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 992 | engines: {node: '>=8'} 993 | dependencies: 994 | to-regex-range: 5.0.1 995 | dev: true 996 | 997 | /finalhandler/1.1.2: 998 | resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} 999 | engines: {node: '>= 0.8'} 1000 | dependencies: 1001 | debug: 2.6.9 1002 | encodeurl: 1.0.2 1003 | escape-html: 1.0.3 1004 | on-finished: 2.3.0 1005 | parseurl: 1.3.3 1006 | statuses: 1.5.0 1007 | unpipe: 1.0.0 1008 | dev: false 1009 | 1010 | /for-each/0.3.3: 1011 | resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 1012 | dependencies: 1013 | is-callable: 1.2.3 1014 | dev: false 1015 | 1016 | /form-data/3.0.1: 1017 | resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} 1018 | engines: {node: '>= 6'} 1019 | dependencies: 1020 | asynckit: 0.4.0 1021 | combined-stream: 1.0.8 1022 | mime-types: 2.1.29 1023 | dev: false 1024 | 1025 | /forwarded/0.1.2: 1026 | resolution: {integrity: sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=} 1027 | engines: {node: '>= 0.6'} 1028 | dev: false 1029 | 1030 | /fresh/0.5.2: 1031 | resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} 1032 | engines: {node: '>= 0.6'} 1033 | dev: false 1034 | 1035 | /fs-capacitor/2.0.4: 1036 | resolution: {integrity: sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==} 1037 | engines: {node: '>=8.5'} 1038 | dev: false 1039 | 1040 | /fsevents/2.3.2: 1041 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 1042 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1043 | os: [darwin] 1044 | dev: true 1045 | optional: true 1046 | 1047 | /function-bind/1.1.1: 1048 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 1049 | dev: false 1050 | 1051 | /get-intrinsic/1.1.1: 1052 | resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} 1053 | dependencies: 1054 | function-bind: 1.1.1 1055 | has: 1.0.3 1056 | has-symbols: 1.0.2 1057 | dev: false 1058 | 1059 | /get-stream/4.1.0: 1060 | resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} 1061 | engines: {node: '>=6'} 1062 | dependencies: 1063 | pump: 3.0.0 1064 | dev: true 1065 | 1066 | /get-stream/5.2.0: 1067 | resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} 1068 | engines: {node: '>=8'} 1069 | dependencies: 1070 | pump: 3.0.0 1071 | dev: true 1072 | 1073 | /glob-parent/5.1.2: 1074 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1075 | engines: {node: '>= 6'} 1076 | dependencies: 1077 | is-glob: 4.0.1 1078 | dev: true 1079 | 1080 | /global-dirs/2.1.0: 1081 | resolution: {integrity: sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==} 1082 | engines: {node: '>=8'} 1083 | dependencies: 1084 | ini: 1.3.7 1085 | dev: true 1086 | 1087 | /got/9.6.0: 1088 | resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} 1089 | engines: {node: '>=8.6'} 1090 | dependencies: 1091 | '@sindresorhus/is': 0.14.0 1092 | '@szmarczak/http-timer': 1.1.2 1093 | cacheable-request: 6.1.0 1094 | decompress-response: 3.3.0 1095 | duplexer3: 0.1.4 1096 | get-stream: 4.1.0 1097 | lowercase-keys: 1.0.1 1098 | mimic-response: 1.0.1 1099 | p-cancelable: 1.1.0 1100 | to-readable-stream: 1.0.0 1101 | url-parse-lax: 3.0.0 1102 | dev: true 1103 | 1104 | /graceful-fs/4.2.6: 1105 | resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==} 1106 | dev: true 1107 | 1108 | /graphql-extensions/0.12.8: 1109 | resolution: {integrity: sha512-xjsSaB6yKt9jarFNNdivl2VOx52WySYhxPgf8Y16g6GKZyAzBoIFiwyGw5PJDlOSUa6cpmzn6o7z8fVMbSAbkg==} 1110 | engines: {node: '>=6.0'} 1111 | peerDependencies: 1112 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 1113 | dependencies: 1114 | '@apollographql/apollo-tools': 0.4.9 1115 | apollo-server-env: 3.0.0 1116 | apollo-server-types: 0.6.3 1117 | dev: false 1118 | 1119 | /graphql-middleware/6.0.4: 1120 | resolution: {integrity: sha512-T49lpvrRHpUzBXUupNnBx82WAmWBoTmsWcc+XdblYClj9WnRMrtxeunQlTsbsSCSD7ZahN1MELd0xKfC2hfwUw==} 1121 | peerDependencies: 1122 | graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 1123 | dependencies: 1124 | '@graphql-tools/delegate': 7.0.10 1125 | '@graphql-tools/schema': 7.1.3 1126 | dev: false 1127 | 1128 | /graphql-shield/7.5.0_graphql-middleware@6.0.4: 1129 | resolution: {integrity: sha512-T1A6OreOe/dHDk/1Qg3AHCrKLmTkDJ3fPFGYpSOmUbYXyDnjubK4J5ab5FjHdKHK5fWQRZNTvA0SrBObYsyfaw==} 1130 | peerDependencies: 1131 | graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 1132 | graphql-middleware: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^6.0.0 1133 | dependencies: 1134 | '@types/yup': 0.29.11 1135 | graphql-middleware: 6.0.4 1136 | object-hash: 2.1.1 1137 | yup: 0.31.1 1138 | dev: false 1139 | 1140 | /graphql-subscriptions/1.2.1: 1141 | resolution: {integrity: sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g==} 1142 | peerDependencies: 1143 | graphql: ^0.10.5 || ^0.11.3 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 1144 | dependencies: 1145 | iterall: 1.3.0 1146 | dev: false 1147 | 1148 | /graphql-tag/2.11.0: 1149 | resolution: {integrity: sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==} 1150 | peerDependencies: 1151 | graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 1152 | dev: false 1153 | 1154 | /graphql-tools/4.0.8: 1155 | resolution: {integrity: sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg==} 1156 | peerDependencies: 1157 | graphql: ^0.13.0 || ^14.0.0 || ^15.0.0 1158 | dependencies: 1159 | apollo-link: 1.2.14 1160 | apollo-utilities: 1.3.4 1161 | deprecated-decorator: 0.1.6 1162 | iterall: 1.3.0 1163 | uuid: 3.4.0 1164 | dev: false 1165 | 1166 | /has-bigints/1.0.1: 1167 | resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} 1168 | dev: false 1169 | 1170 | /has-flag/3.0.0: 1171 | resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} 1172 | engines: {node: '>=4'} 1173 | dev: true 1174 | 1175 | /has-flag/4.0.0: 1176 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1177 | engines: {node: '>=8'} 1178 | dev: true 1179 | 1180 | /has-symbols/1.0.2: 1181 | resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} 1182 | engines: {node: '>= 0.4'} 1183 | dev: false 1184 | 1185 | /has-yarn/2.1.0: 1186 | resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} 1187 | engines: {node: '>=8'} 1188 | dev: true 1189 | 1190 | /has/1.0.3: 1191 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 1192 | engines: {node: '>= 0.4.0'} 1193 | dependencies: 1194 | function-bind: 1.1.1 1195 | dev: false 1196 | 1197 | /http-cache-semantics/4.1.0: 1198 | resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} 1199 | dev: true 1200 | 1201 | /http-errors/1.7.2: 1202 | resolution: {integrity: sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==} 1203 | engines: {node: '>= 0.6'} 1204 | dependencies: 1205 | depd: 1.1.2 1206 | inherits: 2.0.3 1207 | setprototypeof: 1.1.1 1208 | statuses: 1.5.0 1209 | toidentifier: 1.0.0 1210 | dev: false 1211 | 1212 | /http-errors/1.7.3: 1213 | resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} 1214 | engines: {node: '>= 0.6'} 1215 | dependencies: 1216 | depd: 1.1.2 1217 | inherits: 2.0.4 1218 | setprototypeof: 1.1.1 1219 | statuses: 1.5.0 1220 | toidentifier: 1.0.0 1221 | dev: false 1222 | 1223 | /http-errors/1.8.0: 1224 | resolution: {integrity: sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==} 1225 | engines: {node: '>= 0.6'} 1226 | dependencies: 1227 | depd: 1.1.2 1228 | inherits: 2.0.4 1229 | setprototypeof: 1.2.0 1230 | statuses: 1.5.0 1231 | toidentifier: 1.0.0 1232 | dev: false 1233 | 1234 | /iconv-lite/0.4.24: 1235 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 1236 | engines: {node: '>=0.10.0'} 1237 | dependencies: 1238 | safer-buffer: 2.1.2 1239 | dev: false 1240 | 1241 | /ignore-by-default/1.0.1: 1242 | resolution: {integrity: sha1-SMptcvbGo68Aqa1K5odr44ieKwk=} 1243 | dev: true 1244 | 1245 | /import-lazy/2.1.0: 1246 | resolution: {integrity: sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=} 1247 | engines: {node: '>=4'} 1248 | dev: true 1249 | 1250 | /imurmurhash/0.1.4: 1251 | resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} 1252 | engines: {node: '>=0.8.19'} 1253 | dev: true 1254 | 1255 | /inherits/2.0.3: 1256 | resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=} 1257 | dev: false 1258 | 1259 | /inherits/2.0.4: 1260 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 1261 | dev: false 1262 | 1263 | /ini/1.3.7: 1264 | resolution: {integrity: sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==} 1265 | dev: true 1266 | 1267 | /ini/1.3.8: 1268 | resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} 1269 | dev: true 1270 | 1271 | /ipaddr.js/1.9.1: 1272 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} 1273 | engines: {node: '>= 0.10'} 1274 | dev: false 1275 | 1276 | /is-bigint/1.0.1: 1277 | resolution: {integrity: sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==} 1278 | dev: false 1279 | 1280 | /is-binary-path/2.1.0: 1281 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 1282 | engines: {node: '>=8'} 1283 | dependencies: 1284 | binary-extensions: 2.2.0 1285 | dev: true 1286 | 1287 | /is-boolean-object/1.1.0: 1288 | resolution: {integrity: sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==} 1289 | engines: {node: '>= 0.4'} 1290 | dependencies: 1291 | call-bind: 1.0.2 1292 | dev: false 1293 | 1294 | /is-callable/1.2.3: 1295 | resolution: {integrity: sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==} 1296 | engines: {node: '>= 0.4'} 1297 | dev: false 1298 | 1299 | /is-ci/2.0.0: 1300 | resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} 1301 | hasBin: true 1302 | dependencies: 1303 | ci-info: 2.0.0 1304 | dev: true 1305 | 1306 | /is-date-object/1.0.2: 1307 | resolution: {integrity: sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==} 1308 | engines: {node: '>= 0.4'} 1309 | dev: false 1310 | 1311 | /is-extglob/2.1.1: 1312 | resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} 1313 | engines: {node: '>=0.10.0'} 1314 | dev: true 1315 | 1316 | /is-fullwidth-code-point/2.0.0: 1317 | resolution: {integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=} 1318 | engines: {node: '>=4'} 1319 | dev: true 1320 | 1321 | /is-fullwidth-code-point/3.0.0: 1322 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 1323 | engines: {node: '>=8'} 1324 | dev: true 1325 | 1326 | /is-glob/4.0.1: 1327 | resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==} 1328 | engines: {node: '>=0.10.0'} 1329 | dependencies: 1330 | is-extglob: 2.1.1 1331 | dev: true 1332 | 1333 | /is-installed-globally/0.3.2: 1334 | resolution: {integrity: sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==} 1335 | engines: {node: '>=8'} 1336 | dependencies: 1337 | global-dirs: 2.1.0 1338 | is-path-inside: 3.0.3 1339 | dev: true 1340 | 1341 | /is-negative-zero/2.0.1: 1342 | resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==} 1343 | engines: {node: '>= 0.4'} 1344 | dev: false 1345 | 1346 | /is-npm/4.0.0: 1347 | resolution: {integrity: sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==} 1348 | engines: {node: '>=8'} 1349 | dev: true 1350 | 1351 | /is-number-object/1.0.4: 1352 | resolution: {integrity: sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==} 1353 | engines: {node: '>= 0.4'} 1354 | dev: false 1355 | 1356 | /is-number/7.0.0: 1357 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1358 | engines: {node: '>=0.12.0'} 1359 | dev: true 1360 | 1361 | /is-obj/2.0.0: 1362 | resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} 1363 | engines: {node: '>=8'} 1364 | dev: true 1365 | 1366 | /is-path-inside/3.0.3: 1367 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 1368 | engines: {node: '>=8'} 1369 | dev: true 1370 | 1371 | /is-promise/4.0.0: 1372 | resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} 1373 | dev: false 1374 | 1375 | /is-regex/1.1.2: 1376 | resolution: {integrity: sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==} 1377 | engines: {node: '>= 0.4'} 1378 | dependencies: 1379 | call-bind: 1.0.2 1380 | has-symbols: 1.0.2 1381 | dev: false 1382 | 1383 | /is-string/1.0.5: 1384 | resolution: {integrity: sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==} 1385 | engines: {node: '>= 0.4'} 1386 | dev: false 1387 | 1388 | /is-symbol/1.0.3: 1389 | resolution: {integrity: sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==} 1390 | engines: {node: '>= 0.4'} 1391 | dependencies: 1392 | has-symbols: 1.0.2 1393 | dev: false 1394 | 1395 | /is-typedarray/1.0.0: 1396 | resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} 1397 | dev: true 1398 | 1399 | /is-yarn-global/0.3.0: 1400 | resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} 1401 | dev: true 1402 | 1403 | /iterall/1.3.0: 1404 | resolution: {integrity: sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==} 1405 | dev: false 1406 | 1407 | /json-buffer/3.0.0: 1408 | resolution: {integrity: sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=} 1409 | dev: true 1410 | 1411 | /keyv/3.1.0: 1412 | resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} 1413 | dependencies: 1414 | json-buffer: 3.0.0 1415 | dev: true 1416 | 1417 | /latest-version/5.1.0: 1418 | resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} 1419 | engines: {node: '>=8'} 1420 | dependencies: 1421 | package-json: 6.5.0 1422 | dev: true 1423 | 1424 | /lodash-es/4.17.21: 1425 | resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} 1426 | dev: false 1427 | 1428 | /lodash.sortby/4.7.0: 1429 | resolution: {integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=} 1430 | dev: false 1431 | 1432 | /lodash/4.17.21: 1433 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1434 | dev: false 1435 | 1436 | /loglevel/1.7.1: 1437 | resolution: {integrity: sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==} 1438 | engines: {node: '>= 0.6.0'} 1439 | dev: false 1440 | 1441 | /long/4.0.0: 1442 | resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} 1443 | dev: false 1444 | 1445 | /lower-case/2.0.2: 1446 | resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} 1447 | dependencies: 1448 | tslib: 2.1.0 1449 | dev: false 1450 | 1451 | /lowercase-keys/1.0.1: 1452 | resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} 1453 | engines: {node: '>=0.10.0'} 1454 | dev: true 1455 | 1456 | /lowercase-keys/2.0.0: 1457 | resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} 1458 | engines: {node: '>=8'} 1459 | dev: true 1460 | 1461 | /lru-cache/6.0.0: 1462 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1463 | engines: {node: '>=10'} 1464 | dependencies: 1465 | yallist: 4.0.0 1466 | dev: false 1467 | 1468 | /make-dir/3.1.0: 1469 | resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} 1470 | engines: {node: '>=8'} 1471 | dependencies: 1472 | semver: 6.3.0 1473 | dev: true 1474 | 1475 | /media-typer/0.3.0: 1476 | resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} 1477 | engines: {node: '>= 0.6'} 1478 | dev: false 1479 | 1480 | /merge-descriptors/1.0.1: 1481 | resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=} 1482 | dev: false 1483 | 1484 | /methods/1.1.2: 1485 | resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} 1486 | engines: {node: '>= 0.6'} 1487 | dev: false 1488 | 1489 | /mime-db/1.46.0: 1490 | resolution: {integrity: sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==} 1491 | engines: {node: '>= 0.6'} 1492 | dev: false 1493 | 1494 | /mime-types/2.1.29: 1495 | resolution: {integrity: sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==} 1496 | engines: {node: '>= 0.6'} 1497 | dependencies: 1498 | mime-db: 1.46.0 1499 | dev: false 1500 | 1501 | /mime/1.6.0: 1502 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} 1503 | engines: {node: '>=4'} 1504 | hasBin: true 1505 | dev: false 1506 | 1507 | /mimic-response/1.0.1: 1508 | resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} 1509 | engines: {node: '>=4'} 1510 | dev: true 1511 | 1512 | /minimatch/3.0.4: 1513 | resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} 1514 | dependencies: 1515 | brace-expansion: 1.1.11 1516 | dev: true 1517 | 1518 | /minimist/1.2.5: 1519 | resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} 1520 | dev: true 1521 | 1522 | /ms/2.0.0: 1523 | resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} 1524 | 1525 | /ms/2.1.1: 1526 | resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==} 1527 | dev: false 1528 | 1529 | /ms/2.1.3: 1530 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1531 | dev: true 1532 | 1533 | /negotiator/0.6.2: 1534 | resolution: {integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==} 1535 | engines: {node: '>= 0.6'} 1536 | dev: false 1537 | 1538 | /no-case/3.0.4: 1539 | resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} 1540 | dependencies: 1541 | lower-case: 2.0.2 1542 | tslib: 2.1.0 1543 | dev: false 1544 | 1545 | /node-fetch/2.6.1: 1546 | resolution: {integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==} 1547 | engines: {node: 4.x || >=6.0.0} 1548 | dev: false 1549 | 1550 | /nodemon/2.0.7: 1551 | resolution: {integrity: sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==} 1552 | engines: {node: '>=8.10.0'} 1553 | hasBin: true 1554 | requiresBuild: true 1555 | dependencies: 1556 | chokidar: 3.5.1 1557 | debug: 3.2.7 1558 | ignore-by-default: 1.0.1 1559 | minimatch: 3.0.4 1560 | pstree.remy: 1.1.8 1561 | semver: 5.7.1 1562 | supports-color: 5.5.0 1563 | touch: 3.1.0 1564 | undefsafe: 2.0.3 1565 | update-notifier: 4.1.3 1566 | dev: true 1567 | 1568 | /nopt/1.0.10: 1569 | resolution: {integrity: sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=} 1570 | hasBin: true 1571 | dependencies: 1572 | abbrev: 1.1.1 1573 | dev: true 1574 | 1575 | /normalize-path/3.0.0: 1576 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1577 | engines: {node: '>=0.10.0'} 1578 | dev: true 1579 | 1580 | /normalize-url/4.5.0: 1581 | resolution: {integrity: sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==} 1582 | engines: {node: '>=8'} 1583 | dev: true 1584 | 1585 | /object-assign/4.1.1: 1586 | resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} 1587 | engines: {node: '>=0.10.0'} 1588 | dev: false 1589 | 1590 | /object-hash/2.1.1: 1591 | resolution: {integrity: sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==} 1592 | engines: {node: '>= 6'} 1593 | dev: false 1594 | 1595 | /object-inspect/1.9.0: 1596 | resolution: {integrity: sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==} 1597 | dev: false 1598 | 1599 | /object-keys/1.1.1: 1600 | resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1601 | engines: {node: '>= 0.4'} 1602 | dev: false 1603 | 1604 | /object-path/0.11.5: 1605 | resolution: {integrity: sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==} 1606 | engines: {node: '>= 10.12.0'} 1607 | dev: false 1608 | 1609 | /object.assign/4.1.2: 1610 | resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} 1611 | engines: {node: '>= 0.4'} 1612 | dependencies: 1613 | call-bind: 1.0.2 1614 | define-properties: 1.1.3 1615 | has-symbols: 1.0.2 1616 | object-keys: 1.1.1 1617 | dev: false 1618 | 1619 | /object.getownpropertydescriptors/2.1.2: 1620 | resolution: {integrity: sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==} 1621 | engines: {node: '>= 0.8'} 1622 | dependencies: 1623 | call-bind: 1.0.2 1624 | define-properties: 1.1.3 1625 | es-abstract: 1.18.0 1626 | dev: false 1627 | 1628 | /on-finished/2.3.0: 1629 | resolution: {integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=} 1630 | engines: {node: '>= 0.8'} 1631 | dependencies: 1632 | ee-first: 1.1.1 1633 | dev: false 1634 | 1635 | /once/1.4.0: 1636 | resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} 1637 | dependencies: 1638 | wrappy: 1.0.2 1639 | dev: true 1640 | 1641 | /p-cancelable/1.1.0: 1642 | resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} 1643 | engines: {node: '>=6'} 1644 | dev: true 1645 | 1646 | /package-json/6.5.0: 1647 | resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} 1648 | engines: {node: '>=8'} 1649 | dependencies: 1650 | got: 9.6.0 1651 | registry-auth-token: 4.2.1 1652 | registry-url: 5.1.0 1653 | semver: 6.3.0 1654 | dev: true 1655 | 1656 | /parseurl/1.3.3: 1657 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 1658 | engines: {node: '>= 0.8'} 1659 | dev: false 1660 | 1661 | /pascal-case/3.1.2: 1662 | resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} 1663 | dependencies: 1664 | no-case: 3.0.4 1665 | tslib: 2.1.0 1666 | dev: false 1667 | 1668 | /path-to-regexp/0.1.7: 1669 | resolution: {integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=} 1670 | dev: false 1671 | 1672 | /picomatch/2.2.2: 1673 | resolution: {integrity: sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==} 1674 | engines: {node: '>=8.6'} 1675 | dev: true 1676 | 1677 | /prepend-http/2.0.0: 1678 | resolution: {integrity: sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=} 1679 | engines: {node: '>=4'} 1680 | dev: true 1681 | 1682 | /property-expr/2.0.4: 1683 | resolution: {integrity: sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==} 1684 | dev: false 1685 | 1686 | /proxy-addr/2.0.6: 1687 | resolution: {integrity: sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==} 1688 | engines: {node: '>= 0.10'} 1689 | dependencies: 1690 | forwarded: 0.1.2 1691 | ipaddr.js: 1.9.1 1692 | dev: false 1693 | 1694 | /pstree.remy/1.1.8: 1695 | resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} 1696 | dev: true 1697 | 1698 | /pump/3.0.0: 1699 | resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} 1700 | dependencies: 1701 | end-of-stream: 1.4.4 1702 | once: 1.4.0 1703 | dev: true 1704 | 1705 | /pupa/2.1.1: 1706 | resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} 1707 | engines: {node: '>=8'} 1708 | dependencies: 1709 | escape-goat: 2.1.1 1710 | dev: true 1711 | 1712 | /qs/6.7.0: 1713 | resolution: {integrity: sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==} 1714 | engines: {node: '>=0.6'} 1715 | dev: false 1716 | 1717 | /range-parser/1.2.1: 1718 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} 1719 | engines: {node: '>= 0.6'} 1720 | dev: false 1721 | 1722 | /raw-body/2.4.0: 1723 | resolution: {integrity: sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==} 1724 | engines: {node: '>= 0.8'} 1725 | dependencies: 1726 | bytes: 3.1.0 1727 | http-errors: 1.7.2 1728 | iconv-lite: 0.4.24 1729 | unpipe: 1.0.0 1730 | dev: false 1731 | 1732 | /rc/1.2.8: 1733 | resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 1734 | hasBin: true 1735 | dependencies: 1736 | deep-extend: 0.6.0 1737 | ini: 1.3.8 1738 | minimist: 1.2.5 1739 | strip-json-comments: 2.0.1 1740 | dev: true 1741 | 1742 | /readdirp/3.5.0: 1743 | resolution: {integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==} 1744 | engines: {node: '>=8.10.0'} 1745 | dependencies: 1746 | picomatch: 2.2.2 1747 | dev: true 1748 | 1749 | /redis-commands/1.7.0: 1750 | resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==} 1751 | dev: false 1752 | 1753 | /redis-errors/1.2.0: 1754 | resolution: {integrity: sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=} 1755 | engines: {node: '>=4'} 1756 | dev: false 1757 | 1758 | /redis-parser/3.0.0: 1759 | resolution: {integrity: sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=} 1760 | engines: {node: '>=4'} 1761 | dependencies: 1762 | redis-errors: 1.2.0 1763 | dev: false 1764 | 1765 | /redis/3.0.2: 1766 | resolution: {integrity: sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==} 1767 | engines: {node: '>=6'} 1768 | dependencies: 1769 | denque: 1.5.0 1770 | redis-commands: 1.7.0 1771 | redis-errors: 1.2.0 1772 | redis-parser: 3.0.0 1773 | dev: false 1774 | 1775 | /regenerator-runtime/0.13.7: 1776 | resolution: {integrity: sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==} 1777 | dev: false 1778 | 1779 | /registry-auth-token/4.2.1: 1780 | resolution: {integrity: sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==} 1781 | engines: {node: '>=6.0.0'} 1782 | dependencies: 1783 | rc: 1.2.8 1784 | dev: true 1785 | 1786 | /registry-url/5.1.0: 1787 | resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} 1788 | engines: {node: '>=8'} 1789 | dependencies: 1790 | rc: 1.2.8 1791 | dev: true 1792 | 1793 | /responselike/1.0.2: 1794 | resolution: {integrity: sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=} 1795 | dependencies: 1796 | lowercase-keys: 1.0.1 1797 | dev: true 1798 | 1799 | /retry/0.12.0: 1800 | resolution: {integrity: sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=} 1801 | engines: {node: '>= 4'} 1802 | dev: false 1803 | 1804 | /safe-buffer/5.1.2: 1805 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 1806 | dev: false 1807 | 1808 | /safe-buffer/5.2.1: 1809 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1810 | dev: false 1811 | 1812 | /safer-buffer/2.1.2: 1813 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1814 | dev: false 1815 | 1816 | /semver-diff/3.1.1: 1817 | resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} 1818 | engines: {node: '>=8'} 1819 | dependencies: 1820 | semver: 6.3.0 1821 | dev: true 1822 | 1823 | /semver/5.7.1: 1824 | resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} 1825 | hasBin: true 1826 | dev: true 1827 | 1828 | /semver/6.3.0: 1829 | resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} 1830 | hasBin: true 1831 | dev: true 1832 | 1833 | /send/0.17.1: 1834 | resolution: {integrity: sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==} 1835 | engines: {node: '>= 0.8.0'} 1836 | dependencies: 1837 | debug: 2.6.9 1838 | depd: 1.1.2 1839 | destroy: 1.0.4 1840 | encodeurl: 1.0.2 1841 | escape-html: 1.0.3 1842 | etag: 1.8.1 1843 | fresh: 0.5.2 1844 | http-errors: 1.7.3 1845 | mime: 1.6.0 1846 | ms: 2.1.1 1847 | on-finished: 2.3.0 1848 | range-parser: 1.2.1 1849 | statuses: 1.5.0 1850 | dev: false 1851 | 1852 | /serve-static/1.14.1: 1853 | resolution: {integrity: sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==} 1854 | engines: {node: '>= 0.8.0'} 1855 | dependencies: 1856 | encodeurl: 1.0.2 1857 | escape-html: 1.0.3 1858 | parseurl: 1.3.3 1859 | send: 0.17.1 1860 | dev: false 1861 | 1862 | /setprototypeof/1.1.1: 1863 | resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} 1864 | dev: false 1865 | 1866 | /setprototypeof/1.2.0: 1867 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 1868 | dev: false 1869 | 1870 | /sha.js/2.4.11: 1871 | resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} 1872 | hasBin: true 1873 | dependencies: 1874 | inherits: 2.0.4 1875 | safe-buffer: 5.2.1 1876 | dev: false 1877 | 1878 | /signal-exit/3.0.3: 1879 | resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==} 1880 | dev: true 1881 | 1882 | /statuses/1.5.0: 1883 | resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} 1884 | engines: {node: '>= 0.6'} 1885 | dev: false 1886 | 1887 | /stoppable/1.1.0: 1888 | resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} 1889 | engines: {node: '>=4', npm: '>=6'} 1890 | dev: false 1891 | 1892 | /streamsearch/0.1.2: 1893 | resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} 1894 | engines: {node: '>=0.8.0'} 1895 | dev: false 1896 | 1897 | /string-width/3.1.0: 1898 | resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} 1899 | engines: {node: '>=6'} 1900 | dependencies: 1901 | emoji-regex: 7.0.3 1902 | is-fullwidth-code-point: 2.0.0 1903 | strip-ansi: 5.2.0 1904 | dev: true 1905 | 1906 | /string-width/4.2.2: 1907 | resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==} 1908 | engines: {node: '>=8'} 1909 | dependencies: 1910 | emoji-regex: 8.0.0 1911 | is-fullwidth-code-point: 3.0.0 1912 | strip-ansi: 6.0.0 1913 | dev: true 1914 | 1915 | /string.prototype.trimend/1.0.4: 1916 | resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} 1917 | dependencies: 1918 | call-bind: 1.0.2 1919 | define-properties: 1.1.3 1920 | dev: false 1921 | 1922 | /string.prototype.trimstart/1.0.4: 1923 | resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} 1924 | dependencies: 1925 | call-bind: 1.0.2 1926 | define-properties: 1.1.3 1927 | dev: false 1928 | 1929 | /strip-ansi/5.2.0: 1930 | resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} 1931 | engines: {node: '>=6'} 1932 | dependencies: 1933 | ansi-regex: 4.1.0 1934 | dev: true 1935 | 1936 | /strip-ansi/6.0.0: 1937 | resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==} 1938 | engines: {node: '>=8'} 1939 | dependencies: 1940 | ansi-regex: 5.0.0 1941 | dev: true 1942 | 1943 | /strip-json-comments/2.0.1: 1944 | resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} 1945 | engines: {node: '>=0.10.0'} 1946 | dev: true 1947 | 1948 | /subscriptions-transport-ws/0.9.18: 1949 | resolution: {integrity: sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA==} 1950 | peerDependencies: 1951 | graphql: '>=0.10.0' 1952 | dependencies: 1953 | backo2: 1.0.2 1954 | eventemitter3: 3.1.2 1955 | iterall: 1.3.0 1956 | symbol-observable: 1.2.0 1957 | ws: 5.2.2 1958 | dev: false 1959 | 1960 | /supports-color/5.5.0: 1961 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 1962 | engines: {node: '>=4'} 1963 | dependencies: 1964 | has-flag: 3.0.0 1965 | dev: true 1966 | 1967 | /supports-color/7.2.0: 1968 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1969 | engines: {node: '>=8'} 1970 | dependencies: 1971 | has-flag: 4.0.0 1972 | dev: true 1973 | 1974 | /symbol-observable/1.2.0: 1975 | resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} 1976 | engines: {node: '>=0.10.0'} 1977 | dev: false 1978 | 1979 | /term-size/2.2.1: 1980 | resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} 1981 | engines: {node: '>=8'} 1982 | dev: true 1983 | 1984 | /to-readable-stream/1.0.0: 1985 | resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} 1986 | engines: {node: '>=6'} 1987 | dev: true 1988 | 1989 | /to-regex-range/5.0.1: 1990 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1991 | engines: {node: '>=8.0'} 1992 | dependencies: 1993 | is-number: 7.0.0 1994 | dev: true 1995 | 1996 | /toidentifier/1.0.0: 1997 | resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} 1998 | engines: {node: '>=0.6'} 1999 | dev: false 2000 | 2001 | /toposort/2.0.2: 2002 | resolution: {integrity: sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=} 2003 | dev: false 2004 | 2005 | /touch/3.1.0: 2006 | resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} 2007 | hasBin: true 2008 | dependencies: 2009 | nopt: 1.0.10 2010 | dev: true 2011 | 2012 | /ts-invariant/0.4.4: 2013 | resolution: {integrity: sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==} 2014 | dependencies: 2015 | tslib: 1.14.1 2016 | dev: false 2017 | 2018 | /tslib/1.14.1: 2019 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 2020 | dev: false 2021 | 2022 | /tslib/2.0.3: 2023 | resolution: {integrity: sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==} 2024 | dev: false 2025 | 2026 | /tslib/2.1.0: 2027 | resolution: {integrity: sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==} 2028 | dev: false 2029 | 2030 | /type-fest/0.8.1: 2031 | resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} 2032 | engines: {node: '>=8'} 2033 | dev: true 2034 | 2035 | /type-is/1.6.18: 2036 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 2037 | engines: {node: '>= 0.6'} 2038 | dependencies: 2039 | media-typer: 0.3.0 2040 | mime-types: 2.1.29 2041 | dev: false 2042 | 2043 | /typedarray-to-buffer/3.1.5: 2044 | resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} 2045 | dependencies: 2046 | is-typedarray: 1.0.0 2047 | dev: true 2048 | 2049 | /unbox-primitive/1.0.0: 2050 | resolution: {integrity: sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA==} 2051 | dependencies: 2052 | function-bind: 1.1.1 2053 | has-bigints: 1.0.1 2054 | has-symbols: 1.0.2 2055 | which-boxed-primitive: 1.0.2 2056 | dev: false 2057 | 2058 | /undefsafe/2.0.3: 2059 | resolution: {integrity: sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==} 2060 | dependencies: 2061 | debug: 2.6.9 2062 | dev: true 2063 | 2064 | /unique-string/2.0.0: 2065 | resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} 2066 | engines: {node: '>=8'} 2067 | dependencies: 2068 | crypto-random-string: 2.0.0 2069 | dev: true 2070 | 2071 | /unpipe/1.0.0: 2072 | resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} 2073 | engines: {node: '>= 0.8'} 2074 | dev: false 2075 | 2076 | /update-notifier/4.1.3: 2077 | resolution: {integrity: sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==} 2078 | engines: {node: '>=8'} 2079 | dependencies: 2080 | boxen: 4.2.0 2081 | chalk: 3.0.0 2082 | configstore: 5.0.1 2083 | has-yarn: 2.1.0 2084 | import-lazy: 2.1.0 2085 | is-ci: 2.0.0 2086 | is-installed-globally: 0.3.2 2087 | is-npm: 4.0.0 2088 | is-yarn-global: 0.3.0 2089 | latest-version: 5.1.0 2090 | pupa: 2.1.1 2091 | semver-diff: 3.1.1 2092 | xdg-basedir: 4.0.0 2093 | dev: true 2094 | 2095 | /url-parse-lax/3.0.0: 2096 | resolution: {integrity: sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=} 2097 | engines: {node: '>=4'} 2098 | dependencies: 2099 | prepend-http: 2.0.0 2100 | dev: true 2101 | 2102 | /util.promisify/1.1.1: 2103 | resolution: {integrity: sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==} 2104 | dependencies: 2105 | call-bind: 1.0.2 2106 | define-properties: 1.1.3 2107 | for-each: 0.3.3 2108 | has-symbols: 1.0.2 2109 | object.getownpropertydescriptors: 2.1.2 2110 | dev: false 2111 | 2112 | /utils-merge/1.0.1: 2113 | resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=} 2114 | engines: {node: '>= 0.4.0'} 2115 | dev: false 2116 | 2117 | /uuid/3.4.0: 2118 | resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} 2119 | hasBin: true 2120 | dev: false 2121 | 2122 | /uuid/8.3.2: 2123 | resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 2124 | hasBin: true 2125 | dev: false 2126 | 2127 | /vary/1.1.2: 2128 | resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} 2129 | engines: {node: '>= 0.8'} 2130 | dev: false 2131 | 2132 | /which-boxed-primitive/1.0.2: 2133 | resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 2134 | dependencies: 2135 | is-bigint: 1.0.1 2136 | is-boolean-object: 1.1.0 2137 | is-number-object: 1.0.4 2138 | is-string: 1.0.5 2139 | is-symbol: 1.0.3 2140 | dev: false 2141 | 2142 | /widest-line/3.1.0: 2143 | resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} 2144 | engines: {node: '>=8'} 2145 | dependencies: 2146 | string-width: 4.2.2 2147 | dev: true 2148 | 2149 | /wrappy/1.0.2: 2150 | resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} 2151 | dev: true 2152 | 2153 | /write-file-atomic/3.0.3: 2154 | resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} 2155 | dependencies: 2156 | imurmurhash: 0.1.4 2157 | is-typedarray: 1.0.0 2158 | signal-exit: 3.0.3 2159 | typedarray-to-buffer: 3.1.5 2160 | dev: true 2161 | 2162 | /ws/5.2.2: 2163 | resolution: {integrity: sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==} 2164 | dependencies: 2165 | async-limiter: 1.0.1 2166 | dev: false 2167 | 2168 | /ws/6.2.1: 2169 | resolution: {integrity: sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==} 2170 | dependencies: 2171 | async-limiter: 1.0.1 2172 | dev: false 2173 | 2174 | /xdg-basedir/4.0.0: 2175 | resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} 2176 | engines: {node: '>=8'} 2177 | dev: true 2178 | 2179 | /xss/1.0.8: 2180 | resolution: {integrity: sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw==} 2181 | engines: {node: '>= 0.10.0'} 2182 | hasBin: true 2183 | dependencies: 2184 | commander: 2.20.3 2185 | cssfilter: 0.0.10 2186 | dev: false 2187 | 2188 | /yallist/4.0.0: 2189 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 2190 | dev: false 2191 | 2192 | /yup/0.31.1: 2193 | resolution: {integrity: sha512-Lf6648jDYOWR75IlWkVfwesPyW6oj+50NpxlKvsQlpPsB8eI+ndI7b4S1VrwbmeV9hIZDu1MzrlIL4W+gK1jPw==} 2194 | engines: {node: '>=10'} 2195 | dependencies: 2196 | '@babel/runtime': 7.13.10 2197 | lodash: 4.17.21 2198 | lodash-es: 4.17.21 2199 | property-expr: 2.0.4 2200 | toposort: 2.0.2 2201 | dev: false 2202 | 2203 | /zen-observable-ts/0.8.21: 2204 | resolution: {integrity: sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==} 2205 | dependencies: 2206 | tslib: 1.14.1 2207 | zen-observable: 0.8.15 2208 | dev: false 2209 | 2210 | /zen-observable/0.8.15: 2211 | resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} 2212 | dev: false 2213 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-rate-limit", 3 | "version": "3.3.0", 4 | "description": "Add Rate Limiting To Your GraphQL Resolvers 💂‍♀", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "module": "build/esm/index.js", 8 | "repository": "https://github.com/teamplanes/graphql-rate-limit", 9 | "license": "MIT", 10 | "keywords": [], 11 | "scripts": { 12 | "prepare": "npm run build", 13 | "build": "tsup ./src/index.ts --format esm,cjs --legacy-output --dts", 14 | "example": "concurrently \"npm run build -w\" \"cd example && npm run dev\"", 15 | "fix": "eslint ./src/**/*.ts --fix", 16 | "test": "tsup ./src/index.ts && nyc --silent ava", 17 | "lint": "eslint ./src/**/*.ts" 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "engines": { 23 | "node": ">=12.0", 24 | "pnpm": ">=6" 25 | }, 26 | "dependencies": { 27 | "@graphql-tools/utils": "^7.6.0", 28 | "graphql-shield": "^7.5.0", 29 | "lodash.get": "^4.4.2", 30 | "ms": "^2.1.3" 31 | }, 32 | "peerDependencies": { 33 | "graphql": "*" 34 | }, 35 | "devDependencies": { 36 | "@graphql-tools/schema": "^7.1.3", 37 | "@types/lodash.get": "^4.4.6", 38 | "@types/ms": "^0.7.31", 39 | "@types/redis-mock": "^0.17.0", 40 | "@typescript-eslint/eslint-plugin": "^4.19.0", 41 | "@typescript-eslint/parser": "^4.19.0", 42 | "ava": "3.15.0", 43 | "concurrently": "^6.0.0", 44 | "eslint": "7.22.0", 45 | "eslint-config-airbnb-base": "14.2.1", 46 | "eslint-config-prettier": "^8.1.0", 47 | "eslint-plugin-ava": "^11.0.0", 48 | "eslint-plugin-import": "^2.22.1", 49 | "eslint-plugin-prettier": "^3.3.1", 50 | "graphql": "^15.5.0", 51 | "graphql-middleware": "^6.0.4", 52 | "nyc": "^15.1.0", 53 | "prettier": "^2.2.1", 54 | "redis-mock": "^0.56.3", 55 | "standard-version": "^9.1.1", 56 | "ts-node": "^9.1.1", 57 | "tsup": "^4.8.19", 58 | "typescript": "^4.2.3" 59 | }, 60 | "ava": { 61 | "failFast": true, 62 | "extensions": [ 63 | "ts" 64 | ], 65 | "files": [ 66 | "src/lib/**/*.spec.ts" 67 | ], 68 | "require": [ 69 | "ts-node/register" 70 | ] 71 | }, 72 | "prettier": { 73 | "singleQuote": true 74 | }, 75 | "nyc": { 76 | "exclude": [ 77 | "**/*.spec.js" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { createRateLimitDirective } from './lib/field-directive'; 2 | 3 | export * from './lib/types'; 4 | export * from './lib/field-directive'; 5 | export * from './lib/store'; 6 | export * from './lib/in-memory-store'; 7 | export * from './lib/redis-store'; 8 | export * from './lib/rate-limit-error'; 9 | export * from './lib/get-graphql-rate-limiter'; 10 | export * from './lib/rate-limit-shield-rule'; 11 | 12 | export default createRateLimitDirective; 13 | -------------------------------------------------------------------------------- /src/lib/batch-request-cache.ts: -------------------------------------------------------------------------------- 1 | export const getNoOpCache = (): { 2 | set: ({ newTimestamps }: { newTimestamps: number[] }) => number[]; 3 | } => ({ 4 | set: ({ newTimestamps }: { newTimestamps: number[] }) => newTimestamps, 5 | }); 6 | 7 | export const getWeakMapCache = (): { 8 | set: ({ 9 | context, 10 | fieldIdentity, 11 | newTimestamps, 12 | }: { 13 | context: Record; 14 | fieldIdentity: string; 15 | newTimestamps: number[]; 16 | }) => any; 17 | } => { 18 | const cache = new WeakMap(); 19 | 20 | return { 21 | set: ({ 22 | context, 23 | fieldIdentity, 24 | newTimestamps, 25 | }: { 26 | context: Record; 27 | fieldIdentity: string; 28 | newTimestamps: number[]; 29 | }) => { 30 | const currentCalls = cache.get(context) || {}; 31 | 32 | currentCalls[fieldIdentity] = [ 33 | ...(currentCalls[fieldIdentity] || []), 34 | ...newTimestamps, 35 | ]; 36 | cache.set(context, currentCalls); 37 | return currentCalls[fieldIdentity]; 38 | }, 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/field-directive.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { SchemaDirectiveVisitor } from '@graphql-tools/utils'; 3 | import { createRateLimitDirective } from './field-directive'; 4 | 5 | test('createRateLimitDirective', (t) => { 6 | const RateLimiter = createRateLimitDirective(); 7 | t.true(new RateLimiter({}) instanceof SchemaDirectiveVisitor); 8 | }); 9 | -------------------------------------------------------------------------------- /src/lib/field-directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defaultFieldResolver, 3 | DirectiveLocation, 4 | GraphQLBoolean, 5 | GraphQLDirective, 6 | GraphQLField, 7 | GraphQLInt, 8 | GraphQLList, 9 | GraphQLString, 10 | } from 'graphql'; 11 | import { SchemaDirectiveVisitor } from '@graphql-tools/utils'; 12 | import { RateLimitError } from './rate-limit-error'; 13 | import { GraphQLRateLimitDirectiveArgs, GraphQLRateLimitConfig } from './types'; 14 | import { getGraphQLRateLimiter } from './get-graphql-rate-limiter'; 15 | 16 | /** 17 | * Returns the Directive to be passed to your GraphQL server. 18 | * 19 | * TODO: Fix `any` return type; 20 | * @param customConfig 21 | */ 22 | const createRateLimitDirective = ( 23 | customConfig: GraphQLRateLimitConfig = {} 24 | ): any => { 25 | const rateLimiter = getGraphQLRateLimiter(customConfig); 26 | class GraphQLRateLimit extends SchemaDirectiveVisitor { 27 | public static getDirectiveDeclaration( 28 | directiveName: string 29 | ): GraphQLDirective { 30 | return new GraphQLDirective({ 31 | args: { 32 | arrayLengthField: { 33 | type: GraphQLString, 34 | }, 35 | identityArgs: { 36 | type: new GraphQLList(GraphQLString), 37 | }, 38 | max: { 39 | type: GraphQLInt, 40 | }, 41 | message: { 42 | type: GraphQLString, 43 | }, 44 | window: { 45 | type: GraphQLString, 46 | }, 47 | uncountRejected: { 48 | type: GraphQLBoolean, 49 | }, 50 | }, 51 | locations: [DirectiveLocation.FIELD_DEFINITION], 52 | name: directiveName, 53 | }); 54 | } 55 | 56 | public readonly args!: GraphQLRateLimitDirectiveArgs; 57 | 58 | public visitFieldDefinition(field: GraphQLField): void { 59 | const { resolve = defaultFieldResolver } = field; 60 | // eslint-disable-next-line no-param-reassign 61 | field.resolve = async (parent, args, context, info) => { 62 | const errorMessage = await rateLimiter( 63 | { 64 | parent, 65 | args, 66 | context, 67 | info, 68 | }, 69 | this.args 70 | ); 71 | 72 | if (errorMessage) { 73 | throw customConfig.createError 74 | ? customConfig.createError(errorMessage) 75 | : new RateLimitError(errorMessage); 76 | } 77 | 78 | return resolve(parent, args, context, info); 79 | }; 80 | } 81 | } 82 | return GraphQLRateLimit; 83 | }; 84 | 85 | export { createRateLimitDirective }; 86 | -------------------------------------------------------------------------------- /src/lib/get-graphql-rate-limiter.spec.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | import test from 'ava'; 3 | import { GraphQLResolveInfo } from 'graphql'; 4 | import { 5 | getFieldIdentity, 6 | getGraphQLRateLimiter, 7 | } from './get-graphql-rate-limiter'; 8 | import { InMemoryStore } from './in-memory-store'; 9 | import { GraphQLRateLimitDirectiveArgs } from './types'; 10 | 11 | const sleep = (ms: number) => { 12 | return new Promise((resolve) => setTimeout(resolve, ms)); 13 | }; 14 | 15 | test('getFieldIdentity with no identity args', (t) => { 16 | t.is(getFieldIdentity('myField', [], {}), 'myField'); 17 | t.is(getFieldIdentity('random', [], {}), 'random'); 18 | }); 19 | 20 | test('getFieldIdentity with identity args', (t) => { 21 | t.is( 22 | getFieldIdentity('myField', ['id'], { id: 2, name: 'Foo' }), 23 | 'myField:2' 24 | ); 25 | t.is( 26 | getFieldIdentity('myField', ['name', 'id'], { id: 2, name: 'Foo' }), 27 | 'myField:Foo:2' 28 | ); 29 | t.is( 30 | getFieldIdentity('myField', ['name', 'bool'], { bool: true, name: 'Foo' }), 31 | 'myField:Foo:true' 32 | ); 33 | t.is(getFieldIdentity('myField', ['name', 'bool'], {}), 'myField::'); 34 | t.is( 35 | getFieldIdentity('myField', ['name', 'bool'], { name: null }), 36 | 'myField::' 37 | ); 38 | }); 39 | 40 | test('getFieldIdentity with nested identity args', (t) => { 41 | t.is( 42 | getFieldIdentity('myField', ['item.id'], { item: { id: 2 }, name: 'Foo' }), 43 | 'myField:2' 44 | ); 45 | t.is( 46 | getFieldIdentity('myField', ['item.foo'], { item: { id: 2 }, name: 'Foo' }), 47 | 'myField:' 48 | ); 49 | 50 | const obj = { item: { subItem: { id: 9 } }, name: 'Foo' }; 51 | t.is(getFieldIdentity('myField', ['item.subItem.id'], obj), 'myField:9'); 52 | 53 | const objTwo = { item: { subItem: { id: 1 } }, name: 'Foo' }; 54 | t.is( 55 | getFieldIdentity('myField', ['name', 'item.subItem.id'], objTwo), 56 | 'myField:Foo:1' 57 | ); 58 | }); 59 | 60 | test('getGraphQLRateLimiter with an empty store passes, but second time fails', async (t) => { 61 | const rateLimit = getGraphQLRateLimiter({ 62 | store: new InMemoryStore(), 63 | identifyContext: (context) => context.id, 64 | }); 65 | const config = { max: 1, window: '1s' }; 66 | const field = { 67 | parent: {}, 68 | args: {}, 69 | context: { id: '1' }, 70 | info: ({ fieldName: 'myField' } as any) as GraphQLResolveInfo, 71 | }; 72 | t.falsy(await rateLimit(field, config)); 73 | t.is( 74 | await rateLimit(field, config), 75 | `You are trying to access 'myField' too often` 76 | ); 77 | }); 78 | 79 | test('getGraphQLRateLimiter should block a batch of rate limited fields in a single query', async (t) => { 80 | const rateLimit = getGraphQLRateLimiter({ 81 | store: new InMemoryStore(), 82 | identifyContext: (context) => context.id, 83 | enableBatchRequestCache: true, 84 | }); 85 | const config = { max: 3, window: '1s' }; 86 | const field = { 87 | parent: {}, 88 | args: {}, 89 | context: { id: '1' }, 90 | info: ({ fieldName: 'myField' } as any) as GraphQLResolveInfo, 91 | }; 92 | const requests = Array.from({ length: 5 }) 93 | .map(() => rateLimit(field, config)) 94 | .map((p) => p.catch((e) => e)); 95 | 96 | (await Promise.all(requests)).forEach((result, idx) => { 97 | if (idx < 3) t.falsy(result); 98 | else t.is(result, `You are trying to access 'myField' too often`); 99 | }); 100 | }); 101 | 102 | test('getGraphQLRateLimiter timestamps should expire', async (t) => { 103 | const rateLimit = getGraphQLRateLimiter({ 104 | store: new InMemoryStore(), 105 | identifyContext: (context) => context.id, 106 | }); 107 | const config = { max: 1, window: '0.5s' }; 108 | const field = { 109 | parent: {}, 110 | args: {}, 111 | context: { id: '1' }, 112 | info: ({ fieldName: 'myField' } as any) as GraphQLResolveInfo, 113 | }; 114 | t.falsy(await rateLimit(field, config)); 115 | t.is( 116 | await rateLimit(field, config), 117 | `You are trying to access 'myField' too often` 118 | ); 119 | await sleep(500); 120 | t.falsy(await rateLimit(field, config)); 121 | }); 122 | 123 | test('getGraphQLRateLimiter uncountRejected should ignore rejections', async (t) => { 124 | const rateLimit = getGraphQLRateLimiter({ 125 | store: new InMemoryStore(), 126 | identifyContext: (context) => context.id, 127 | }); 128 | const config = { max: 1, window: '1s', uncountRejected: true }; 129 | const field = { 130 | parent: {}, 131 | args: {}, 132 | context: { id: '1' }, 133 | info: ({ fieldName: 'myField' } as any) as GraphQLResolveInfo, 134 | }; 135 | t.falsy(await rateLimit(field, config)); 136 | await sleep(500); 137 | t.is( 138 | await rateLimit(field, config), 139 | `You are trying to access 'myField' too often` 140 | ); 141 | await sleep(500); 142 | t.falsy(await rateLimit(field, config)); 143 | }); 144 | 145 | test('getGraphQLRateLimiter should limit by callCount if arrayLengthField is passed', async (t) => { 146 | const rateLimit = getGraphQLRateLimiter({ 147 | store: new InMemoryStore(), 148 | identifyContext: (context) => context.id, 149 | }); 150 | const config: GraphQLRateLimitDirectiveArgs = { 151 | max: 4, 152 | window: '1s', 153 | arrayLengthField: 'items', 154 | }; 155 | const field = { 156 | parent: {}, 157 | args: { 158 | items: [1, 2, 3, 4, 5], 159 | }, 160 | context: { id: '1' }, 161 | info: ({ fieldName: 'listOfItems' } as any) as GraphQLResolveInfo, 162 | }; 163 | t.is( 164 | await rateLimit(field, config), 165 | `You are trying to access 'listOfItems' too often` 166 | ); 167 | }); 168 | 169 | test('getGraphQLRateLimiter should allow multiple calls to a field if the identityArgs change', async (t) => { 170 | const rateLimit = getGraphQLRateLimiter({ 171 | store: new InMemoryStore(), 172 | identifyContext: (context) => context.id, 173 | }); 174 | const config: GraphQLRateLimitDirectiveArgs = { 175 | max: 1, 176 | window: '1s', 177 | identityArgs: ['id'], 178 | }; 179 | const field = { 180 | parent: {}, 181 | args: { 182 | id: '1', 183 | }, 184 | context: { id: '1' }, 185 | info: ({ fieldName: 'listOfItems' } as any) as GraphQLResolveInfo, 186 | }; 187 | t.falsy(await rateLimit(field, config)); 188 | t.is( 189 | await rateLimit(field, config), 190 | `You are trying to access 'listOfItems' too often` 191 | ); 192 | t.falsy(await rateLimit({ ...field, args: { id: '2' } }, config)); 193 | t.is( 194 | await rateLimit(field, config), 195 | `You are trying to access 'listOfItems' too often` 196 | ); 197 | }); 198 | -------------------------------------------------------------------------------- /src/lib/get-graphql-rate-limiter.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql'; 2 | import get from 'lodash.get'; 3 | import ms from 'ms'; 4 | import { InMemoryStore } from './in-memory-store'; 5 | import { getNoOpCache, getWeakMapCache } from './batch-request-cache'; 6 | import { 7 | FormatErrorInput, 8 | GraphQLRateLimitConfig, 9 | GraphQLRateLimitDirectiveArgs, 10 | Identity, 11 | } from './types'; 12 | 13 | // Default field options 14 | const DEFAULT_WINDOW = 60 * 1000; 15 | const DEFAULT_MAX = 5; 16 | const DEFAULT_FIELD_IDENTITY_ARGS: readonly string[] = []; 17 | 18 | /** 19 | * Returns a string key for the given field + args. With no identityArgs are provided, just the fieldName 20 | * will be used for the key. If an array of resolveArgs are provided, the values of those will be built 21 | * into the key. 22 | * 23 | * Example: 24 | * (fieldName = 'books', identityArgs: ['id', 'title'], resolveArgs: { id: 1, title: 'Foo', subTitle: 'Bar' }) 25 | * => books:1:Foo 26 | * 27 | * @param fieldName 28 | * @param identityArgs 29 | * @param resolveArgs 30 | */ 31 | const getFieldIdentity = ( 32 | fieldName: string, 33 | identityArgs: readonly string[], 34 | resolveArgs: unknown 35 | ): string => { 36 | const argsKey = identityArgs.map((arg) => get(resolveArgs, arg)); 37 | return [fieldName, ...argsKey].join(':'); 38 | }; 39 | 40 | /** 41 | * This is the core rate limiting logic function, APIs (directive, sheild etc.) 42 | * can wrap this or it can be used directly in resolvers. 43 | * @param userConfig - global (usually app-wide) rate limiting config 44 | */ 45 | const getGraphQLRateLimiter = ( 46 | // Main config (e.g. the config passed to the createRateLimitDirective func) 47 | userConfig: GraphQLRateLimitConfig 48 | ): (( 49 | { 50 | args, 51 | context, 52 | info, 53 | }: { 54 | parent: any; 55 | args: Record; 56 | context: any; 57 | info: GraphQLResolveInfo; 58 | }, 59 | { 60 | arrayLengthField, 61 | identityArgs, 62 | max, 63 | window, 64 | message, 65 | uncountRejected, 66 | }: GraphQLRateLimitDirectiveArgs 67 | ) => Promise) => { 68 | // Default directive config 69 | const defaultConfig = { 70 | enableBatchRequestCache: false, 71 | formatError: ({ fieldName }: FormatErrorInput) => { 72 | return `You are trying to access '${fieldName}' too often`; 73 | }, 74 | // Required 75 | identifyContext: () => { 76 | throw new Error( 77 | 'You must implement a createRateLimitDirective.config.identifyContext' 78 | ); 79 | }, 80 | store: new InMemoryStore(), 81 | }; 82 | 83 | const { enableBatchRequestCache, identifyContext, formatError, store } = { 84 | ...defaultConfig, 85 | ...userConfig, 86 | }; 87 | 88 | const batchRequestCache = enableBatchRequestCache 89 | ? getWeakMapCache() 90 | : getNoOpCache(); 91 | 92 | /** 93 | * Field level rate limiter function that returns the error message or undefined 94 | * @param args - pass the resolver args as an object 95 | * @param config - field level config 96 | */ 97 | const rateLimiter = async ( 98 | // Resolver args 99 | { 100 | args, 101 | context, 102 | info, 103 | }: { 104 | parent: any; 105 | args: Record; 106 | context: any; 107 | info: GraphQLResolveInfo; 108 | }, 109 | // Field level config (e.g. the directive parameters) 110 | { 111 | arrayLengthField, 112 | identityArgs, 113 | max, 114 | window, 115 | message, 116 | readOnly, 117 | uncountRejected, 118 | }: GraphQLRateLimitDirectiveArgs 119 | ): Promise => { 120 | // Identify the user or client on the context 121 | const contextIdentity = identifyContext(context); 122 | // User defined window in ms that this field can be accessed for before the call is expired 123 | const windowMs = (window ? ms(window) : DEFAULT_WINDOW) as number; 124 | // String key for this field 125 | const fieldIdentity = getFieldIdentity( 126 | info.fieldName, 127 | identityArgs || DEFAULT_FIELD_IDENTITY_ARGS, 128 | args 129 | ); 130 | 131 | // User configured maximum calls to this field 132 | const maxCalls = max || DEFAULT_MAX; 133 | // Call count could be determined by the lenght of the array value, but most commonly 1 134 | const callCount = 135 | (arrayLengthField && get(args, [arrayLengthField, 'length'])) || 1; 136 | // Allinclusive 'identity' for this resolver call 137 | const identity: Identity = { contextIdentity, fieldIdentity }; 138 | // Timestamp of this call to be save for future ref 139 | const timestamp = Date.now(); 140 | // Create an array of callCount length, filled with the current timestamp 141 | const newTimestamps = [...new Array(callCount || 1)].map(() => timestamp); 142 | 143 | // We set these new timestamps in a temporary memory cache so we can enforce 144 | // ratelimits across queries batched in a single request. 145 | const batchedTimestamps = batchRequestCache.set({ 146 | context, 147 | fieldIdentity, 148 | newTimestamps, 149 | }); 150 | 151 | // Fetch timestamps from previous requests out of the store 152 | // and get all the timestamps that haven't expired 153 | const filteredAccessTimestamps = ( 154 | await store.getForIdentity(identity) 155 | ).filter((t) => { 156 | return t + windowMs > Date.now(); 157 | }); 158 | 159 | // Flag indicating requests limit reached 160 | const limitReached = 161 | filteredAccessTimestamps.length + batchedTimestamps.length > maxCalls; 162 | 163 | // Confogure access timestamps to save according to uncountRejected setting 164 | const timestampsToStore: readonly any[] = [ 165 | ...filteredAccessTimestamps, 166 | ...(!uncountRejected || !limitReached ? batchedTimestamps : []), 167 | ]; 168 | 169 | // Save these access timestamps for future requests. 170 | if (!readOnly) { 171 | await store.setForIdentity(identity, timestampsToStore, windowMs); 172 | } 173 | 174 | // Field level custom message or a global formatting function 175 | const errorMessage = 176 | message || 177 | formatError({ 178 | contextIdentity, 179 | fieldIdentity, 180 | fieldName: info.fieldName, 181 | max: maxCalls, 182 | window: windowMs, 183 | }); 184 | 185 | // Returns an error message or undefined if no error 186 | return limitReached ? errorMessage : undefined; 187 | }; 188 | 189 | return rateLimiter; 190 | }; 191 | 192 | export { getGraphQLRateLimiter, getFieldIdentity }; 193 | -------------------------------------------------------------------------------- /src/lib/in-memory-store.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { InMemoryStore } from './in-memory-store'; 3 | 4 | test('InMemoryStore sets correct timestamps', (t) => { 5 | const store = new InMemoryStore(); 6 | store.setForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar' }, [ 7 | 1, 8 | 2, 9 | 3, 10 | ]); 11 | t.deepEqual(store.state, { foo: { bar: [1, 2, 3] } }); 12 | 13 | store.setForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar2' }, [ 14 | 4, 15 | 5, 16 | ]); 17 | t.deepEqual(store.state, { foo: { bar: [1, 2, 3], bar2: [4, 5] } }); 18 | 19 | store.setForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar' }, [ 20 | 10, 21 | 20, 22 | ]); 23 | t.deepEqual(store.state, { foo: { bar: [10, 20], bar2: [4, 5] } }); 24 | }); 25 | 26 | test('InMemoryStore get correct timestamps', (t) => { 27 | const store = new InMemoryStore(); 28 | store.setForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar' }, [ 29 | 1, 30 | 2, 31 | 3, 32 | ]); 33 | t.deepEqual( 34 | store.getForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar' }), 35 | [1, 2, 3] 36 | ); 37 | 38 | store.setForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar2' }, [ 39 | 4, 40 | 5, 41 | ]); 42 | t.deepEqual( 43 | store.getForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar2' }), 44 | [4, 5] 45 | ); 46 | 47 | store.setForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar' }, [ 48 | 10, 49 | 20, 50 | ]); 51 | t.deepEqual( 52 | store.getForIdentity({ contextIdentity: 'foo', fieldIdentity: 'bar' }), 53 | [10, 20] 54 | ); 55 | }); 56 | -------------------------------------------------------------------------------- /src/lib/in-memory-store.ts: -------------------------------------------------------------------------------- 1 | import { Store } from './store'; 2 | import { Identity } from './types'; 3 | 4 | interface StoreData { 5 | // Object of fields identified by the field name + potentially args. 6 | readonly [identity: string]: { 7 | // Array of calls for a given field identity 8 | readonly [fieldIdentity: string]: readonly number[]; 9 | }; 10 | } 11 | 12 | class InMemoryStore implements Store { 13 | // The store is mutable. 14 | // tslint:disable-next-line readonly-keyword 15 | public state: StoreData = {}; 16 | 17 | public setForIdentity( 18 | identity: Identity, 19 | timestamps: readonly number[] 20 | ): void { 21 | // tslint:disable-next-line no-object-mutation 22 | this.state = { 23 | ...(this.state || {}), 24 | [identity.contextIdentity]: { 25 | ...(this.state[identity.contextIdentity] || {}), 26 | [identity.fieldIdentity]: [...timestamps], 27 | }, 28 | }; 29 | } 30 | 31 | public getForIdentity(identity: Identity): readonly number[] { 32 | const ctxState = this.state[identity.contextIdentity]; 33 | return (ctxState && ctxState[identity.fieldIdentity]) || []; 34 | } 35 | } 36 | 37 | export { InMemoryStore }; 38 | -------------------------------------------------------------------------------- /src/lib/integration.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { makeExecutableSchema } from '@graphql-tools/schema'; 3 | import { graphql } from 'graphql'; 4 | import { applyMiddleware } from 'graphql-middleware'; 5 | import { shield } from 'graphql-shield'; 6 | import { createRateLimitDirective } from './field-directive'; 7 | import { createRateLimitRule } from './rate-limit-shield-rule'; 8 | import { getGraphQLRateLimiter } from './get-graphql-rate-limiter'; 9 | 10 | test('rate limit with schema directive', async (t) => { 11 | const directive = createRateLimitDirective({ 12 | identifyContext: (ctx) => ctx.id, 13 | }); 14 | const schema = makeExecutableSchema({ 15 | schemaDirectives: { 16 | rateLimit: directive, 17 | }, 18 | resolvers: { 19 | Query: { 20 | test: () => 'Result', 21 | }, 22 | }, 23 | typeDefs: ` 24 | directive @rateLimit( 25 | message: String 26 | identityArgs: [String] 27 | arrayLengthField: String 28 | max: Int 29 | window: String 30 | ) on FIELD_DEFINITION 31 | 32 | type Query { test: String! @rateLimit(max: 1, window: "1s") } 33 | `, 34 | }); 35 | const { data } = await graphql(schema, 'query { test }', {}, { id: '1' }); 36 | t.deepEqual(data, { test: 'Result' }); 37 | const { data: data2, errors } = await graphql( 38 | schema, 39 | 'query { test }', 40 | {}, 41 | { id: '1' } 42 | ); 43 | t.falsy(data2); 44 | t.truthy(errors); 45 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 46 | t.is(errors!.length, 1); 47 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 48 | const [error] = errors!; 49 | t.is(error.message, "You are trying to access 'test' too often"); 50 | 51 | const { data: data3 } = await graphql(schema, 'query{test}', {}, { id: '2' }); 52 | t.deepEqual(data3, { test: 'Result' }); 53 | }); 54 | 55 | test('batch query should error when rate limit exceeded', async (t) => { 56 | const rule = createRateLimitRule({ 57 | identifyContext: (ctx) => ctx.id, 58 | enableBatchRequestCache: true, 59 | }); 60 | const schema = applyMiddleware( 61 | makeExecutableSchema({ 62 | resolvers: { Query: { test: () => 'Result' } }, 63 | typeDefs: 'type Query { test: String! }', 64 | }), 65 | shield( 66 | { Query: { test: rule({ max: 1, window: '1s' }) } }, 67 | { allowExternalErrors: true } 68 | ) 69 | ); 70 | const { data } = await graphql( 71 | schema, 72 | 'query { test otherTest: test otherOtherTest: test }', 73 | {}, 74 | { id: '1' } 75 | ); 76 | t.is(data, null); 77 | }); 78 | 79 | test('batch query should succeed when within rate limit', async (t) => { 80 | const rule = createRateLimitRule({ 81 | identifyContext: (ctx) => ctx.id, 82 | enableBatchRequestCache: true, 83 | }); 84 | const schema = applyMiddleware( 85 | makeExecutableSchema({ 86 | resolvers: { Query: { test: () => 'Result' } }, 87 | typeDefs: 'type Query { test: String! }', 88 | }), 89 | shield( 90 | { Query: { test: rule({ max: 5, window: '1s' }) } }, 91 | { allowExternalErrors: true } 92 | ) 93 | ); 94 | const { data } = await graphql( 95 | schema, 96 | 'query { test otherTest: test otherOtherTest: test }', 97 | {}, 98 | { id: '1' } 99 | ); 100 | t.deepEqual(data, { 101 | test: 'Result', 102 | otherTest: 'Result', 103 | otherOtherTest: 'Result', 104 | }); 105 | }); 106 | 107 | test('rate limit with graphql shield', async (t) => { 108 | const rule = createRateLimitRule({ 109 | identifyContext: (ctx) => ctx.id, 110 | }); 111 | const schema = applyMiddleware( 112 | makeExecutableSchema({ 113 | resolvers: { Query: { test: () => 'Result' } }, 114 | typeDefs: 'type Query { test: String! }', 115 | }), 116 | shield({ Query: { test: rule({ max: 1, window: '1s' }) } }) 117 | ); 118 | 119 | const res = await graphql(schema, 'query { test }', {}, { id: '1' }); 120 | t.deepEqual(res.data, { test: 'Result' }); 121 | 122 | const res2 = await graphql(schema, 'query { test }', {}, { id: '1' }); 123 | t.falsy(res2.data); 124 | t.truthy(res2.errors); 125 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 126 | t.is(res2.errors!.length, 1); 127 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 128 | const [error] = res2.errors!; 129 | t.is(error.message, "You are trying to access 'test' too often"); 130 | 131 | const res3 = await graphql(schema, 'query{test}', {}, { id: '2' }); 132 | t.deepEqual(res3.data, { test: 'Result' }); 133 | }); 134 | 135 | test('rate limit with base rate limiter', async (t) => { 136 | const rateLimiter = getGraphQLRateLimiter({ 137 | identifyContext: (ctx) => ctx.id, 138 | }); 139 | const schema = makeExecutableSchema({ 140 | resolvers: { 141 | Query: { 142 | test: async (parent, args, context, info) => { 143 | const errorMessage = await rateLimiter( 144 | { parent, args, context, info }, 145 | { max: 1, window: '1s' } 146 | ); 147 | if (errorMessage) throw new Error(errorMessage); 148 | return 'Result'; 149 | }, 150 | }, 151 | }, 152 | typeDefs: 'type Query { test: String! }', 153 | }); 154 | 155 | const res = await graphql(schema, 'query { test }', {}, { id: '1' }); 156 | t.deepEqual(res.data, { test: 'Result' }); 157 | 158 | const res2 = await graphql(schema, 'query { test }', {}, { id: '1' }); 159 | t.falsy(res2.data); 160 | t.truthy(res2.errors); 161 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 162 | t.is(res2.errors!.length, 1); 163 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 164 | const [error] = res2.errors!; 165 | t.is(error.message, "You are trying to access 'test' too often"); 166 | 167 | const res3 = await graphql(schema, 'query{test}', {}, { id: '2' }); 168 | t.deepEqual(res3.data, { test: 'Result' }); 169 | }); 170 | -------------------------------------------------------------------------------- /src/lib/rate-limit-error.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { RateLimitError } from './rate-limit-error'; 3 | 4 | test('RateLimitError is an Error', (t) => { 5 | t.true(new RateLimitError('Some message') instanceof Error); 6 | }); 7 | 8 | test('RateLimitError.isRateLimitError is true', (t) => { 9 | t.true(new RateLimitError('Some message').isRateLimitError); 10 | }); 11 | -------------------------------------------------------------------------------- /src/lib/rate-limit-error.ts: -------------------------------------------------------------------------------- 1 | class RateLimitError extends Error { 2 | public readonly isRateLimitError = true; 3 | 4 | public constructor(message: string) { 5 | super(message); 6 | Object.setPrototypeOf(this, RateLimitError.prototype); 7 | } 8 | } 9 | 10 | export { RateLimitError }; 11 | -------------------------------------------------------------------------------- /src/lib/rate-limit-shield-rule.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { createRateLimitRule } from './rate-limit-shield-rule'; 3 | 4 | test('createRateLimitRule', (t) => { 5 | const rule = createRateLimitRule({ 6 | identifyContext: (ctx) => ctx.id, 7 | }); 8 | t.true(typeof rule === 'function'); 9 | const fieldRule = rule({ max: 1, window: '1s' }); 10 | t.true((fieldRule as any).cache === 'no_cache'); 11 | }); 12 | -------------------------------------------------------------------------------- /src/lib/rate-limit-shield-rule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql'; 2 | import { rule, IRule } from 'graphql-shield'; 3 | import { GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs } from './types'; 4 | import { getGraphQLRateLimiter } from './get-graphql-rate-limiter'; 5 | import { RateLimitError } from './rate-limit-error'; 6 | 7 | /** 8 | * Creates a graphql-shield rate limit rule. e.g. 9 | * 10 | * ```js 11 | * const rateLimit = createRateLimitRule({ identifyContext: (ctx) => ctx.id }); 12 | * const permissions = shield({ Mutation: { signup: rateLimit({ window: '10s', max: 1 }) } }) 13 | * ``` 14 | */ 15 | const createRateLimitRule = ( 16 | config: GraphQLRateLimitConfig 17 | ): ((fieldConfig: GraphQLRateLimitDirectiveArgs) => IRule) => { 18 | const rateLimiter = getGraphQLRateLimiter(config); 19 | 20 | return (fieldConfig: GraphQLRateLimitDirectiveArgs) => 21 | rule({ cache: 'no_cache' })(async (parent, args, context, info) => { 22 | const errorMessage = await rateLimiter( 23 | { 24 | parent, 25 | args, 26 | context, 27 | info: info as GraphQLResolveInfo, // I hope this is so. 28 | }, 29 | fieldConfig 30 | ); 31 | 32 | if (errorMessage) { 33 | return config.createError 34 | ? config.createError(errorMessage) 35 | : new RateLimitError(errorMessage); 36 | } 37 | 38 | return true; 39 | }); 40 | }; 41 | 42 | export { createRateLimitRule }; 43 | -------------------------------------------------------------------------------- /src/lib/redis-store.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import redis from 'redis-mock'; 3 | import { RedisStore } from './redis-store'; 4 | 5 | test('RedisStore sets and gets correct timestamps', async (t) => { 6 | const storeInstance = new RedisStore(redis.createClient()); 7 | 8 | await storeInstance.setForIdentity( 9 | { contextIdentity: 'foo', fieldIdentity: 'bar' }, 10 | [1, 2, 3] 11 | ); 12 | t.deepEqual( 13 | await storeInstance.getForIdentity({ 14 | contextIdentity: 'foo', 15 | fieldIdentity: 'bar', 16 | }), 17 | [1, 2, 3] 18 | ); 19 | 20 | await storeInstance.setForIdentity( 21 | { contextIdentity: 'foo', fieldIdentity: 'bar2' }, 22 | [4, 5] 23 | ); 24 | t.deepEqual( 25 | await storeInstance.getForIdentity({ 26 | contextIdentity: 'foo', 27 | fieldIdentity: 'bar2', 28 | }), 29 | [4, 5] 30 | ); 31 | 32 | await storeInstance.setForIdentity( 33 | { contextIdentity: 'foo', fieldIdentity: 'bar' }, 34 | [10, 20] 35 | ); 36 | t.deepEqual( 37 | await storeInstance.getForIdentity({ 38 | contextIdentity: 'foo', 39 | fieldIdentity: 'bar', 40 | }), 41 | [10, 20] 42 | ); 43 | }); 44 | -------------------------------------------------------------------------------- /src/lib/redis-store.ts: -------------------------------------------------------------------------------- 1 | import { Store } from './store'; 2 | import { Identity } from './types'; 3 | 4 | class RedisStore implements Store { 5 | public store: any; 6 | 7 | private readonly nameSpacedKeyPrefix: string = 'redis-store-id::'; 8 | 9 | public constructor(redisStoreInstance: unknown) { 10 | this.store = redisStoreInstance; 11 | } 12 | 13 | public setForIdentity( 14 | identity: Identity, 15 | timestamps: readonly number[], 16 | windowMs?: number 17 | ): Promise { 18 | return new Promise((res, rej): void => { 19 | const expiry = windowMs 20 | ? [ 21 | 'EX', 22 | Math.ceil((Date.now() + windowMs - Math.max(...timestamps)) / 1000), 23 | ] 24 | : []; 25 | this.store.set( 26 | [ 27 | this.generateNamedSpacedKey(identity), 28 | JSON.stringify([...timestamps]), 29 | ...expiry, 30 | ], 31 | (err: Error | null): void => { 32 | if (err) return rej(err); 33 | return res(); 34 | } 35 | ); 36 | }); 37 | } 38 | 39 | public async getForIdentity(identity: Identity): Promise { 40 | return new Promise((res, rej): void => { 41 | this.store.get( 42 | this.generateNamedSpacedKey(identity), 43 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 44 | (err: Error | null, obj: any): void => { 45 | if (err) { 46 | return rej(err); 47 | } 48 | return res(obj ? JSON.parse(obj) : []); 49 | } 50 | ); 51 | }); 52 | } 53 | 54 | private readonly generateNamedSpacedKey = (identity: Identity): string => { 55 | return `${this.nameSpacedKeyPrefix}${identity.contextIdentity}:${identity.fieldIdentity}`; 56 | }; 57 | } 58 | 59 | export { RedisStore }; 60 | -------------------------------------------------------------------------------- /src/lib/store.ts: -------------------------------------------------------------------------------- 1 | import { Identity } from './types'; 2 | 3 | abstract class Store { 4 | /** 5 | * Sets an array of call timestamps in the store for a given identity 6 | * 7 | * @param identity 8 | * @param timestamps 9 | */ 10 | public abstract setForIdentity( 11 | identity: Identity, 12 | timestamps: readonly number[], 13 | windowMs?: number 14 | ): void | Promise; 15 | 16 | /** 17 | * Gets an array of call timestamps for a given identity. 18 | * 19 | * @param identity 20 | */ 21 | public abstract getForIdentity( 22 | identity: Identity 23 | ): readonly number[] | Promise; 24 | } 25 | 26 | export { Store }; 27 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { Store } from './store'; 2 | 3 | /** 4 | * Two keys that define the identity for the call to a given 5 | * field resolver with a given context. 6 | */ 7 | export interface Identity { 8 | /** 9 | * The return value from `identifyContext` 10 | */ 11 | readonly contextIdentity: string; 12 | /** 13 | * Returns value from `getFieldIdentity` 14 | */ 15 | readonly fieldIdentity: string; 16 | } 17 | 18 | /** 19 | * 20 | */ 21 | export interface Options { 22 | readonly windowMs: number; 23 | readonly max: number; 24 | readonly callCount?: number; 25 | } 26 | 27 | /** 28 | * GraphQLRateLimitDirectiveArgs: The directive parameters. 29 | * 30 | * myField(id: String): Field @rateLimit(message: "Stop!", window: 100000, max: 10, identityArgs: ["id"]) 31 | */ 32 | export interface GraphQLRateLimitDirectiveArgs { 33 | /** 34 | * Error message used when/if the RateLimit error is thrown 35 | */ 36 | readonly message?: string; 37 | /** 38 | * Window duration in millis. 39 | */ 40 | readonly window?: string; 41 | /** 42 | * Max number of calls within the `window` duration. 43 | */ 44 | readonly max?: number; 45 | /** 46 | * Values to build into the key used to identify the resolve call. 47 | */ 48 | readonly identityArgs?: readonly string[]; 49 | /** 50 | * Limit by the length of an input array 51 | */ 52 | readonly arrayLengthField?: string; 53 | /** 54 | * Prevents registering the current request to the store. 55 | * This can be useful for example when you only wanna rate limit on failed attempts. 56 | */ 57 | readonly readOnly?: boolean; 58 | /** 59 | * Prevents rejected requests (due to limit reach) from being counted. 60 | */ 61 | readonly uncountRejected?: boolean; 62 | } 63 | 64 | /** 65 | * Args passed to the format error callback. 66 | */ 67 | export interface FormatErrorInput { 68 | readonly fieldName: string; 69 | readonly contextIdentity: string; 70 | readonly max: number; 71 | readonly window: number; 72 | readonly fieldIdentity?: string; 73 | } 74 | 75 | /** 76 | * Config object type passed to the directive factory. 77 | */ 78 | export interface GraphQLRateLimitConfig { 79 | /** 80 | * Provide a store to hold info on client requests. 81 | * 82 | * Defaults to an inmemory store if not provided. 83 | */ 84 | readonly store?: Store; 85 | /** 86 | * Return a string to identify the user or client. 87 | * 88 | * Example: 89 | * identifyContext: (context) => context.user.id; 90 | * identifyContext: (context) => context.req.ip; 91 | */ 92 | readonly identifyContext?: (context: any) => string; 93 | /** 94 | * Custom error messages. 95 | */ 96 | readonly formatError?: (input: FormatErrorInput) => string; 97 | /** 98 | * Return an error. 99 | * 100 | * Defaults to new RateLimitError. 101 | */ 102 | readonly createError?: (message: string) => Error; 103 | 104 | readonly enableBatchRequestCache?: boolean; 105 | } 106 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "outDir": "dist", 5 | "rootDir": "src", 6 | "moduleResolution": "node", 7 | "module": "commonjs", 8 | "declaration": true, 9 | "inlineSourceMap": true, 10 | "esModuleInterop": true, 11 | 12 | "strict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | 18 | "traceResolution": false, 19 | "listEmittedFiles": false, 20 | "listFiles": false, 21 | "pretty": true, 22 | 23 | "lib": [], 24 | "types": ["node"], 25 | "typeRoots": ["node_modules/@types", "src/types"] 26 | }, 27 | "include": ["src/**/*.ts"], 28 | "exclude": ["node_modules/**"], 29 | "compileOnSave": false 30 | } 31 | --------------------------------------------------------------------------------