Skip to content

Commit 4ea037f

Browse files
committed
exported types for succesful typescript build.
1 parent ece955b commit 4ea037f

File tree

13 files changed

+44
-32
lines changed

13 files changed

+44
-32
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
"error"
2727
]
2828
},
29-
"ignorePatterns": ["jest.*"]
29+
"ignorePatterns": ["jest.*", "build/*"]
3030
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,5 @@ dist
102102

103103
# TernJS port file
104104
.tern-port
105+
106+
build/*

src/@types/buildTypeWeights.d.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
interface Fields {
1+
export interface Fields {
22
[index: string]: FieldWeight;
33
}
4-
type WeightFunction = (args: ArgumentNode[]) => number;
5-
type FieldWeight = number | WeightFunction;
6-
7-
interface Type {
4+
export type WeightFunction = (args: ArgumentNode[]) => number;
5+
export type FieldWeight = number | WeightFunction;
6+
export interface Type {
87
readonly weight: number;
98
readonly fields: Fields;
109
}
11-
12-
interface TypeWeightObject {
10+
export interface TypeWeightObject {
1311
[index: string]: Type;
1412
}
15-
16-
interface TypeWeightConfig {
13+
export interface TypeWeightConfig {
1714
mutation?: number;
1815
query?: number;
1916
object?: number;

src/@types/rateLimit.d.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
interface RateLimiter {
1+
export interface RateLimiter {
22
/**
33
* Checks if a request is allowed under the given conditions and withdraws the specified number of tokens
44
* @param uuid Unique identifier for the user associated with the request
@@ -13,17 +13,17 @@ interface RateLimiter {
1313
) => Promise<RateLimiterResponse>;
1414
}
1515

16-
interface RateLimiterResponse {
16+
export interface RateLimiterResponse {
1717
success: boolean;
1818
tokens: number;
1919
}
2020

21-
interface RedisBucket {
21+
export interface RedisBucket {
2222
tokens: number;
2323
timestamp: number;
2424
}
2525

26-
type RateLimiterSelection =
26+
export type RateLimiterSelection =
2727
| 'TOKEN_BUCKET'
2828
| 'LEAKY_BUCKET'
2929
| 'FIXED_WINDOW'
@@ -34,12 +34,12 @@ type RateLimiterSelection =
3434
* @type {number} bucketSize - Size of the token bucket
3535
* @type {number} refillRate - Rate at which tokens are added to the bucket in seconds
3636
*/
37-
interface TokenBucketOptions {
37+
export interface TokenBucketOptions {
3838
bucketSize: number;
3939
refillRate: number;
4040
}
4141

4242
// TODO: This will be a union type where we can specify Option types for other Rate Limiters
4343
// Record<string, never> represents the empty object for alogorithms that don't require settings
4444
// and might be able to be removed in the future.
45-
type RateLimiterOptions = TokenBucketOptions | Record<string, never>;
45+
export type RateLimiterOptions = TokenBucketOptions | Record<string, never>;

src/analysis/ASTnodefunctions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
SelectionNode,
99
ArgumentNode,
1010
} from 'graphql';
11+
import { FieldWeight, TypeWeightObject } from '../@types/buildTypeWeights';
1112

1213
// TODO: handle variables and arguments
1314
// ! this is not functional

src/analysis/buildTypeWeights.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { Maybe } from 'graphql/jsutils/Maybe';
2020
import { ObjMap } from 'graphql/jsutils/ObjMap';
2121
import { GraphQLSchema } from 'graphql/type/schema';
22+
import { TypeWeightConfig, TypeWeightObject } from '../@types/buildTypeWeights';
2223

2324
export const KEYWORDS = ['first', 'last', 'limit'];
2425

