Skip to content

Commit 60466f9

Browse files
committed
lint passes!
1 parent 2e50045 commit 60466f9

File tree

6 files changed

+114
-65
lines changed

6 files changed

+114
-65
lines changed

example/server.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ if (cluster.isPrimary) {
3838
}),
3939
)
4040

41-
app.get('/', (request, res) => res.send(`hello from ${process.pid}`))
41+
app.get('/', (request, response) =>
42+
response.send(`hello from ${process.pid}`),
43+
)
4244

4345
// Tip: run with PORT=0 to have the system automatically choose an avaliable port
4446
const server = app.listen(process.env.PORT ?? 3000, () => {

package.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,21 @@
7474
"express-rate-limit": ">= 6"
7575
},
7676
"xo": {
77-
"prettier": true
77+
"prettier": true,
78+
"overrides": [
79+
{
80+
"files": "test/**/*.ts",
81+
"rules": {
82+
"@typescript-eslint/no-unsafe-argument": 0,
83+
"@typescript-eslint/no-unsafe-assignment": 0,
84+
"@typescript-eslint/no-unsafe-call": 0,
85+
"@typescript-eslint/no-unsafe-return": 0,
86+
"import/extensions": 0,
87+
"unicorn/prevent-abbreviations": 0,
88+
"no-promise-executor-return": 0
89+
}
90+
}
91+
]
7892
},
7993
"prettier": "@express-rate-limit/prettier",
8094
"lint-staged": {

source/primary.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
import cluster, { type Worker } from 'node:cluster'
55
import { MemoryStore, type Store } from 'express-rate-limit'
6-
import { type WorkerToPrimaryMessage, from } from './shared'
6+
import {
7+
type WorkerToPrimaryMessage,
8+
from,
9+
type PrimaryToWorkerMessage,
10+
} from './shared.js'
711
// Import type { Options } from './types'
812

913
export class ClusterMemoryStorePrimary {
@@ -43,14 +47,18 @@ export class ClusterMemoryStorePrimary {
4347
}
4448

4549
if (command in store && typeof store[command] === 'function') {
46-
const result = await (
47-
store[command] as (this: Store, ...[]) => Promise<any>
48-
).apply(store, args)
49-
worker.send({
50+
const result: unknown =
51+
await // eslint-disable-next-line no-empty-pattern
52+
(store[command] as (this: Store, ...[]) => Promise<any>).apply(
53+
store,
54+
args,
55+
)
56+
const message: PrimaryToWorkerMessage = {
5057
requestId,
5158
result,
5259
from,
53-
})
60+
}
61+
worker.send(message)
5462
} else {
5563
console.error(
5664
new Error(

source/shared.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
import type { Store } from 'express-rate-limit'
1+
import type { IncrementResponse, Store } from 'express-rate-limit'
22

33
export const from = 'cluster-memory-store'
44

55
export type Command = keyof Omit<Store, 'prefix' | 'localKeys'>
66

7-
export type WorkerToPrimaryMessage = {
7+
type Message = {
8+
from: 'cluster-memory-store'
9+
}
10+
11+
export type WorkerToPrimaryMessage = Message & {
812
command: Command
913
args: any[]
1014
requestId: number
1115
prefix: string
12-
from: 'cluster-memory-store'
1316
}
1417

15-
export type PrimaryToWorkerMessage = {
18+
export type PrimaryToWorkerMessage = Message & {
1619
requestId: number
17-
result: any
20+
result: unknown
21+
}
22+
23+
export type SerializedIncrementResponse = IncrementResponse & {
24+
resetTime: string
1825
}

source/worker.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
type PrimaryToWorkerMessage,
1515
type WorkerToPrimaryMessage,
1616
from,
17-
} from './shared'
17+
type SerializedIncrementResponse,
18+
} from './shared.js'
1819

1920
/**
2021
* A `Store` for the `express-rate-limit` package that communicates with the primary process to store and retrieve hits
@@ -30,6 +31,19 @@ export class ClusterMemoryStoreWorker implements Store {
3031
*/
3132
prefix!: string
3233

34+
/**
35+
* Map of requestId to the data & calllback needed to finish handling a request without throwingany errors
36+
*/
37+
private readonly openRequests = new Map<
38+
number,
39+
{ timeoutId: NodeJS.Timeout; resolve: (value: unknown) => void }
40+
>()
41+
42+
/**
43+
* Counter to generate reqiestIds, which are used to tie the response to the matching openRequest
44+
*/
45+
private currentRequestId = 0
46+
3347
/**
3448
* @constructor for `ClusterMemoryStore`.
3549
*
@@ -77,7 +91,9 @@ export class ClusterMemoryStoreWorker implements Store {
7791
* @returns {IncrementResponse} - The number of hits and reset time for that client.
7892
*/
7993
async increment(key: string): Promise<IncrementResponse> {
80-
const { totalHits, resetTime } = await this.send('increment', [key])
94+
const { totalHits, resetTime } = (await this.send('increment', [
95+
key,
96+
])) as SerializedIncrementResponse
8197
return {
8298
totalHits,
8399
resetTime: new Date(resetTime), // Date objects are serialized to strings for IPC
@@ -90,7 +106,7 @@ export class ClusterMemoryStoreWorker implements Store {
90106
* @param key {string} - The identifier for a client
91107
*/
92108
async decrement(key: string): Promise<void> {
93-
return this.send('decrement', [key])
109+
await this.send('decrement', [key])
94110
}
95111

96112
/**
@@ -99,16 +115,9 @@ export class ClusterMemoryStoreWorker implements Store {
99115
* @param key {string} - The identifier for a client.
100116
*/
101117
async resetKey(key: string): Promise<void> {
102-
return this.send('resetKey', [key])
118+
await this.send('resetKey', [key])
103119
}
104120

105-
private readonly openRequests = new Map<
106-
number,
107-
{ timeoutId: NodeJS.Timeout; resolve: (value: unknown) => void }
108-
>()
109-
110-
private currentRequestId = 0
111-
112121
private async send(command: Command, args: any[]): Promise<any> {
113122
return new Promise((resolve, reject) => {
114123
const requestId = this.currentRequestId++
@@ -119,6 +128,7 @@ export class ClusterMemoryStoreWorker implements Store {
119128
`ClusterMemoryStoreWorker: no response recieved to ${command} command after ${timelimit}ms.`,
120129
),
121130
)
131+
this.openRequests.delete(requestId)
122132
}, timelimit)
123133
this.openRequests.set(requestId, { timeoutId, resolve })
124134
if (!process.send) {
@@ -167,6 +177,7 @@ export class ClusterMemoryStoreWorker implements Store {
167177
const { timeoutId, resolve } = this.openRequests.get(
168178
message_.requestId,
169179
)!
180+
this.openRequests.delete(message_.requestId)
170181
clearTimeout(timeoutId)
171182
resolve(message_.result)
172183
} else {

test/realtime-e2e-test.ts

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,52 +12,59 @@ import {
1212
} from '../source/index'
1313

1414
async function getStatus(port: number) {
15-
const resp = await fetch(`http://localhost:${port}`);
15+
const resp = await fetch(`http://localhost:${port}`)
1616
return resp.status
1717
}
1818

19-
if (cluster.isPrimary) {
20-
const rateLimiterStore = new ClusterMemoryStorePrimary()
21-
rateLimiterStore.init()
22-
23-
// One worker
24-
const worker = cluster.fork()
25-
26-
// get the port the worker ends up listening on
27-
const port:number = await new Promise((resolve) => cluster.on('listening', (worker: any, address:any) => resolve(address.port)))
28-
29-
// make a few requests
30-
assert.equal(await getStatus(port), 200) // success
31-
assert.equal(await getStatus(port), 429) // rate limited
32-
await new Promise(resolve => setTimeout(resolve, 100)) // wait for the limit to be reset
33-
assert.equal(await getStatus(port), 200) // success
34-
35-
// cleanup
36-
worker.kill()
37-
38-
console.log('test completed successfully')
39-
40-
} else {
41-
// Worker
42-
const app = express()
43-
44-
app.use(
45-
rateLimit({
46-
store: new ClusterMemoryStoreWorker(),
47-
validate: false,
48-
limit: 1,
49-
windowMs: 100
50-
}),
51-
)
52-
53-
app.get('/', (request, response) => response.send(`hello from ${process.pid}`))
54-
55-
// port 0 lets the system assign an avaliable port
56-
const server = app.listen(0 /*, () => {
19+
if (cluster.isPrimary) {
20+
const rateLimiterStore = new ClusterMemoryStorePrimary()
21+
rateLimiterStore.init()
22+
23+
// One worker
24+
const worker = cluster.fork()
25+
26+
// Get the port the worker ends up listening on
27+
const port: number = await new Promise((resolve) =>
28+
cluster.on('listening', (worker: any, address: any) => {
29+
resolve(address.port)
30+
}),
31+
)
32+
33+
// Make a few requests
34+
assert.equal(await getStatus(port), 200) // Success
35+
assert.equal(await getStatus(port), 429) // Rate limited
36+
await new Promise((resolve) => setTimeout(resolve, 100)) // Wait for the limit to be reset
37+
assert.equal(await getStatus(port), 200) // Success
38+
39+
// cleanup
40+
worker.kill()
41+
42+
console.log('test completed successfully')
43+
} else {
44+
// Worker
45+
const app = express()
46+
47+
app.use(
48+
rateLimit({
49+
store: new ClusterMemoryStoreWorker(),
50+
validate: false,
51+
limit: 1,
52+
windowMs: 100,
53+
}),
54+
)
55+
56+
app.get('/', (request, response) =>
57+
response.send(`hello from ${process.pid}`),
58+
)
59+
60+
// Port 0 lets the system assign an avaliable port
61+
const server = app.listen(
62+
0 /* , () => {
5763
console.log(
5864
`Worker ${process.pid} listening at on http://localhost:${
5965
(server.address() as any).port
6066
}/`,
6167
)
62-
}*/)
63-
}
68+
} */,
69+
)
70+
}

0 commit comments

Comments
 (0)