ac-ratelimiter 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/index.js +7 -23
- package/package.json +12 -8
- package/test/test.js +38 -37
- package/.eslintrc.js +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
<a name="2.0.2"></a>
|
|
2
|
+
|
|
3
|
+
## [2.0.2](https://github.com/admiralcloud/ac-ratelimiter/compare/v2.0.1..v2.0.2) (2024-07-06 18:51:38)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fix
|
|
7
|
+
|
|
8
|
+
* **Limiter:** Replaced isPrivateIP with isSpecialIP | MP | [d14140a22a00e5a18eb21d286712f21f8daea8f3](https://github.com/admiralcloud/ac-ratelimiter/commit/d14140a22a00e5a18eb21d286712f21f8daea8f3)
|
|
9
|
+
Replaced isPrivateIP with isSpecialIP
|
|
10
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
11
|
+
<a name="2.0.1"></a>
|
|
12
|
+
|
|
13
|
+
## [2.0.1](https://github.com/admiralcloud/ac-ratelimiter/compare/v2.0.0..v2.0.1) (2024-07-06 14:32:00)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fix
|
|
17
|
+
|
|
18
|
+
* **Limiter:** Updated packages | MP | [6d31ce52d1b300021ba471c3a6261859604ceec5](https://github.com/admiralcloud/ac-ratelimiter/commit/6d31ce52d1b300021ba471c3a6261859604ceec5)
|
|
19
|
+
Updated packages. Use ACError package. Fixed tests for updated packages
|
|
20
|
+
Related issues: [undefined/undefined#develop](undefined/browse/develop)
|
|
1
21
|
<a name="2.0.0"></a>
|
|
2
22
|
|
|
3
23
|
# [2.0.0](https://github.com/admiralcloud/ac-ratelimiter/compare/v1.0.10..v2.0.0) (2024-02-26 07:51:24)
|
package/index.js
CHANGED
|
@@ -2,23 +2,7 @@ const { setTimeout } = require('timers/promises')
|
|
|
2
2
|
|
|
3
3
|
const NodeCache = require('node-cache')
|
|
4
4
|
const acts = require('ac-ip')
|
|
5
|
-
|
|
6
|
-
class ACError extends Error {
|
|
7
|
-
constructor(message, options = {}) {
|
|
8
|
-
super(message)
|
|
9
|
-
|
|
10
|
-
if (Error.captureStackTrace) {
|
|
11
|
-
Error.captureStackTrace(this, ACError)
|
|
12
|
-
}
|
|
13
|
-
// info
|
|
14
|
-
this.code = options.code || -1
|
|
15
|
-
this.errorMessage = message
|
|
16
|
-
// show other properties of options object
|
|
17
|
-
for (const [key, value] of Object.entries(options)) {
|
|
18
|
-
this[key] = value;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
5
|
+
const { ACError } = require('ac-custom-error')
|
|
22
6
|
|
|
23
7
|
class RateLimiter {
|
|
24
8
|
constructor({ redisInstance, logger = console, routes = [], knownIPs = [], ignorePrivateIps } = {}) {
|
|
@@ -76,7 +60,7 @@ class RateLimiter {
|
|
|
76
60
|
rateLimitCounter
|
|
77
61
|
}) {
|
|
78
62
|
|
|
79
|
-
if (this.ignorePrivateIps && acts.
|
|
63
|
+
if (this.ignorePrivateIps && acts.isSpecialIP(ip)) return
|
|
80
64
|
|
|
81
65
|
const logIdentifier = typeof identifier === 'string' && identifier.replace(/(\w{1,4})-(\w{1,4})/g, 'xxxx')
|
|
82
66
|
const knownIP = this.knownIPs.find(({ knownIP }) => knownIP === ip)
|
|
@@ -115,7 +99,7 @@ class RateLimiter {
|
|
|
115
99
|
})
|
|
116
100
|
}
|
|
117
101
|
|
|
118
|
-
|
|
102
|
+
const current = {
|
|
119
103
|
expires,
|
|
120
104
|
limit,
|
|
121
105
|
throttleLimit,
|
|
@@ -145,7 +129,7 @@ class RateLimiter {
|
|
|
145
129
|
else {
|
|
146
130
|
// use Node cache (memory) for rate limiting - please see README before using in production!
|
|
147
131
|
rateLimitCounter = this.cache.get(rateLimiterKey)
|
|
148
|
-
|
|
132
|
+
const ts = this.cache.getTtl(rateLimiterKey) // ts in ms when the key will expire
|
|
149
133
|
rateLimitCounter = rateLimitCounter + 1 || 1
|
|
150
134
|
if (ts === undefined || ts === 0) {
|
|
151
135
|
// first entry - set expiration
|
|
@@ -167,7 +151,7 @@ class RateLimiter {
|
|
|
167
151
|
if (rateLimitCounter === current.limit || rateLimitCounter % 10 === 0) {
|
|
168
152
|
rateLogger({ type: 'Blocking', rateLimitCounter, currentLimit: current.limit })
|
|
169
153
|
}
|
|
170
|
-
throw new ACError('tooManyRequestsFromThisIP',
|
|
154
|
+
throw new ACError('tooManyRequestsFromThisIP', 429, { logging: false, counter: rateLimitCounter, expires: current.expires })
|
|
171
155
|
|
|
172
156
|
}
|
|
173
157
|
else if (current.throttleLimit && rateLimitCounter > current.limit * 0.9) {
|
|
@@ -176,7 +160,7 @@ class RateLimiter {
|
|
|
176
160
|
await setTimeout(current.expires * 1000)
|
|
177
161
|
// do not "taint" process time when deliberately throttline
|
|
178
162
|
if (req._startTime) req._startTime += current.expires * 1000
|
|
179
|
-
throw new ACError('finalThrottlingActive_requestsIsDelayed',
|
|
163
|
+
throw new ACError('finalThrottlingActive_requestsIsDelayed', 900, { counter: rateLimitCounter, expires: current.expires })
|
|
180
164
|
}
|
|
181
165
|
else if (current.throttleLimit && rateLimitCounter > current.throttleLimit) {
|
|
182
166
|
// log the first throttling and every 50th entry
|
|
@@ -186,7 +170,7 @@ class RateLimiter {
|
|
|
186
170
|
await setTimeout(current.delay)
|
|
187
171
|
// do not "taint" process time when deliberately throttline
|
|
188
172
|
if (req._startTime) req._startTime += current.delay
|
|
189
|
-
throw new ACError('throttlingActive_requestsIsDelayed',
|
|
173
|
+
throw new ACError('throttlingActive_requestsIsDelayed', 900, { counter: rateLimitCounter, expires: current.expires })
|
|
190
174
|
}
|
|
191
175
|
}
|
|
192
176
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ac-ratelimiter",
|
|
3
3
|
"description": "Simple ratelimiter for express",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.2",
|
|
5
5
|
"author": "Mark Poepping (www.admiralcloud.com)",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -9,22 +9,26 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://www.admiralcloud.com",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"ac-
|
|
12
|
+
"ac-custom-error": "^1.0.0",
|
|
13
|
+
"ac-ip": "^4.1.0",
|
|
13
14
|
"node-cache": "^5.1.2"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"ac-semantic-release": "^0.4.2",
|
|
17
18
|
"chai": "^4.4.1",
|
|
18
|
-
"eslint": "
|
|
19
|
-
"ioredis": "^5.
|
|
20
|
-
"mocha": "^10.
|
|
19
|
+
"eslint": "9.6.0",
|
|
20
|
+
"ioredis": "^5.4.1",
|
|
21
|
+
"mocha": "^10.6.0"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
|
-
"test": "./node_modules/.bin/mocha --slow 1000 --colors ./test/test.js || :",
|
|
24
|
-
"test-local": "env TESTMODE=true ./node_modules/.bin/mocha --slow 1000 --colors ./test/test.js || :",
|
|
24
|
+
"test": "./node_modules/.bin/mocha --slow 1000 --colors --bail ./test/test.js || :",
|
|
25
|
+
"test-local": "env TESTMODE=true ./node_modules/.bin/mocha --slow 1000 --colors --bail ./test/test.js || :",
|
|
25
26
|
"test-jenkins": "JUNIT_REPORT_PATH=./report.xml mocha --slow 1000 --colors --reporter mocha-jenkins-reporter --reporter-options junit_report_name='RATELIMITER'"
|
|
26
27
|
},
|
|
27
28
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"resolutions": {
|
|
32
|
+
"mocha/chokidar/braces": "^3.0.3"
|
|
29
33
|
}
|
|
30
34
|
}
|
package/test/test.js
CHANGED
|
@@ -6,7 +6,7 @@ const ratelimiterModule = require('../index')
|
|
|
6
6
|
const Redis = require('ioredis')
|
|
7
7
|
const redis = new Redis()
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const req = {
|
|
10
10
|
options: {
|
|
11
11
|
controller: 'user',
|
|
12
12
|
action: 'find',
|
|
@@ -15,12 +15,12 @@ let req = {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const initOptions = {
|
|
19
19
|
routes: [
|
|
20
20
|
{ route: 'user/find', throttleLimit: 1, limit: 2, expires: 3, delay: 250 },
|
|
21
21
|
]
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
const options = {}
|
|
24
24
|
|
|
25
25
|
const ratelimiter = new ratelimiterModule(initOptions)
|
|
26
26
|
const ratelimiterRedis = new ratelimiterModule({ redisInstance: redis, routes: initOptions.routes })
|
|
@@ -37,7 +37,7 @@ describe('Use NodeCache', () => {
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
it('should not trigger - req #1', async() => {
|
|
40
|
-
|
|
40
|
+
const result = await ratelimiter.limiter(req, options)
|
|
41
41
|
expect(result).eql(undefined)
|
|
42
42
|
})
|
|
43
43
|
it('should still not trigger - req #2 - but throw 900', async() => {
|
|
@@ -45,12 +45,13 @@ describe('Use NodeCache', () => {
|
|
|
45
45
|
await ratelimiter.limiter(req, options)
|
|
46
46
|
}
|
|
47
47
|
catch(e) {
|
|
48
|
+
console.log(45, e)
|
|
48
49
|
expect(e).to.have.property('message', 'finalThrottlingActive_requestsIsDelayed')
|
|
49
|
-
expect(e).to.have.property('
|
|
50
|
+
expect(e).to.have.property('code', 900)
|
|
50
51
|
}
|
|
51
52
|
})
|
|
52
53
|
it('should not trigger - req #3 - rate limiter is reset', async() => {
|
|
53
|
-
|
|
54
|
+
const result = await ratelimiter.limiter(req, options)
|
|
54
55
|
expect(result).eql(undefined)
|
|
55
56
|
})
|
|
56
57
|
})
|
|
@@ -81,7 +82,7 @@ describe('Use NodeCache', () => {
|
|
|
81
82
|
catch(e) {
|
|
82
83
|
expect(e).to.be.an('error')
|
|
83
84
|
expect(e).to.have.property('message', 'tooManyRequestsFromThisIP')
|
|
84
|
-
expect(e).to.have.property('
|
|
85
|
+
expect(e).to.have.property('code', 429)
|
|
85
86
|
}
|
|
86
87
|
})
|
|
87
88
|
})
|
|
@@ -105,7 +106,7 @@ describe('Use NodeCache', () => {
|
|
|
105
106
|
|
|
106
107
|
it('should not trigger req #1', async() => {
|
|
107
108
|
req.determinedIP = '2.3.4.1'
|
|
108
|
-
|
|
109
|
+
const result = await ratelimiter.limiter(req, options)
|
|
109
110
|
expect(result).eql(undefined)
|
|
110
111
|
})
|
|
111
112
|
it('should still not trigger - delayed req #2', async() => {
|
|
@@ -114,7 +115,7 @@ describe('Use NodeCache', () => {
|
|
|
114
115
|
}
|
|
115
116
|
catch(e) {
|
|
116
117
|
expect(e).to.have.property('message', 'finalThrottlingActive_requestsIsDelayed')
|
|
117
|
-
expect(e).to.have.property('
|
|
118
|
+
expect(e).to.have.property('code', 900)
|
|
118
119
|
}
|
|
119
120
|
})
|
|
120
121
|
it('should trigger the limiter', async() => {
|
|
@@ -123,7 +124,7 @@ describe('Use NodeCache', () => {
|
|
|
123
124
|
}
|
|
124
125
|
catch(e) {
|
|
125
126
|
expect(e).to.have.property('message', 'tooManyRequestsFromThisIP')
|
|
126
|
-
expect(e).to.have.property('
|
|
127
|
+
expect(e).to.have.property('code', 429)
|
|
127
128
|
}
|
|
128
129
|
})
|
|
129
130
|
})
|
|
@@ -132,7 +133,7 @@ describe('Use NodeCache', () => {
|
|
|
132
133
|
describe('Test section #4 - routes with clientId', function() {
|
|
133
134
|
describe('RATE LIMITER TEST', function() {
|
|
134
135
|
this.timeout(5000)
|
|
135
|
-
|
|
136
|
+
const req = {
|
|
136
137
|
options: {
|
|
137
138
|
controller: 'customer',
|
|
138
139
|
action: 'find'
|
|
@@ -152,7 +153,7 @@ describe('Use NodeCache', () => {
|
|
|
152
153
|
})
|
|
153
154
|
|
|
154
155
|
it('should not trigger - req #1', async() => {
|
|
155
|
-
|
|
156
|
+
const result = await ratelimiter.limiter(req, options)
|
|
156
157
|
expect(result).eql(undefined)
|
|
157
158
|
})
|
|
158
159
|
it('should still not trigger but delay - req #2', async() => {
|
|
@@ -161,11 +162,11 @@ describe('Use NodeCache', () => {
|
|
|
161
162
|
}
|
|
162
163
|
catch(e) {
|
|
163
164
|
expect(e).to.have.property('message', 'finalThrottlingActive_requestsIsDelayed')
|
|
164
|
-
expect(e).to.have.property('
|
|
165
|
+
expect(e).to.have.property('code', 900)
|
|
165
166
|
}
|
|
166
167
|
})
|
|
167
168
|
it('should still not trigger as ratelimiter is reset', async() => {
|
|
168
|
-
|
|
169
|
+
const result = await ratelimiter.limiter(req, options)
|
|
169
170
|
expect(result).eql(undefined)
|
|
170
171
|
})
|
|
171
172
|
|
|
@@ -179,17 +180,17 @@ describe('Use NodeCache', () => {
|
|
|
179
180
|
})
|
|
180
181
|
|
|
181
182
|
it('should not trigger #1', async() => {
|
|
182
|
-
|
|
183
|
+
const result = await ratelimiter.limiter(req, options)
|
|
183
184
|
expect(result).eql(undefined)
|
|
184
185
|
})
|
|
185
186
|
|
|
186
187
|
it('should not trigger #2', async() => {
|
|
187
|
-
|
|
188
|
+
const result = await ratelimiter.limiter(req, options)
|
|
188
189
|
expect(result).eql(undefined)
|
|
189
190
|
})
|
|
190
191
|
|
|
191
192
|
it('should not trigger #3', async() => {
|
|
192
|
-
|
|
193
|
+
const result = await ratelimiter.limiter(req, options)
|
|
193
194
|
expect(result).eql(undefined)
|
|
194
195
|
})
|
|
195
196
|
|
|
@@ -199,7 +200,7 @@ describe('Use NodeCache', () => {
|
|
|
199
200
|
}
|
|
200
201
|
catch(e) {
|
|
201
202
|
expect(e).to.have.property('message', 'throttlingActive_requestsIsDelayed')
|
|
202
|
-
expect(e).to.have.property('
|
|
203
|
+
expect(e).to.have.property('code', 900)
|
|
203
204
|
}
|
|
204
205
|
})
|
|
205
206
|
})
|
|
@@ -208,7 +209,7 @@ describe('Use NodeCache', () => {
|
|
|
208
209
|
describe('Test section #5 - no limiting', function () {
|
|
209
210
|
describe('RATE LIMITER TEST', function() {
|
|
210
211
|
this.timeout(5000)
|
|
211
|
-
|
|
212
|
+
const req = {
|
|
212
213
|
options: {
|
|
213
214
|
controller: 'search',
|
|
214
215
|
action: 'search'
|
|
@@ -221,27 +222,27 @@ describe('Use NodeCache', () => {
|
|
|
221
222
|
})
|
|
222
223
|
|
|
223
224
|
it('should not trigger #1', async() => {
|
|
224
|
-
|
|
225
|
+
const result = await ratelimiter.limiter(req, options)
|
|
225
226
|
expect(result).eql(undefined)
|
|
226
227
|
})
|
|
227
228
|
|
|
228
229
|
it('should not trigger #2', async() => {
|
|
229
|
-
|
|
230
|
+
const result = await ratelimiter.limiter(req, options)
|
|
230
231
|
expect(result).eql(undefined)
|
|
231
232
|
})
|
|
232
233
|
|
|
233
234
|
it('should not trigger #3', async() => {
|
|
234
|
-
|
|
235
|
+
const result = await ratelimiter.limiter(req, options)
|
|
235
236
|
expect(result).eql(undefined)
|
|
236
237
|
})
|
|
237
238
|
|
|
238
239
|
it('should not trigger #4', async() => {
|
|
239
|
-
|
|
240
|
+
const result = await ratelimiter.limiter(req, options)
|
|
240
241
|
expect(result).eql(undefined)
|
|
241
242
|
})
|
|
242
243
|
|
|
243
244
|
it('should not trigger #5', async() => {
|
|
244
|
-
|
|
245
|
+
const result = await ratelimiter.limiter(req, options)
|
|
245
246
|
expect(result).eql(undefined)
|
|
246
247
|
})
|
|
247
248
|
})
|
|
@@ -251,7 +252,7 @@ describe('Use NodeCache', () => {
|
|
|
251
252
|
describe('RATE LIMITER TEST - ignorePrivateIps', function() {
|
|
252
253
|
this.timeout(5000)
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
const req = {
|
|
255
256
|
options: {
|
|
256
257
|
controller: 'user',
|
|
257
258
|
action: 'find'
|
|
@@ -278,19 +279,19 @@ describe('Use NodeCache', () => {
|
|
|
278
279
|
}
|
|
279
280
|
catch(e) {
|
|
280
281
|
expect(e).to.have.property('message', 'tooManyRequestsFromThisIP')
|
|
281
|
-
expect(e).to.have.property('
|
|
282
|
+
expect(e).to.have.property('code', 429)
|
|
282
283
|
}
|
|
283
284
|
})
|
|
284
285
|
|
|
285
286
|
it('should not trigger', async() => {
|
|
286
287
|
req.determinedIP = '127.0.0.1'
|
|
287
|
-
|
|
288
|
+
const result = await ratelimiter.limiter(req, options)
|
|
288
289
|
expect(result).eql(undefined)
|
|
289
290
|
})
|
|
290
291
|
|
|
291
|
-
it('should not trigger - IPv6', async() => {
|
|
292
|
-
req.determinedIP = '
|
|
293
|
-
|
|
292
|
+
it('should not trigger - private IPv6', async() => {
|
|
293
|
+
req.determinedIP = 'FD8A:4C5D:3E1F:0001:ABCD:1234:5678:9ABC'
|
|
294
|
+
const result = await ratelimiter.limiter(req, options)
|
|
294
295
|
expect(result).eql(undefined)
|
|
295
296
|
})
|
|
296
297
|
})
|
|
@@ -313,7 +314,7 @@ describe('Use NodeCache', () => {
|
|
|
313
314
|
})
|
|
314
315
|
|
|
315
316
|
it('should not trigger', async() => {
|
|
316
|
-
|
|
317
|
+
const result = await ratelimiter.limiter(req, { rateLimitCounter: 0 })
|
|
317
318
|
expect(result).eql(undefined)
|
|
318
319
|
})
|
|
319
320
|
|
|
@@ -323,7 +324,7 @@ describe('Use NodeCache', () => {
|
|
|
323
324
|
}
|
|
324
325
|
catch(e) {
|
|
325
326
|
expect(e).to.have.property('message', 'tooManyRequestsFromThisIP')
|
|
326
|
-
expect(e).to.have.property('
|
|
327
|
+
expect(e).to.have.property('code', 429)
|
|
327
328
|
}
|
|
328
329
|
})
|
|
329
330
|
|
|
@@ -353,13 +354,13 @@ describe('Use Redis', () => {
|
|
|
353
354
|
it('should trigger immediately', async() => {
|
|
354
355
|
req.determinedIP = '4.1.4.1'
|
|
355
356
|
try {
|
|
356
|
-
|
|
357
|
+
const x = await ratelimiterRedis.limiter(req, options)
|
|
357
358
|
console.log(304, x)
|
|
358
359
|
}
|
|
359
360
|
catch(e) {
|
|
360
361
|
expect(e).to.be.an('error')
|
|
361
362
|
expect(e).to.have.property('message', 'tooManyRequestsFromThisIP')
|
|
362
|
-
expect(e).to.have.property('
|
|
363
|
+
expect(e).to.have.property('code', 429)
|
|
363
364
|
}
|
|
364
365
|
})
|
|
365
366
|
|
|
@@ -376,7 +377,7 @@ describe('Use Redis', () => {
|
|
|
376
377
|
})
|
|
377
378
|
|
|
378
379
|
it('req #1 should not trigger', async() => {
|
|
379
|
-
|
|
380
|
+
const result = await ratelimiterRedis.limiter(req, options)
|
|
380
381
|
expect(result).eql(undefined)
|
|
381
382
|
})
|
|
382
383
|
|
|
@@ -388,13 +389,13 @@ describe('Use Redis', () => {
|
|
|
388
389
|
catch(e) {
|
|
389
390
|
expect(e).to.be.an('error')
|
|
390
391
|
expect(e).to.have.property('message', 'finalThrottlingActive_requestsIsDelayed')
|
|
391
|
-
expect(e).to.have.property('
|
|
392
|
+
expect(e).to.have.property('code', 900)
|
|
392
393
|
expect(e.additionalInfo).to.have.property('counter', 2)
|
|
393
394
|
}
|
|
394
395
|
})
|
|
395
396
|
|
|
396
397
|
it('req #3 should not trigger the limiter - the throttling has reset the limiter', async() => {
|
|
397
|
-
|
|
398
|
+
const result = await ratelimiterRedis.limiter(req, options)
|
|
398
399
|
expect(result).eql(undefined)
|
|
399
400
|
})
|
|
400
401
|
|
package/.eslintrc.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const config = {
|
|
2
|
-
root: true,
|
|
3
|
-
'env': {
|
|
4
|
-
'commonjs': true,
|
|
5
|
-
'es6': true,
|
|
6
|
-
'node': true
|
|
7
|
-
},
|
|
8
|
-
'extends': 'eslint:recommended',
|
|
9
|
-
"rules": {
|
|
10
|
-
"space-before-function-paren": 0,
|
|
11
|
-
"no-extra-semi": 0,
|
|
12
|
-
"object-curly-spacing": ["error", "always"],
|
|
13
|
-
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
|
14
|
-
"no-useless-escape": 0,
|
|
15
|
-
"standard/no-callback-literal": 0,
|
|
16
|
-
"new-cap": 0
|
|
17
|
-
},
|
|
18
|
-
globals: {
|
|
19
|
-
describe: true,
|
|
20
|
-
it: true,
|
|
21
|
-
after: true
|
|
22
|
-
},
|
|
23
|
-
'parserOptions': {
|
|
24
|
-
'ecmaVersion': 2022
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
module.exports = config
|