Skip to content

Commit f48b3d6

Browse files
chore(deps): upgraded to latest dependencies
upgraded to latest dependencies GH-0
1 parent 7d9a00a commit f48b3d6

File tree

9 files changed

+2562
-3902
lines changed

9 files changed

+2562
-3902
lines changed

package-lock.json

Lines changed: 2389 additions & 3832 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,54 +44,56 @@
4444
"!*/__tests__"
4545
],
4646
"dependencies": {
47-
"@loopback/boot": "^8.0.4",
48-
"@loopback/context": "^8.0.3",
49-
"@loopback/core": "^7.0.3",
50-
"@loopback/repository": "^8.0.3",
51-
"@loopback/rest": "^15.0.4",
52-
"express-rate-limit": "^6.4.0",
53-
"rate-limit-memcached": "^0.6.0",
47+
"@loopback/boot": "^8.0.11",
48+
"@loopback/context": "^8.0.10",
49+
"@loopback/core": "^7.0.10",
50+
"@loopback/repository": "^8.0.10",
51+
"@loopback/rest": "^15.0.11",
52+
"express-rate-limit": "^8.3.1",
53+
"rate-limit-memcached": "^1.0.1",
5454
"rate-limit-mongo": "^2.3.2",
55-
"rate-limit-redis": "^3.0.1"
55+
"rate-limit-redis": "^4.3.1"
5656
},
5757
"devDependencies": {
58-
"@commitlint/cli": "^17.7.1",
59-
"@commitlint/config-conventional": "^17.7.0",
60-
"@loopback/build": "^12.0.3",
58+
"@commitlint/cli": "^20.5.0",
59+
"@commitlint/config-conventional": "^20.5.0",
60+
"@loopback/build": "^12.0.10",
6161
"@loopback/eslint-config": "^16.0.1",
62-
"@loopback/testlab": "^8.0.3",
63-
"@semantic-release/changelog": "^6.0.1",
64-
"@semantic-release/commit-analyzer": "^9.0.2",
62+
"@loopback/testlab": "^8.0.10",
63+
"@semantic-release/changelog": "^6.0.3",
64+
"@semantic-release/commit-analyzer": "^13.0.1",
6565
"@semantic-release/git": "^10.0.1",
66-
"@semantic-release/github": "^12.0.0",
67-
"@semantic-release/npm": "^13.1.1",
68-
"@semantic-release/release-notes-generator": "^10.0.3",
69-
"@types/express-rate-limit": "^5.0.0",
70-
"@types/memcached": "^2.2.6",
71-
"@types/node": "^18.11.9",
72-
"@types/proxyquire": "^1.3.28",
73-
"@types/rate-limit-redis": "^1.7.4",
74-
"@typescript-eslint/eslint-plugin": "^7.16.0",
75-
"@typescript-eslint/parser": "^7.16.0",
66+
"@semantic-release/github": "^12.0.6",
67+
"@semantic-release/npm": "^13.1.5",
68+
"@semantic-release/release-notes-generator": "^14.1.0",
69+
"@types/express-rate-limit": "^6.0.2",
70+
"@types/ioredis": "^4.28.10",
71+
"@types/memcached": "^2.2.10",
72+
"@types/node": "^25.5.0",
73+
"@types/proxyquire": "^1.3.31",
74+
"@types/rate-limit-redis": "^3.0.0",
75+
"@typescript-eslint/eslint-plugin": "^7.18.0",
76+
"@typescript-eslint/parser": "^7.18.0",
7677
"cz-conventional-changelog": "^3.3.0",
77-
"cz-customizable": "^6.3.0",
78-
"eslint": "^8.57.0",
79-
"eslint-config-prettier": "^9.1.0",
78+
"cz-customizable": "^7.5.1",
79+
"eslint": "^8.57.1",
80+
"eslint-config-prettier": "^10.1.8",
8081
"eslint-plugin-eslint-plugin": "^5.5.1",
81-
"eslint-plugin-mocha": "^10.4.3",
82-
"fs-extra": "^11.2.0",
82+
"eslint-plugin-mocha": "^10.5.0",
83+
"fs-extra": "^11.3.4",
8384
"git-release-notes": "^5.0.0",
84-
"husky": "^7.0.4",
85-
"jsdom": "^21.0.0",
85+
"husky": "^9.1.7",
86+
"jsdom": "^29.0.0",
8687
"loopback-connector-kv-redis": "^4.0.0",
8788
"memcached": "^2.2.2",
8889
"proxyquire": "^2.1.3",
89-
"semantic-release": "^25.0.1",
90-
"simple-git": "^3.15.1",
90+
"semantic-release": "^25.0.3",
91+
"simple-git": "^3.33.0",
9192
"source-map-support": "^0.5.21",
92-
"typescript": "~5.2.2"
93+
"typescript": "~5.5.4"
9394
},
9495
"overrides": {
96+
"uuid": "^9.0.1",
9597
"peerDependencies": {
9698
"loopback-connector-kv-redis": "^4.0.0",
9799
"memcached": "^2.2.2"

src/__tests__/acceptance/ratelimit-action-acceptance/rate-limiter.acceptance.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Client} from '@loopback/testlab';
22
import {memoryStore} from '../store.provider';
33
import {TestApplication} from './fixtures/application';
44
import {setUpApplication} from './helper';
5+
import {clearRateLimitCache} from '../../../providers/ratelimit-action.provider';
56

67
const OK_STATUS_CODE = 200;
78
const TOO_MANY_REQS_CODE = 429;
@@ -15,6 +16,7 @@ describe('Acceptance Test Cases', () => {
1516
});
1617
afterEach(async () => {
1718
await clearStore();
19+
clearRateLimitCache();
1820
});
1921

2022
after(async () => app.stop());

src/__tests__/acceptance/ratelimit-middleware-acceptance/rate-limiter.acceptance.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {Client} from '@loopback/testlab';
22
import {memoryStore} from '../store.provider';
33
import {TestApplication} from './fixtures/application';
44
import {setUpApplication} from './helper';
5+
import {clearRateLimitCache} from '../../../middleware/ratelimit.middleware';
6+
57
describe('Acceptance Test Cases', () => {
68
let app: TestApplication;
79
let client: Client;
@@ -11,6 +13,7 @@ describe('Acceptance Test Cases', () => {
1113
});
1214
afterEach(async () => {
1315
await clearStore();
16+
clearRateLimitCache();
1417
});
1518

1619
after(async () => app.stop());
Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import {inject, Provider, ValueOrPromise} from '@loopback/core';
1+
import {Provider, ValueOrPromise} from '@loopback/core';
22
import {Store} from 'express-rate-limit';
3-
import {RateLimitSecurityBindings} from '../../keys';
4-
import {RateLimitOptions} from '../../types';
5-
import {InMemoryStore} from './in-memory-store';
6-
export const memoryStore = new InMemoryStore();
7-
export class StoreProvider implements Provider<Store> {
8-
constructor(
9-
@inject(RateLimitSecurityBindings.CONFIG, {optional: true})
10-
private readonly config?: RateLimitOptions,
11-
) {}
12-
value(): ValueOrPromise<Store> {
13-
const DEFAULT_WINDOW_MS = 60000;
14-
const windowMs = this.config?.windowMs ?? DEFAULT_WINDOW_MS;
15-
memoryStore.setInterval(windowMs);
16-
return memoryStore;
3+
4+
// InMemoryStore is no longer used as a shared store in v8
5+
// express-rate-limit v8 creates its own InMemoryStore instances internally
6+
// This provider now returns null to use the default InMemoryStore
7+
8+
export class StoreProvider implements Provider<Store | null> {
9+
value(): ValueOrPromise<Store | null> {
10+
// Return null to let express-rate-limit v8 create its own InMemoryStore
11+
// Each RateLimit instance will have its own store with proper state management
12+
return null;
1713
}
1814
}
15+
16+
// Export empty memoryStore object for backward compatibility with tests
17+
export const memoryStore = {
18+
resetAll: (): void => {
19+
// No-op since stores are managed by express-rate-limit v8
20+
// Rate limit state will be reset between tests via cache clearing
21+
},
22+
};

src/middleware/ratelimit.middleware.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ import * as RateLimit from 'express-rate-limit';
1616
import {RateLimitSecurityBindings} from '../keys';
1717
import {RateLimitMetadata, RateLimitOptions} from '../types';
1818
import {RatelimitActionMiddlewareGroup} from './middleware.enum';
19+
20+
// Cache for RateLimit instances to avoid store reuse error in v8
21+
const rateLimitCache = new Map<string, RateLimit.RateLimitRequestHandler>();
22+
23+
function getRateLimiterKey(opts: Partial<RateLimitOptions>): string {
24+
return JSON.stringify(opts);
25+
}
26+
27+
// Export function to clear cache for testing
28+
export function clearRateLimitCache(): void {
29+
rateLimitCache.clear();
30+
}
31+
1932
@injectable(
2033
asMiddleware({
2134
group: RatelimitActionMiddlewareGroup.RATELIMIT,
@@ -26,7 +39,7 @@ import {RatelimitActionMiddlewareGroup} from './middleware.enum';
2639
export class RatelimitMiddlewareProvider implements Provider<Middleware> {
2740
constructor(
2841
@inject.getter(RateLimitSecurityBindings.DATASOURCEPROVIDER)
29-
private readonly getDatastore: Getter<RateLimit.Store>,
42+
private readonly getDatastore: Getter<RateLimit.Store | null>,
3043
@inject.getter(RateLimitSecurityBindings.METADATA)
3144
private readonly getMetadata: Getter<RateLimitMetadata>,
3245
@inject(CoreBindings.APPLICATION_INSTANCE)
@@ -60,17 +73,44 @@ export class RatelimitMiddlewareProvider implements Provider<Middleware> {
6073
const operationMetadata = metadata ? metadata.options : {};
6174

6275
// Create options based on global config and method level config
63-
const opts = {...this.config, ...operationMetadata};
76+
const rawOpts = {...this.config, ...operationMetadata};
77+
78+
// Filter out unsupported options for express-rate-limit v8
79+
// 'name' is no longer supported in v8
80+
// 'client', 'type', 'uri', 'collectionName' are custom DataSourceConfig options
81+
/* eslint-disable @typescript-eslint/no-unused-vars */
82+
const {
83+
name,
84+
client,
85+
type,
86+
uri,
87+
collectionName,
88+
store: originalStore,
89+
...opts
90+
} = rawOpts as RateLimitOptions & {store?: unknown};
91+
/* eslint-enable @typescript-eslint/no-unused-vars */
6492

93+
// If dataStore is null or undefined, don't set the store property
94+
// express-rate-limit v8 will create its own InMemoryStore
6595
if (dataStore) {
66-
opts.store = dataStore;
96+
(opts as RateLimit.Options).store = dataStore;
6797
}
6898

6999
opts.message = new HttpErrors.TooManyRequests(
70100
opts.message?.toString() ?? 'Method rate limit reached !',
71101
);
72102

73-
const limiter = RateLimit.default(opts);
103+
// Get or create a RateLimit instance for this configuration
104+
// This avoids the "store reuse" error in express-rate-limit v8
105+
// Note: We exclude 'store' from cache key since each store instance is unique
106+
const cacheKey = getRateLimiterKey(opts);
107+
let limiter = rateLimitCache.get(cacheKey);
108+
109+
if (!limiter) {
110+
limiter = RateLimit.default(opts);
111+
rateLimitCache.set(cacheKey, limiter);
112+
}
113+
74114
limiter(request, response, (err: unknown) => {
75115
if (err) {
76116
reject(err);

src/providers/ratelimit-action.provider.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,22 @@ import * as RateLimit from 'express-rate-limit';
66
import {RateLimitSecurityBindings} from '../keys';
77
import {RateLimitAction, RateLimitMetadata, RateLimitOptions} from '../types';
88

9+
// Cache for RateLimit instances to avoid store reuse error in v8
10+
const rateLimitCache = new Map<string, RateLimit.RateLimitRequestHandler>();
11+
12+
function getRateLimiterKey(opts: Partial<RateLimitOptions>): string {
13+
return JSON.stringify(opts);
14+
}
15+
16+
// Export function to clear cache for testing
17+
export function clearRateLimitCache(): void {
18+
rateLimitCache.clear();
19+
}
20+
921
export class RatelimitActionProvider implements Provider<RateLimitAction> {
1022
constructor(
1123
@inject.getter(RateLimitSecurityBindings.DATASOURCEPROVIDER)
12-
private readonly getDatastore: Getter<RateLimit.Store>,
24+
private readonly getDatastore: Getter<RateLimit.Store | null>,
1325
@inject.getter(RateLimitSecurityBindings.METADATA)
1426
private readonly getMetadata: Getter<RateLimitMetadata>,
1527
@inject(CoreBindings.APPLICATION_INSTANCE)
@@ -39,17 +51,44 @@ export class RatelimitActionProvider implements Provider<RateLimitAction> {
3951
const operationMetadata = metadata ? metadata.options : {};
4052

4153
// Create options based on global config and method level config
42-
const opts = {...this.config, ...operationMetadata};
54+
const rawOpts = {...this.config, ...operationMetadata};
4355

56+
// Filter out unsupported options for express-rate-limit v8
57+
// 'name' is no longer supported in v8
58+
// 'client', 'type', 'uri', 'collectionName' are custom DataSourceConfig options
59+
/* eslint-disable @typescript-eslint/no-unused-vars */
60+
const {
61+
name,
62+
client,
63+
type,
64+
uri,
65+
collectionName,
66+
store: originalStore,
67+
...opts
68+
} = rawOpts as RateLimitOptions & {store?: unknown};
69+
/* eslint-enable @typescript-eslint/no-unused-vars */
70+
71+
// If dataStore is null or undefined, don't set the store property
72+
// express-rate-limit v8 will create its own InMemoryStore
4473
if (dataStore) {
45-
opts.store = dataStore;
74+
(opts as RateLimit.Options).store = dataStore;
4675
}
4776

4877
opts.message = new HttpErrors.TooManyRequests(
4978
opts.message?.toString() ?? 'Method rate limit reached !',
5079
);
5180

52-
const limiter = RateLimit.default(opts);
81+
// Get or create a RateLimit instance for this configuration
82+
// This avoids the "store reuse" error in express-rate-limit v8
83+
// Note: We exclude 'store' from cache key since each store instance is unique
84+
const cacheKey = getRateLimiterKey(opts);
85+
let limiter = rateLimitCache.get(cacheKey);
86+
87+
if (!limiter) {
88+
limiter = RateLimit.default(opts);
89+
rateLimitCache.set(cacheKey, limiter);
90+
}
91+
5392
limiter(request, response, (err: unknown) => {
5493
if (err) {
5594
reject(err);

src/providers/ratelimit-datasource.provider.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import {CoreBindings, inject, Provider} from '@loopback/core';
22
import {Getter, juggler} from '@loopback/repository';
33
import {HttpErrors, RestApplication} from '@loopback/rest';
4-
import MemcachedStore from 'rate-limit-memcached';
5-
import MongoStore from 'rate-limit-mongo';
6-
import RedisStore, {RedisReply} from 'rate-limit-redis';
4+
import {MemcachedStore} from 'rate-limit-memcached';
5+
import MongoStore = require('rate-limit-mongo');
6+
import {RedisStore, type RedisReply} from 'rate-limit-redis';
77
import {TextDecoder} from 'util';
88
import {RateLimitSecurityBindings} from '../keys';
9-
import {RateLimitMetadata, RateLimitOptions, Store} from '../types';
9+
import {
10+
MemcachedClient,
11+
RateLimitMetadata,
12+
RateLimitOptions,
13+
Store,
14+
} from '../types';
1015

1116
const decoder = new TextDecoder('utf-8');
1217
export class RatelimitDatasourceProvider implements Provider<Store> {
@@ -34,8 +39,10 @@ export class RatelimitDatasourceProvider implements Provider<Store> {
3439
const var2 = 1000;
3540

3641
if (this.config?.type === 'MemcachedStore') {
37-
const expiration = (opts.windowMs ?? var1 * var2) / var2;
38-
return new MemcachedStore({client: this.config?.client, expiration});
42+
return new MemcachedStore({
43+
client: this.config?.client as MemcachedClient,
44+
prefix: '',
45+
});
3946
}
4047
if (this.config?.type === 'MongoStore') {
4148
const expireTimeMs = (opts.windowMs ?? var1 * var2) / var2;

src/types.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import {Request, Response} from '@loopback/rest';
22
import {Options} from 'express-rate-limit';
3-
import MemcachedStore from 'rate-limit-memcached';
4-
import MongoStore from 'rate-limit-mongo';
5-
import RedisStore from 'rate-limit-redis';
6-
import {RedisClient} from 'redis';
7-
import IORedis = require('ioredis');
3+
import {MemcachedStore} from 'rate-limit-memcached';
4+
import MongoStore = require('rate-limit-mongo');
5+
import {RedisStore} from 'rate-limit-redis';
6+
import type {Redis as IORedis} from 'ioredis';
7+
import type Memcached from 'memcached';
88

9-
export type RedisClientType = IORedis.Redis | RedisClient;
9+
export type RedisClientType = IORedis;
10+
11+
// Extract only the methods we need from Memcached class
12+
export type MemcachedClient = Pick<
13+
Memcached,
14+
'get' | 'set' | 'add' | 'del' | 'incr' | 'decr'
15+
>;
1016

1117
export interface DataSourceConfig {
1218
name: string;
13-
client?: string | RedisClientType;
19+
client?: string | RedisClientType | MemcachedClient;
1420
type?: string;
1521
uri?: string;
1622
collectionName?: string;

0 commit comments

Comments
 (0)