rate-limiter-flexible 7.1.1 → 7.2.0
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/README.md +10 -12
- package/index.js +2 -0
- package/lib/RateLimiterDrizzle.js +170 -0
- package/lib/index.d.ts +18 -10
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -26,12 +26,8 @@ Memory limiter also works in the browser.
|
|
|
26
26
|
|
|
27
27
|
**Friendly.** No matter which node package you prefer: [`valkey-glide`](https://www.npmjs.com/package/@valkey/valkey-glide) or [`iovalkey`](https://www.npmjs.com/package/iovalkey), `redis` or `ioredis`, `sequelize`/`typeorm` or `knex`, `memcached`, native driver or `mongoose`. It works with all of them.
|
|
28
28
|
|
|
29
|
-
**Safe for using with valkey cluster.** [`valkey-glide`](https://www.npmjs.com/package/@valkey/valkey-glide) implementation, [RateLimiterValkeyGlide](https://github.com/animir/node-rate-limiter-flexible/wiki/Valkey-Glide), is being tested to ensure compatibility and high performance.
|
|
30
|
-
|
|
31
29
|
**In-memory blocks.** Avoid extra requests to store with [inMemoryBlockOnConsumed](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#inmemoryblockonconsumed).
|
|
32
30
|
|
|
33
|
-
Allow **traffic bursts** with [BurstyRateLimiter](https://github.com/animir/node-rate-limiter-flexible/wiki/BurstyRateLimiter).
|
|
34
|
-
|
|
35
31
|
**Deno compatible** See [this example](https://gist.github.com/animir/d06ca92931677f330d3f2d4c6c3108e4)
|
|
36
32
|
|
|
37
33
|
It uses a **fixed window**, as it is much faster than a rolling window.
|
|
@@ -123,12 +119,12 @@ Full documentation is on [Wiki](https://github.com/animir/node-rate-limiter-flex
|
|
|
123
119
|
Some copy/paste examples on Wiki:
|
|
124
120
|
* [Minimal protection against password brute-force](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#minimal-protection-against-password-brute-force)
|
|
125
121
|
* [Login endpoint protection](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#login-endpoint-protection)
|
|
122
|
+
* [Apply Block Strategy](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#apply-in-memory-block-strategy-to-avoid-extra-requests-to-store)
|
|
123
|
+
* [Setup Insurance Strategy](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#setup-insurance-strategy-for-store-limiters)
|
|
126
124
|
* [Websocket connection prevent flooding](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding)
|
|
127
125
|
* [Dynamic block duration](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#dynamic-block-duration)
|
|
128
126
|
* [Authorized users specific limits](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users)
|
|
129
127
|
* [Different limits for different parts of application](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#different-limits-for-different-parts-of-application)
|
|
130
|
-
* [Apply Block Strategy](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#apply-in-memory-block-strategy-to-avoid-extra-requests-to-store)
|
|
131
|
-
* [Setup Insurance Strategy](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#setup-insurance-strategy-for-store-limiters)
|
|
132
128
|
* [Third-party API, crawler, bot rate limiting](https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#third-party-api-crawler-bot-rate-limiting)
|
|
133
129
|
|
|
134
130
|
### Migration from other packages
|
|
@@ -139,19 +135,21 @@ Some copy/paste examples on Wiki:
|
|
|
139
135
|
|
|
140
136
|
* [Options](https://github.com/animir/node-rate-limiter-flexible/wiki/Options)
|
|
141
137
|
* [API methods](https://github.com/animir/node-rate-limiter-flexible/wiki/API-methods)
|
|
142
|
-
|
|
143
|
-
* [
|
|
144
|
-
* [Memory](https://github.com/animir/node-rate-limiter-flexible/wiki/Memory)
|
|
138
|
+
|
|
139
|
+
* [Drizzle](https://github.com/animir/node-rate-limiter-flexible/wiki/Drizzle)
|
|
145
140
|
* [DynamoDb](https://github.com/animir/node-rate-limiter-flexible/wiki/DynamoDB)
|
|
146
141
|
* [Etcd](https://github.com/animir/node-rate-limiter-flexible/wiki/Etcd)
|
|
147
|
-
* [
|
|
148
|
-
* [
|
|
142
|
+
* [Memcached](https://github.com/animir/node-rate-limiter-flexible/wiki/Memcache)
|
|
143
|
+
* [Memory](https://github.com/animir/node-rate-limiter-flexible/wiki/Memory)
|
|
149
144
|
* [Mongo](https://github.com/animir/node-rate-limiter-flexible/wiki/Mongo) (with [sharding support](https://github.com/animir/node-rate-limiter-flexible/wiki/Mongo#mongodb-sharding-options))
|
|
150
145
|
* [MySQL](https://github.com/animir/node-rate-limiter-flexible/wiki/MySQL) (support Sequelize and Knex)
|
|
151
146
|
* [Postgres](https://github.com/animir/node-rate-limiter-flexible/wiki/PostgreSQL) (support Sequelize, TypeORM and Knex)
|
|
147
|
+
* [Prisma](https://github.com/animir/node-rate-limiter-flexible/wiki/Prisma)
|
|
148
|
+
* [Redis](https://github.com/animir/node-rate-limiter-flexible/wiki/Redis)
|
|
152
149
|
* [SQLite](https://github.com/animir/node-rate-limiter-flexible/wiki/SQLite)
|
|
150
|
+
* Valkey: [iovalkey](https://github.com/animir/node-rate-limiter-flexible/wiki/IoValkey) or [ValkeyGlide](https://github.com/animir/node-rate-limiter-flexible/wiki/Valkey-Glide)
|
|
153
151
|
* [RateLimiterCluster](https://github.com/animir/node-rate-limiter-flexible/wiki/Cluster) ([PM2 cluster docs read here](https://github.com/animir/node-rate-limiter-flexible/wiki/PM2-cluster))
|
|
154
|
-
* [
|
|
152
|
+
* [BurstyRateLimiter](https://github.com/animir/node-rate-limiter-flexible/wiki/BurstyRateLimiter) Traffic burst support
|
|
155
153
|
* [RateLimiterUnion](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterUnion) Combine 2 or more limiters to act as single
|
|
156
154
|
* [RLWrapperBlackAndWhite](https://github.com/animir/node-rate-limiter-flexible/wiki/Black-and-White-lists) Black and White lists
|
|
157
155
|
* [RateLimiterQueue](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterQueue) Rate limiter with FIFO queue
|
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const BurstyRateLimiter = require('./lib/BurstyRateLimiter');
|
|
|
12
12
|
const RateLimiterRes = require('./lib/RateLimiterRes');
|
|
13
13
|
const RateLimiterDynamo = require('./lib/RateLimiterDynamo');
|
|
14
14
|
const RateLimiterPrisma = require('./lib/RateLimiterPrisma');
|
|
15
|
+
const RateLimiterDrizzle = require('./lib/RateLimiterDrizzle');
|
|
15
16
|
const RateLimiterValkey = require('./lib/RateLimiterValkey');
|
|
16
17
|
const RateLimiterValkeyGlide = require('./lib/RateLimiterValkeyGlide');
|
|
17
18
|
const RateLimiterSQLite = require('./lib/RateLimiterSQLite');
|
|
@@ -39,5 +40,6 @@ module.exports = {
|
|
|
39
40
|
RateLimiterValkeyGlide,
|
|
40
41
|
RateLimiterSQLite,
|
|
41
42
|
RateLimiterEtcd,
|
|
43
|
+
RateLimiterDrizzle,
|
|
42
44
|
RateLimiterEtcdNonAtomic,
|
|
43
45
|
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
let drizzleOperators = null;
|
|
2
|
+
const CLEANUP_INTERVAL_MS = 300000; // 5 minutes
|
|
3
|
+
const EXPIRED_THRESHOLD_MS = 3600000; // 1 hour
|
|
4
|
+
|
|
5
|
+
class RateLimiterDrizzleError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'RateLimiterDrizzleError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getDrizzleOperators() {
|
|
13
|
+
if (drizzleOperators) return drizzleOperators;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { and, or, gt, lt, eq, isNull , sql } = require('drizzle-orm');
|
|
17
|
+
drizzleOperators = { and, or, gt, lt, eq, isNull , sql };
|
|
18
|
+
return drizzleOperators;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new RateLimiterDrizzleError(
|
|
21
|
+
'drizzle-orm is not installed. Please install drizzle-orm to use RateLimiterDrizzle.'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
|
|
27
|
+
const RateLimiterRes = require('./RateLimiterRes');
|
|
28
|
+
|
|
29
|
+
class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
30
|
+
constructor(opts) {
|
|
31
|
+
super(opts);
|
|
32
|
+
|
|
33
|
+
if (!opts?.schema) {
|
|
34
|
+
throw new RateLimiterDrizzleError('Drizzle schema is required');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!opts?.storeClient) {
|
|
38
|
+
throw new RateLimiterDrizzleError('Drizzle client is required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.schema = opts.schema;
|
|
42
|
+
this.drizzleClient = opts.storeClient;
|
|
43
|
+
this.clearExpiredByTimeout = opts.clearExpiredByTimeout ?? true;
|
|
44
|
+
|
|
45
|
+
if (this.clearExpiredByTimeout) {
|
|
46
|
+
this._clearExpiredHourAgo();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_getRateLimiterRes(rlKey, changedPoints, result) {
|
|
51
|
+
const res = new RateLimiterRes();
|
|
52
|
+
|
|
53
|
+
let doc = result;
|
|
54
|
+
res.isFirstInDuration = doc.points === changedPoints;
|
|
55
|
+
res.consumedPoints = doc.points;
|
|
56
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
|
|
57
|
+
res.msBeforeNext = doc.expire !== null
|
|
58
|
+
? Math.max(new Date(doc.expire).getTime() - Date.now(), 0)
|
|
59
|
+
: -1;
|
|
60
|
+
|
|
61
|
+
return res;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async _upsert(key, points, msDuration, forceExpire = false) {
|
|
65
|
+
if (!this.drizzleClient) {
|
|
66
|
+
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { eq , sql } = getDrizzleOperators();
|
|
70
|
+
const now = new Date();
|
|
71
|
+
const newExpire = msDuration > 0 ? new Date(now.getTime() + msDuration) : null;
|
|
72
|
+
|
|
73
|
+
const query = await this.drizzleClient.transaction(async (tx) => {
|
|
74
|
+
const [existingRecord] = await tx
|
|
75
|
+
.select()
|
|
76
|
+
.from(this.schema)
|
|
77
|
+
.where(eq(this.schema.key, key))
|
|
78
|
+
.limit(1);
|
|
79
|
+
|
|
80
|
+
const shouldUpdateExpire =
|
|
81
|
+
forceExpire ||
|
|
82
|
+
!existingRecord?.expire ||
|
|
83
|
+
existingRecord?.expire <= now ||
|
|
84
|
+
newExpire === null;
|
|
85
|
+
|
|
86
|
+
const [data] = await tx
|
|
87
|
+
.insert(this.schema)
|
|
88
|
+
.values({
|
|
89
|
+
key,
|
|
90
|
+
points,
|
|
91
|
+
expire: newExpire,
|
|
92
|
+
})
|
|
93
|
+
.onConflictDoUpdate({
|
|
94
|
+
target: this.schema.key,
|
|
95
|
+
set: {
|
|
96
|
+
points: !shouldUpdateExpire
|
|
97
|
+
? sql`${this.schema.points} + ${points}`
|
|
98
|
+
: points,
|
|
99
|
+
...(shouldUpdateExpire && { expire: newExpire }),
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
.returning();
|
|
103
|
+
|
|
104
|
+
return data;
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return query
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async _get(rlKey) {
|
|
111
|
+
if (!this.drizzleClient) {
|
|
112
|
+
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { and, or, gt, eq, isNull } = getDrizzleOperators();
|
|
116
|
+
|
|
117
|
+
const [response] = await this.drizzleClient
|
|
118
|
+
.select()
|
|
119
|
+
.from(this.schema)
|
|
120
|
+
.where(
|
|
121
|
+
and(
|
|
122
|
+
eq(this.schema.key, rlKey),
|
|
123
|
+
or(gt(this.schema.expire, new Date()), isNull(this.schema.expire))
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
.limit(1);
|
|
127
|
+
|
|
128
|
+
return response || null;
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async _delete(rlKey) {
|
|
133
|
+
if (!this.drizzleClient) {
|
|
134
|
+
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { eq } = getDrizzleOperators();
|
|
138
|
+
|
|
139
|
+
const [result] = await this.drizzleClient
|
|
140
|
+
.delete(this.schema)
|
|
141
|
+
.where(eq(this.schema.key, rlKey))
|
|
142
|
+
.returning({ key: this.schema.key });
|
|
143
|
+
|
|
144
|
+
return !!result?.key
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_clearExpiredHourAgo() {
|
|
148
|
+
if (this._clearExpiredTimeoutId) {
|
|
149
|
+
clearTimeout(this._clearExpiredTimeoutId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { lt } = getDrizzleOperators();
|
|
153
|
+
|
|
154
|
+
this._clearExpiredTimeoutId = setTimeout(async () => {
|
|
155
|
+
try {
|
|
156
|
+
await this.drizzleClient
|
|
157
|
+
.delete(this.schema)
|
|
158
|
+
.where(lt(this.schema.expire, new Date(Date.now() - EXPIRED_THRESHOLD_MS)));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.warn('Failed to clear expired records:', error);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this._clearExpiredHourAgo();
|
|
164
|
+
}, CLEANUP_INTERVAL_MS);
|
|
165
|
+
|
|
166
|
+
this._clearExpiredTimeoutId.unref();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = RateLimiterDrizzle;
|
package/lib/index.d.ts
CHANGED
|
@@ -241,6 +241,10 @@ interface IRateLimiterStoreNoAutoExpiryOptions extends IRateLimiterStoreOptions
|
|
|
241
241
|
clearExpiredByTimeout?: boolean;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
interface IRateLimiterStoreNoAutoExpiryOptionsAndSchema extends IRateLimiterStoreNoAutoExpiryOptions {
|
|
245
|
+
schema: any;
|
|
246
|
+
}
|
|
247
|
+
|
|
244
248
|
interface IRateLimiterMongoOptions extends IRateLimiterStoreOptions {
|
|
245
249
|
indexKeyPrefix?: {
|
|
246
250
|
[key: string]: any;
|
|
@@ -366,6 +370,10 @@ export class RateLimiterPrisma extends RateLimiterStoreAbstract {
|
|
|
366
370
|
constructor(opts: IRateLimiterStoreNoAutoExpiryOptions, cb?: ICallbackReady);
|
|
367
371
|
}
|
|
368
372
|
|
|
373
|
+
export class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
374
|
+
constructor(opts: IRateLimiterStoreNoAutoExpiryOptionsAndSchema, cb?: ICallbackReady);
|
|
375
|
+
}
|
|
376
|
+
|
|
369
377
|
export class RateLimiterMemcache extends RateLimiterStoreAbstract { }
|
|
370
378
|
|
|
371
379
|
export class RateLimiterUnion {
|
|
@@ -439,7 +447,7 @@ interface IRateLimiterValkeyGlideOptions extends IRateLimiterStoreOptions {
|
|
|
439
447
|
* - KEYS[1]: The key being rate limited
|
|
440
448
|
* - ARGV[1]: Points to consume (as string, use tonumber() to convert)
|
|
441
449
|
* - ARGV[2]: Duration in seconds (as string, use tonumber() to convert)
|
|
442
|
-
*
|
|
450
|
+
*
|
|
443
451
|
* Must return an array with exactly two elements:
|
|
444
452
|
* - [0]: Consumed points (number)
|
|
445
453
|
* - [1]: TTL in milliseconds (number)
|
|
@@ -449,7 +457,7 @@ interface IRateLimiterValkeyGlideOptions extends IRateLimiterStoreOptions {
|
|
|
449
457
|
/**
|
|
450
458
|
* Custom name for the function library, defaults to 'ratelimiter'.
|
|
451
459
|
* The name is used to identify the library of the Lua function.
|
|
452
|
-
* A custom name should be used only if you want to use different
|
|
460
|
+
* A custom name should be used only if you want to use different
|
|
453
461
|
* libraries for different rate limiters.
|
|
454
462
|
* @default 'ratelimiter'
|
|
455
463
|
*/
|
|
@@ -462,9 +470,9 @@ interface IRateLimiterValkeyGlideOptions extends IRateLimiterStoreOptions {
|
|
|
462
470
|
export class RateLimiterValkeyGlide extends RateLimiterStoreAbstract {
|
|
463
471
|
/**
|
|
464
472
|
* Creates a new instance of RateLimiterValkeyGlide
|
|
465
|
-
*
|
|
473
|
+
*
|
|
466
474
|
* @param opts Configuration options
|
|
467
|
-
*
|
|
475
|
+
*
|
|
468
476
|
* @example
|
|
469
477
|
* ```typescript
|
|
470
478
|
* // Basic usage
|
|
@@ -473,24 +481,24 @@ export class RateLimiterValkeyGlide extends RateLimiterStoreAbstract {
|
|
|
473
481
|
* points: 5,
|
|
474
482
|
* duration: 1
|
|
475
483
|
* });
|
|
476
|
-
*
|
|
484
|
+
*
|
|
477
485
|
* // With custom Lua function
|
|
478
486
|
* const customScript = `local key = KEYS[1]
|
|
479
487
|
* local pointsToConsume = tonumber(ARGV[1]) or 0
|
|
480
488
|
* local secDuration = tonumber(ARGV[2]) or 0
|
|
481
|
-
*
|
|
489
|
+
*
|
|
482
490
|
* -- Custom implementation
|
|
483
491
|
* -- ...
|
|
484
|
-
*
|
|
492
|
+
*
|
|
485
493
|
* -- Must return exactly two values: [consumed_points, ttl_in_ms]
|
|
486
494
|
* return {consumed, ttl}`;
|
|
487
|
-
*
|
|
495
|
+
*
|
|
488
496
|
* const rateLimiter = new RateLimiterValkeyGlide({
|
|
489
497
|
* storeClient: glideClient,
|
|
490
498
|
* points: 5,
|
|
491
499
|
* customFunction: customScript
|
|
492
500
|
* });
|
|
493
|
-
*
|
|
501
|
+
*
|
|
494
502
|
* // With insurance limiter
|
|
495
503
|
* const rateLimiter = new RateLimiterValkeyGlide({
|
|
496
504
|
* storeClient: primaryGlideClient,
|
|
@@ -508,7 +516,7 @@ export class RateLimiterValkeyGlide extends RateLimiterStoreAbstract {
|
|
|
508
516
|
/**
|
|
509
517
|
* Close the rate limiter and release resources
|
|
510
518
|
* Note: The method won't close the Valkey client, as it may be shared with other instances.
|
|
511
|
-
*
|
|
519
|
+
*
|
|
512
520
|
* @returns Promise that resolves when the rate limiter is closed
|
|
513
521
|
*/
|
|
514
522
|
close(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rate-limiter-flexible",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"valkey-cluster:down": "docker-compose -f docker-compose.valkey-cluster.yml down -v",
|
|
11
11
|
"test:valkey-cluster": "VALKEY_CLUSTER_PORT=7001 mocha test/RateLimiterValkeyGlide.test.js -- -g 'RateLimiterValkeyGlide with cluster client'",
|
|
12
12
|
"prisma:postgres": "prisma generate --schema=./test/RateLimiterPrisma/Postgres/schema.prisma && prisma db push --schema=./test/RateLimiterPrisma/Postgres/schema.prisma",
|
|
13
|
-
"
|
|
13
|
+
"drizzle:postgres": "cd ./test/RateLimiterDrizzle/Postgres && drizzle-kit push",
|
|
14
|
+
"test": "npm run prisma:postgres && npm run drizzle:postgres && nyc --reporter=html --reporter=text mocha",
|
|
14
15
|
"debug-test": "mocha --inspect-brk lib/**/**.test.js",
|
|
15
16
|
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
|
16
17
|
"eslint": "eslint --quiet lib/**/**.js test/**/**.js",
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"mysql",
|
|
35
36
|
"postgres",
|
|
36
37
|
"prisma",
|
|
38
|
+
"drizzle",
|
|
37
39
|
"koa",
|
|
38
40
|
"express",
|
|
39
41
|
"hapi",
|
|
@@ -53,15 +55,18 @@
|
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"@aws-sdk/client-dynamodb": "^3.431.0",
|
|
55
57
|
"@prisma/client": "^5.8.0",
|
|
58
|
+
"@valkey/valkey-glide": "^1.3.1",
|
|
56
59
|
"better-sqlite3": "^11.9.0",
|
|
57
60
|
"chai": "^4.1.2",
|
|
58
61
|
"coveralls": "^3.0.1",
|
|
59
|
-
"
|
|
62
|
+
"drizzle-kit": "^0.31.4",
|
|
63
|
+
"drizzle-orm": "^0.44.3",
|
|
60
64
|
"eslint": "^4.19.1",
|
|
61
65
|
"eslint-config-airbnb-base": "^12.1.0",
|
|
62
66
|
"eslint-plugin-import": "^2.7.0",
|
|
63
67
|
"eslint-plugin-node": "^6.0.1",
|
|
64
68
|
"eslint-plugin-security": "^1.4.0",
|
|
69
|
+
"etcd3": "^1.1.2",
|
|
65
70
|
"ioredis": "^5.3.2",
|
|
66
71
|
"iovalkey": "^0.3.1",
|
|
67
72
|
"istanbul": "^1.1.0-alpha.1",
|
|
@@ -69,12 +74,12 @@
|
|
|
69
74
|
"memcached-mock": "^0.1.0",
|
|
70
75
|
"mocha": "^10.2.0",
|
|
71
76
|
"nyc": "^15.1.0",
|
|
77
|
+
"pg": "^8.16.3",
|
|
72
78
|
"prisma": "^5.8.0",
|
|
73
79
|
"redis": "^4.6.8",
|
|
74
80
|
"redis-mock": "^0.48.0",
|
|
75
81
|
"sinon": "^17.0.1",
|
|
76
|
-
"sqlite3": "^5.1.7"
|
|
77
|
-
"@valkey/valkey-glide": "^1.3.1"
|
|
82
|
+
"sqlite3": "^5.1.7"
|
|
78
83
|
},
|
|
79
84
|
"browser": {
|
|
80
85
|
"cluster": false,
|