@@ -81,6 +82,7 @@ function parseQuery(
8182
queryFields[field].args.forEach((arg: GraphQLArgument) => {
8283
// If query has an argument matching one of the limiting keywords and resolves to a list then the weight of the query
8384
// should be dependent on both the weight of the resolved type and the limiting argument.
85+
// FIXME: Can nonnull wrap list types?
8486
if (KEYWORDS.includes(arg.name) && isListType(resolveType)) {
8587
// Get the type that comprises the list
8688
const listType = resolveType.ofType;
@@ -125,7 +127,7 @@ function parseQuery(
125127
}
126128
});
127129

128-
// if the field is a scalar or an enum set weight accordingly
130+
// if the field is a scalar or an enum set weight accordingly. It is not a list in this case
129131
if (isScalarType(resolveType) || isEnumType(resolveType)) {
130132
result.query.fields[field] = typeWeights.scalar || DEFAULT_SCALAR_WEIGHT;
131133
}
@@ -152,7 +154,7 @@ function parseTypes(
152154

153155
// Handle Object, Interface, Enum and Union types
154156
Object.keys(typeMap).forEach((type) => {
155-
const typeName = type.toLowerCase();
157+
const typeName: string = type.toLowerCase();
156158

157159
const currentType: GraphQLNamedType = typeMap[type];
158160
// Get all types that aren't Query or Mutation or a built in type that starts with '__'
@@ -219,7 +221,7 @@ function buildTypeWeightsFromSchema(
219221
...typeWeightsConfig,
220222
};
221223

222-
// Confirm that any custom weights are positive
224+
// Confirm that any custom weights are non-negative
223225
Object.entries(typeWeights).forEach((value: [string, number]) => {
224226
if (value[1] < 0) {
225227
throw new Error(`Type weights cannot be negative. Received: ${value[0]}: ${value[1]} `);

src/analysis/typeComplexityAnalysis.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DocumentNode } from 'graphql';
2+
import { TypeWeightObject } from '../@types/buildTypeWeights';
23
import { documentNode } from './ASTnodefunctions';
34

45
/**

src/middleware/index.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { Request, Response, NextFunction, RequestHandler } from 'express';
66
import buildTypeWeightsFromSchema, { defaultTypeWeightsConfig } from '../analysis/buildTypeWeights';
77
import setupRateLimiter from './rateLimiterSetup';
88
import getQueryTypeComplexity from '../analysis/typeComplexityAnalysis';
9+
import { RateLimiterOptions, RateLimiterSelection } from '../@types/rateLimit';
10+
import { TypeWeightConfig } from '../@types/buildTypeWeights';
911

1012
// FIXME: Will the developer be responsible for first parsing the schema from a file?
1113
// Can consider accepting a string representing a the filepath to a schema
@@ -50,7 +52,6 @@ export function expressRateLimiter(
5052
console.log('There is no query on the request');
5153
return next();
5254
}
53-
5455
/**
5556
* There are numorous ways to get the ip address off of the request object.
5657
* - the header 'x-forward-for' will hold the originating ip address if a proxy is placed infront of the server. This would be commen for a production build.
@@ -68,10 +69,12 @@ export function expressRateLimiter(
6869
// validate the query against the schema. The GraphQL validation function returns an array of errors.
6970
const validationErrors = validate(schema, queryAST);
7071
// check if the length of the returned GraphQL Errors array is greater than zero. If it is, there were errors. Call next so that the GraphQL server can handle those.
71-
if (validationErrors.length > 0) return next();
72+
if (validationErrors.length > 0) {
73+
// FIXME: Customize this error to throw the GraphQLError
74+
return next(Error('invalid query'));
75+
}
7276

7377
const queryComplexity = getQueryTypeComplexity(queryAST, variables, typeWeightObject);
74-
7578
try {
7679
// process the request and conditinoally respond to client with status code 429 o
7780
// r pass the request onto the next middleware function
@@ -83,14 +86,15 @@ export function expressRateLimiter(
8386
if (rateLimiterResponse.success === false) {
8487
// TODO: add a header 'Retry-After' with the time to wait untill next query will succeed
8588
// FIXME: send information about query complexity, tokens, etc, to the client on rejected query
86-
res.status(429).send();
89+
res.status(429).json({ graphqlGate: rateLimiterResponse });
90+
} else {
91+
res.locals.graphqlGate = {
92+
timestamp: requestTimestamp,
93+
complexity: queryComplexity,
94+
tokens: rateLimiterResponse.tokens,
95+
};
96+
return next();
8797
}
88-
res.locals.graphqlGate = {
89-
timestamp: requestTimestamp,
90-
complexity: queryComplexity,
91-
tokens: rateLimiterResponse.tokens,
92-
};
93-
return next();
9498
} catch (err) {
9599
return next(err);
96100
}

src/middleware/rateLimiterSetup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Redis from 'ioredis';
2+
import { RateLimiterOptions, RateLimiterSelection } from '../@types/rateLimit';
23
import TokenBucket from '../rateLimiters/tokenBucket';
34

45
/**

src/rateLimiters/tokenBucket.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Redis from 'ioredis';
2+
import { RateLimiter, RateLimiterResponse, RedisBucket } from '../@types/rateLimit';
23

34
/**
45
* The TokenBucket instance of a RateLimiter limits requests based on a unique user ID.
@@ -98,7 +99,7 @@ class TokenBucket implements RateLimiter {
9899
timestamp: number
99100
): number => {
100101
const timeSinceLastQueryInSeconds: number = Math.floor(
101-
(timestamp - bucket.timestamp) / 1000 // 1000 ms in a second
102+
(timestamp - bucket.timestamp) / 1000 // 1000 ms in a second FIXME: magic number if specifying custom timeframe
102103
);
103104
const tokensToAdd = timeSinceLastQueryInSeconds * this.refillRate;
104105
const updatedTokenCount = bucket.tokens + tokensToAdd;

0 commit comments

Comments
 (0)