FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: rename passParams -> gateParams & rateLimit static values (#43) · feathersjs/feathers-utils@f0324fd · GitHub

Commit f0324fd

Browse files
authored
fix: rename passParams -> gateParams & rateLimit static values (#43)
1 parent e96faaa commit f0324fd

12 files changed

Lines changed: 141 additions & 75 deletions

‎src/hooks/cache/cache.hook.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ hook:
66
method: ['find', 'get', 'create', 'update', 'patch', 'remove']
77
multi: true
88
see:
9-
- utils/passParams
9+
- utils/gateParams
1010
---
1111

1212
The `cache` hook caches `get` and `find` results based on `params`. On mutating methods (`create`, `update`, `patch`, `remove`), affected cache entries are automatically invalidated.
@@ -21,16 +21,16 @@ The `cache` hook caches `get` and `find` results based on `params`. On mutating
2121
| ----------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2222
| `map` | `Cache` | The cache implementation. Must implement `get`, `set`, `delete`, `clear`, and `keys`. |
2323
| `id` | `string` | The id field to use. Defaults to `service.options.id`, then `'id'`. |
24-
| `transformParams` | `(params) => params` | Transform params before they are used as cache key. Compose it with [`passParams`](/utils/pass-params) to declaratively pick/drop keys and avoid false hits — see [Choosing Cache-Relevant Params](#choosing-cache-relevant-params-with-passparams). |
24+
| `transformParams` | `(params) => params` | Transform params before they are used as cache key. Compose it with [`gateParams`](/utils/gate-params) to declaratively pick/drop keys and avoid false hits — see [Choosing Cache-Relevant Params](#choosing-cache-relevant-params-with-gateparams). |
2525

26-
## Choosing Cache-Relevant Params (with `passParams`)
26+
## Choosing Cache-Relevant Params (with `gateParams`)
2727

2828
Deciding which `params` keys form the cache key is the trickiest part of caching, and the two failure modes are asymmetric:
2929

3030
- **False hits (dangerous):** if a key that affects the result is left out (e.g. `user`/tenant, `provider`), two semantically different requests collapse to the same key — one user can be served another user's cached data.
3131
- **False misses (wasteful):** if a per-request/metrics key is included (e.g. `rateLimit`), every request produces a unique key and the cache never hits. A function-valued key (e.g. `stashed` from `stashable`) would even make serialization throw.
3232

33-
The [`passParams`](/utils/pass-params) utility makes this explicit and safe. It takes a declarative path schema (`true` include, `false` drop, or a predicate/projection function). `query` is always included by default, and keys you never classified are **kept by default** — the safe direction, since a forgotten key causes at worst a harmless cache miss, never a false hit.
33+
The [`gateParams`](/utils/gate-params) utility makes this explicit and safe. It takes a declarative path schema (`true` include, `false` drop, or a predicate/projection function). `query` is always included by default, and keys you never classified are **kept by default** — the safe direction, since a forgotten key causes at worst a harmless cache miss, never a false hit.
3434

3535
> Transient keys that feathers-utils' own hooks attach to `params``rateLimit` (`rateLimit`), `skipHooks` (`skippable`/`addSkip`), the `stashed` function and `_stashable` flag (`stashable`) — are never cache-relevant. Drop them with `false`, or keep only what you list via `dropUnknownParams: true`.
3636
@@ -39,12 +39,12 @@ The [`passParams`](/utils/pass-params) utility makes this explicit and safe. It
3939
Cache on everything except the keys you explicitly drop with `false`. This is the default direction — safe against false hits:
4040

4141
```ts
42-
import { passParams } from 'feathers-utils/utils'
42+
import { gateParams } from 'feathers-utils/utils'
4343

4444
cache({
4545
map: new Map(),
4646
transformParams: (params) =>
47-
passParams(params, { rateLimit: false, skipHooks: false }),
47+
gateParams(params, { rateLimit: false, skipHooks: false }),
4848
})
4949
```
5050

@@ -53,12 +53,12 @@ cache({
5353
Set `dropUnknownParams: true` so only `query` (always) and the listed paths form the cache key. `user.id` is picked via dot-notation so different tenants never collide and per-request `user` fields don't bloat the key. Use `onUnknownParams` to log anything that was dropped:
5454

5555
```ts
56-
import { passParams } from 'feathers-utils/utils'
56+
import { gateParams } from 'feathers-utils/utils'
5757

5858
cache({
5959
map: new Map(),
6060
transformParams: (params) =>
61-
passParams(
61+
gateParams(
6262
params,
6363
{ 'user.id': true }, // `query` is included automatically
6464
{

‎src/hooks/cache/cache.hook.test.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { TTLCache } from '@isaacs/ttlcache'
77
import { MemoryService } from '@feathersjs/memory'
88
import { expect, expectTypeOf } from 'vitest'
99
import { copy } from 'fast-copy'
10-
import { passParams } from '../../utils/pass-params/pass-params.util.js'
10+
import { gateParams } from '../../utils/gate-params/gate-params.util.js'
1111

1212
const setup = (options: CacheOptions, serviceOptions?: { id?: string }) => {
1313
const app = feathers<{
@@ -1149,12 +1149,12 @@ describe('cache hook as an around hook', () => {
11491149
})
11501150
})
11511151

1152-
describe('cache hook with passParams', () => {
1152+
describe('cache hook with gateParams', () => {
11531153
it('prevents false hits across users and collapses non-id user fields (whitelist)', async () => {
11541154
const { usersService, before } = setup({
11551155
map: new Map(),
11561156
// `query` is included by default; only `user.id` is added explicitly.
1157-
transformParams: (params) => passParams(params, { 'user.id': true }),
1157+
transformParams: (params) => gateParams(params, { 'user.id': true }),
11581158
})
11591159

11601160
await usersService.create({ id: 1, name: 'John' })
@@ -1185,7 +1185,7 @@ describe('cache hook with passParams', () => {
11851185
const { usersService, before } = setup({
11861186
map: new Map(),
11871187
// keep everything except the transient `rateLimit` metric.
1188-
transformParams: (params) => passParams(params, { rateLimit: false }),
1188+
transformParams: (params) => gateParams(params, { rateLimit: false }),
11891189
})
11901190

11911191
await usersService.create({ id: 1, name: 'John' })
@@ -1209,7 +1209,7 @@ describe('cache hook with passParams', () => {
12091209
map: new Map(),
12101210
// keep only `query` (default); `stashed` (a function) is dropped.
12111211
transformParams: (params) =>
1212-
passParams(params, {}, { dropUnknownParams: true }),
1212+
gateParams(params, {}, { dropUnknownParams: true }),
12131213
})
12141214

12151215
await usersService.create({ id: 1, name: 'John' })
@@ -1235,7 +1235,7 @@ describe('cache hook with passParams', () => {
12351235
const { usersService } = setup({
12361236
map: new Map(),
12371237
transformParams: (params) =>
1238-
passParams(params, { query: true }, { onUnknownParams }),
1238+
gateParams(params, { query: true }, { onUnknownParams }),
12391239
})
12401240

12411241
await usersService.create({ id: 1, name: 'John' })

‎src/hooks/cache/cache.hook.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ export type CacheOptions = {
3535
* There are params properties you don't want to include in the cache key.
3636
* You can use this function to transform the params before they are stringified.
3737
*
38-
* The {@link passParams} util is built for exactly this: it declaratively
38+
* The {@link gateParams} util is built for exactly this: it declaratively
3939
* selects/projects `params` keys (keeping `query` by default) so noise like
4040
* `rateLimit` never ends up in the cache key.
4141
*
4242
* @example
4343
* ```ts
44-
* import { passParams } from 'feathers-utils/utils'
44+
* import { gateParams } from 'feathers-utils/utils'
4545
*
4646
* cache({
4747
* map: new Map(),
48-
* transformParams: (params) => passParams(params, { rateLimit: false }),
48+
* transformParams: (params) => gateParams(params, { rateLimit: false }),
4949
* })
5050
* ```
5151
*/

‎src/hooks/rate-limit/rate-limit.hook.md‎

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
title: rateLimit
33
category: hooks
44
hook:
5-
type: ["before", "around"]
6-
method: ["find", "get", "create", "update", "patch", "remove"]
5+
type: ['before', 'around']
6+
method: ['find', 'get', 'create', 'update', 'patch', 'remove']
77
multi: true
88
---
99

@@ -13,10 +13,10 @@ Any rate limiter backend supported by `rate-limiter-flexible` can be used (Memor
1313

1414
## Options
1515

16-
| Option | Type | Description |
17-
| --- | --- | --- |
18-
| `key` | `(context) => string` | Generate the rate-limiting key. Defaults to `context.path`. |
19-
| `points` | `(context) => number` | Number of points to consume per request. Defaults to `1`. |
16+
| Option | Type | Description |
17+
| -------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18+
| `key` | `string \| ((context) => string)` | The rate-limiting key, or a function to derive it from the context. Defaults to `context.path`. Pass a static string for a single shared bucket (a global rate limit). |
19+
| `points` | `number \| ((context) => number)` | Number of points to consume per request, or a function to compute it from the context. Defaults to `1`. |
2020

2121
The `RateLimiterRes` is stored on `context.params.rateLimit` on both success and failure, so downstream hooks or services can inspect `remainingPoints`, `consumedPoints`, `msBeforeNext`, etc.
2222

@@ -58,16 +58,40 @@ app.service('messages').hooks({
5858
})
5959
```
6060

61+
### Global Rate Limit
62+
63+
Pass a static string as the `key` to share a single bucket across all requests — a global cap on an endpoint instead of one bucket per `context.path`:
64+
65+
```ts
66+
const rateLimiter = new RateLimiterMemory({ points: 1000, duration: 60 })
67+
68+
app.service('search').hooks({
69+
before: {
70+
find: [rateLimit(rateLimiter, { key: 'search' })],
71+
},
72+
})
73+
```
74+
6175
### Custom Points per Request
6276

63-
Use the `points` option to consume more points for expensive operations:
77+
Pass a static number to consume a fixed cost per request:
78+
79+
```ts
80+
app.service('reports').hooks({
81+
before: {
82+
find: [rateLimit(rateLimiter, { points: 5 })],
83+
},
84+
})
85+
```
86+
87+
Or pass a function to compute the cost from the context — e.g. to charge more for expensive queries:
6488

6589
```ts
6690
app.service('reports').hooks({
6791
before: {
6892
find: [
6993
rateLimit(rateLimiter, {
70-
points: (context) => context.params.query?.$limit > 100 ? 5 : 1,
94+
points: (context) => (context.params.query?.$limit > 100 ? 5 : 1),
7195
}),
7296
],
7397
},
@@ -128,4 +152,4 @@ app.service('users').hooks({
128152

129153
// Skip rate limiting for this call
130154
app.service('users').find({ skipHooks: ['rateLimit'] })
131-
```
155+
```

‎src/hooks/rate-limit/rate-limit.hook.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,37 @@ describe('hook - rateLimit', () => {
9393
)
9494
})
9595

96+
it('uses a static string key as a shared bucket', async () => {
97+
const context: any = {
98+
type: 'before',
99+
method: 'find',
100+
path: 'users',
101+
params: {},
102+
}
103+
const rateLimiter = new RateLimiterMemory({ points: 1, duration: 1 })
104+
105+
// Both requests share the same static bucket, so the second is rejected
106+
await rateLimit(rateLimiter, { key: 'global' })(context)
107+
await expect(
108+
rateLimit(rateLimiter, { key: 'global' })(context),
109+
).rejects.toThrow('Too many requests')
110+
})
111+
112+
it('uses static number points', async () => {
113+
const context: any = {
114+
type: 'before',
115+
method: 'find',
116+
path: 'users',
117+
params: {},
118+
}
119+
const rateLimiter = new RateLimiterMemory({ points: 1, duration: 1 })
120+
121+
// Consuming 2 points against a 1-point limit should fail immediately
122+
await expect(
123+
rateLimit(rateLimiter, { points: 2 })(context),
124+
).rejects.toThrow('Too many requests')
125+
})
126+
96127
it('throws when used in an after hook', async () => {
97128
const context: any = {
98129
type: 'after',

‎src/hooks/rate-limit/rate-limit.hook.ts‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@ import { checkContext } from '../../utils/index.js'
55
import type { Promisable } from '../../internal.utils.js'
66

77
export type RateLimitOptions<H extends HookContext = HookContext> = {
8-
/** Generate the rate-limiting key. Defaults to `context.path`. */
9-
key?: (context: H) => Promisable<string>
10-
/** Number of points to consume per request. Defaults to `1`. */
11-
points?: (context: H) => Promisable<number>
8+
/**
9+
* The rate-limiting key, or a function to derive it from the context.
10+
* Defaults to `context.path`.
11+
*
12+
* Pass a static string to use a single shared bucket (a global rate limit
13+
* across all requests), or a function to compute the key per request
14+
* (e.g. per user or per IP).
15+
*/
16+
key?: string | ((context: H) => Promisable<string>)
17+
/**
18+
* Number of points to consume per request, or a function to compute it from
19+
* the context. Defaults to `1`.
20+
*/
21+
points?: number | ((context: H) => Promisable<number>)
1222
}
1323

1424
/**
@@ -35,13 +45,14 @@ export const rateLimit = <H extends HookContext = HookContext>(
3545
options?: RateLimitOptions<H>,
3646
) => {
3747
const key = options?.key ?? ((context: HookContext) => context.path)
38-
const points = options?.points ?? (() => 1)
48+
const points = options?.points ?? 1
3949

4050
return async (context: H, next?: NextFunction): Promise<void> => {
4151
checkContext(context, { type: ['before', 'around'], label: 'rateLimit' })
4252

43-
const resolvedKey = await key(context)
44-
const resolvedPoints = await points(context)
53+
const resolvedKey = typeof key === 'function' ? await key(context) : key
54+
const resolvedPoints =
55+
typeof points === 'function' ? await points(context) : points
4556

4657
try {
4758
const res = await rateLimiter.consume(resolvedKey, resolvedPoints)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: passParams
2+
title: gateParams
33
category: utils
44
see:
55
- hooks/cache

src/utils/pass-params/pass-params.util.test-d.ts renamed to src/utils/gate-params/gate-params.util.test-d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { Params } from '@feathersjs/feathers'
22
import { expectTypeOf } from 'vitest'
3-
import { passParams } from './pass-params.util.js'
3+
import { gateParams } from './gate-params.util.js'
44

55
it('returns a Params object', () => {
6-
const out = passParams({ query: {} }, { query: true })
6+
const out = gateParams({ query: {} }, { query: true })
77
expectTypeOf(out).toEqualTypeOf<Params>()
88
})
99

1010
it('accepts boolean and function rules, including nested paths and custom keys', () => {
11-
passParams({ query: {}, user: { id: 1 }, custom: 1 } as Params, {
11+
gateParams({ query: {}, user: { id: 1 }, custom: 1 } as Params, {
1212
query: true,
1313
paginate: false,
1414
'user.id': true,
@@ -22,7 +22,7 @@ it('accepts boolean and function rules, including nested paths and custom keys',
2222
})
2323

2424
it('types onUnknownParams and dropUnknownParams', () => {
25-
passParams(
25+
gateParams(
2626
{ query: {} } as Params,
2727
{ query: true },
2828
{

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL