Skip to content

Commit

Permalink
Refactor Rate Limit plugin (ardatan#2292)
Browse files Browse the repository at this point in the history
* Refactor Rate Limit plugin

* chore(dependencies): updated changesets for modified dependencies

* Ignore changesets

* ..

* Fix field identity support

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ardatan and github-actions[bot] authored Aug 20, 2024
1 parent ba368ba commit c3dd2c3
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 193 deletions.
7 changes: 7 additions & 0 deletions .changeset/@envelop_rate-limiter-2292-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@envelop/rate-limiter": patch
---
dependencies updates:
- Updated dependency [`graphql-rate-limit@^3.3.0` ↗︎](https://www.npmjs.com/package/graphql-rate-limit/v/3.3.0) (from `3.3.0`, in `dependencies`)
- Added dependency [`@graphql-tools/utils@^10.5.4` ↗︎](https://www.npmjs.com/package/@graphql-tools/utils/v/10.5.4) (to `dependencies`)
- Added dependency [`minimatch@^10.0.1` ↗︎](https://www.npmjs.com/package/minimatch/v/10.0.1) (to `dependencies`)
26 changes: 26 additions & 0 deletions .changeset/chilled-shirts-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@envelop/rate-limiter': minor
---

Now you can define a custom string interpolation function to be used in the rate limit message. This
is useful when you want to include dynamic values in the message.

```ts
useRateLimiter({
configByField: [
{
type: 'Query',
field: 'search', // You can also use glob patterns
max: 10,
window: '1m',
message:
'My custom message with interpolated values: ${args.searchTerm} and ${context.user.id}'
}
],
interpolateMessage: (message, args, context) => {
return message.replace(/\${(.*?)}/g, (_, key) => {
return key.split('.').reduce((acc, part) => acc[part], { args, context })
})
}
})
```
5 changes: 5 additions & 0 deletions .changeset/early-deers-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@envelop/on-resolve': patch
---

Refactor the plugin to avoid extra promises with \`mapMaybePromise\`
17 changes: 17 additions & 0 deletions .changeset/fuzzy-donkeys-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@envelop/rate-limiter': minor
---

New directive SDL;

```graphql
directive @rateLimit(
max: Int
window: String
message: String
identityArgs: [String]
arrayLengthField: String
readOnly: Boolean
uncountRejected: Boolean
) on FIELD_DEFINITION
```
18 changes: 18 additions & 0 deletions .changeset/olive-rings-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@envelop/rate-limiter': minor
---

Programmatic API to define rate limit configuration in addition to directives

```ts
useRateLimiter({
configByField: [
{
type: 'Query',
field: 'search', // You can also use glob patterns
max: 10,
window: '1m'
}
]
})
```
5 changes: 5 additions & 0 deletions .changeset/sharp-spoons-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@envelop/core': patch
---

Export `mapMaybePromise` and `isPromise`
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ examples/sveltekit
.next
pnpm-lock.yaml
.husky
.changeset/
29 changes: 29 additions & 0 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,32 @@ export function errorAsyncIterator<TInput>(

return stream;
}

export function isPromise<T>(value: any): value is Promise<T> {
return value?.then !== undefined;
}

export function mapMaybePromise<T, R>(
value: PromiseOrValue<T>,
mapper: (v: T) => PromiseOrValue<R>,
errorMapper?: (e: any) => PromiseOrValue<R>,
): PromiseOrValue<R> {
if (isPromise(value)) {
if (errorMapper) {
try {
return value.then(mapper, errorMapper);
} catch (e) {
return errorMapper(e);
}
}
return value.then(mapper);
}
if (errorMapper) {
try {
return mapper(value);
} catch (e) {
return errorMapper(e);
}
}
return mapper(value);
}
89 changes: 56 additions & 33 deletions packages/plugins/on-resolve/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
isIntrospectionType,
isObjectType,
} from 'graphql';
import { Plugin, PromiseOrValue } from '@envelop/core';
import { mapMaybePromise, Plugin, PromiseOrValue } from '@envelop/core';

export type Resolver<Context = unknown> = (
root: unknown,
Expand Down Expand Up @@ -63,39 +63,62 @@ export function useOnResolve<PluginContext extends Record<string, any> = {}>(

let resolver = (field.resolve || defaultFieldResolver) as Resolver<PluginContext>;

field.resolve = async (root, args, context, info) => {
const afterResolve = await onResolve({
root,
args,
context,
info,
resolver,
replaceResolver: newResolver => {
resolver = newResolver;
},
});

let result;
try {
result = await resolver(root, args, context, info);
} catch (err) {
result = err as Error;
}

if (typeof afterResolve === 'function') {
await afterResolve({
result,
setResult: newResult => {
result = newResult;
field.resolve = (root, args, context, info) =>
mapMaybePromise(
onResolve({
root,
args,
context,
info,
resolver,
replaceResolver: newResolver => {
resolver = newResolver;
},
});
}

if (result instanceof Error) {
throw result;
}
return result;
};
}),
afterResolve => {
if (typeof afterResolve === 'function') {
try {
return mapMaybePromise(
resolver(root, args, context, info),
result =>
mapMaybePromise(
afterResolve({
result,
setResult: newResult => {
result = newResult;
},
}),
() => result,
),
errorResult =>
mapMaybePromise(
afterResolve({
result: errorResult,
setResult: newResult => {
errorResult = newResult;
},
}),
() => {
throw errorResult;
},
),
);
} catch (err) {
let errorResult = err;
return mapMaybePromise(
afterResolve({
result: errorResult,
setResult: newResult => {
errorResult = newResult;
},
}),
() => errorResult,
);
}
}
return resolver(root, args, context, info);
},
);

field[hasWrappedResolveSymbol] = true;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/plugins/rate-limiter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
},
"dependencies": {
"@envelop/on-resolve": "^4.1.0",
"graphql-rate-limit": "3.3.0",
"@graphql-tools/utils": "^10.5.4",
"graphql-rate-limit": "^3.3.0",
"minimatch": "^10.0.1",
"tslib": "^2.5.0"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit c3dd2c3

Please sign in to comment.