Skip to content

Commit 2a550ca

Browse files
authored
Merge pull request #103 from oslabs-beta/dev
v1.3.0 deployment
2 parents 1229cd0 + f923041 commit 2a550ca

20 files changed

+9527
-4768
lines changed

.eslintignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
node_modules
1+
node_modules
2+
dist

.eslintrc.json

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,22 @@
1717
"ecmaVersion": "latest",
1818
"project": "./tsconfig.json"
1919
},
20-
"plugins": ["import", "prettier"],
20+
"plugins": [
21+
"import", "prettier"
22+
],
2123
"rules": {
22-
"no-plusplus": [2, {
23-
"allowForLoopAfterthoughts": true
24-
}],
24+
"no-plusplus": [
25+
2,
26+
{
27+
"allowForLoopAfterthoughts": true
28+
}
29+
],
2530
"prettier/prettier": [
2631
"error"
2732
]
28-
},
29-
"ignorePatterns": ["jest.*", "build/*"]
30-
}
33+
},
34+
"ignorePatterns": [
35+
"jest.*",
36+
"dist/*"
37+
]
38+
}

.npmignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
**/*
2+
dist/src/utils/__mock__
3+
dist/test/*
4+
!dist/src/analysis/*
5+
!dist/src/middleware/*
6+
!dist/src/rateLimiters/*
7+
!dist/src/utils/*
8+
!dist/src/*
9+
!package.json

.travis.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ script:
1515
- 'npm run lint'
1616
- 'npm run test'
1717
- 'npm run build'
18-
# specify a job to run
18+
19+
# specify deployment
20+
before_deploy:
21+
- 'npm run build'
22+
- 'npm run build:fix'
23+
1924
deploy:
2025
on:
2126
branch: main
22-
tags: true
23-
skip_cleanup: false
27+
tags: false
28+
skip_cleanup: true
2429
provider: npm
2530
email: $NPM_EMAIL_ADDRESS
2631
api_key: $NPM_API_KEY

README.md

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
</div>
88

99
&nbsp;
10+
## Summary
11+
12+
Developed under tech-accelerator [OSLabs](https://opensourcelabs.io/), GraphQLGate strives for a principled approach to complexity analysis and rate-limiting for GraphQL queries by accurately estimating an upper-bound of the response size of the query. Within a loosely opinionated framework with lots of configuration options, you can reliably throttle GraphQL queries by complexity and depth to protect your GraphQL API. Our solution is inspired by [this paper](https://github.com/Alan-Cha/fse20/blob/master/submissions/functional/FSE-24/graphql-paper.pdf) from IBM research teams.
1013

1114
## Table of Contents
1215

@@ -16,6 +19,7 @@
1619
- [How It Works](#how-it-works)
1720
- [Response](#response)
1821
- [Error Handling](#error-handling)
22+
- [Internals](#internals)
1923
- [Future Development](#future-development)
2024
- [Contributions](#contributions)
2125
- [Developers](#developers)
@@ -35,7 +39,7 @@ NOTE: a Redis server instance will need to be started in order for the limiter t
3539

3640
```javascript
3741
// import package
38-
import expressGraphQLRateLimiter from 'graphql-limiter';
42+
import { expressGraphQLRateLimiter } from 'graphql-limiter';
3943

4044
/**
4145
* Import other dependencies
@@ -135,7 +139,7 @@ type Query {
135139

136140
Requests are rate-limited based on the IP address associated with the request.
137141

138-
On startup, the GraphQL (GQL) schema is parsed to build an object that maps GQL types/fields to their corresponding weights. Type weights can be provided during <a href="typeWeights">initial configuration</a>. When a request is received, this object is used to cross reference the fields queried by the user and compute the complexity of each field. The total complexity of the request is the sum of these values.
142+
On startup, the GraphQL (GQL) schema is parsed to build an object that maps GQL types/fields to their corresponding weights. Type weights can be provided during <a href="#typeWeights">initial configuration</a>. When a request is received, this object is used to cross reference the fields queried by the user and compute the complexity of each field. The total complexity of the request is the sum of these values.
139143

140144
Complexity is determined, statically (before any resolvers are called) to estimate the upper bound of the response size - a proxy for the work done by the server to build the response. The total complexity is then used to allow/block the request based on popular rate-limiting algorithms.
141145

@@ -176,7 +180,7 @@ query {
176180

177181
```javascript
178182
{
179-
graphglGate: {
183+
graphqlGate: {
180184
success: boolean, // true when successful
181185
tokens: number, // tokens available after request
182186
compexity: number, // complexity of the query
@@ -191,6 +195,85 @@ query {
191195
- Incoming queries are validated against the GraphQL schema. If the query is invalid, a response with status code `400` is returned along with an array of GraphQL Errors that were found.
192196
- To avoid disrupting server activity, errors thrown during the analysis and rate-limiting of the query are logged and the request is passed onto the next piece of middleware in the chain.
193197

198+
## <a name="internals"></a> Internals
199+
200+
This package exposes 3 additional functionalities which comprise the internals of the package. This is a breif documentaion on them.
201+
202+
### Complexity Analysis
203+
204+
1. #### `typeWeightsFromSchema` | function to create the type weight object from the schema for complexity analysis
205+
206+
- `schema: GraphQLSchema` | GraphQL schema object
207+
- `typeWeightsConfig: TypeWeightConfig = defaultTypeWeightsConfig` | type weight configuration
208+
- `enforceBoundedLists = false`
209+
- returns: `TypeWeightObject`
210+
- usage:
211+
212+
```ts
213+
import { typeWeightsFromSchema } from 'graphql-limiter';
214+
import { GraphQLSchema } from 'graphql/type/schema';
215+
import { buildSchema } from 'graphql';
216+
217+
let schema: GraphQLSchema = buildSchema(`...`);
218+
219+
const typeWeights: TypeWeightObject = typeWeightsFromSchema(schema);
220+
```
221+
222+
2. #### `QueryParser` | class to calculate the complexity of the query based on the type weights and variables
223+
224+
- `typeWeights: TypeWeightObject`
225+
- `variables: Variables` | variables on request
226+
- returns a class with method:
227+
228+
- `processQuery(queryAST: DocumentNode): number`
229+
- returns: complexity of the query and exposes `maxDepth` property for depth limiting
230+
231+
```ts
232+
import { typeWeightsFromSchema } from 'graphql-limiter';
233+
import { parse, validate } from 'graphql';
234+
235+
let queryAST: DocumentNode = parse(`...`);
236+
237+
const queryParser: QueryParser = new QueryParser(typeWeights, variables);
238+
239+
// query must be validatied against the schema before processing the query
240+
const validationErrors = validate(schema, queryAST);
241+
242+
const complexity: number = queryParser.processQuery(queryAST);
243+
```
244+
245+
### Rate-limiting
246+
247+
3. #### `rateLimiter` | returns a rate limiting class instance based on selections
248+
249+
- `rateLimiter: RateLimiterConfig` | see "configuration" -> rateLimiter
250+
- `client: Redis` | an ioredis client
251+
- `keyExpiry: number` | time (ms) for key to persist in cache
252+
- returns a rate limiter class with method:
253+
254+
- `processRequest(uuid: string, timestamp: number, tokens = 1): Promise<RateLimiterResponse>`
255+
- returns: `{ success: boolean, tokens: number, retryAfter?: number }` | where `tokens` is tokens available, `retryAfter` is time to wait in seconds before the request would be successful and `success` is false if the request is blocked
256+
257+
```ts
258+
import { rateLimiter } from 'graphql-limiter';
259+
260+
const limiter: RateLimiter = rateLimiter(
261+
{
262+
type: 'TOKEN_BUCKET',
263+
refillRate: 1,
264+
capacity: 10,
265+
},
266+
redisClient,
267+
86400000 // 24 hours
268+
);
269+
270+
const response: RateLimiterResponse = limiter.processRequest(
271+
'user-1',
272+
new Date().valueOf(),
273+
5
274+
);
275+
```
276+
194277
## <a name="future-development"></a> Future Development
195278

196279
- Ability to use this package with other caching technologies or libraries

0 commit comments

Comments
 (0)