@veryfront/ext-redis 0.1.1190 → 0.1.1192
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/esm/index.d.ts +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/rate-limit-store.d.ts +1 -0
- package/esm/rate-limit-store.d.ts.map +1 -1
- package/esm/rate-limit-store.js +39 -23
- package/package.json +2 -2
package/esm/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export default extRedis;
|
|
|
10
10
|
export { RedisMemory } from "./agent-memory.js";
|
|
11
11
|
export { createRedisCacheAdministration } from "./cache-administration.js";
|
|
12
12
|
export { RedisCacheBackend } from "./cache-backend.js";
|
|
13
|
-
export { RedisRateLimitStore } from "./rate-limit-store.js";
|
|
13
|
+
export { type RedisRateLimitOptions, RedisRateLimitStore } from "./rate-limit-store.js";
|
|
14
14
|
export { RedisCacheStore } from "./render-cache-store.js";
|
|
15
15
|
export { startProxyRoutingInvalidationBus } from "./routing-invalidation-bus.js";
|
|
16
16
|
export { createRedisRuntimeProvider } from "./redis-runtime-provider.js";
|
package/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAOnE,QAAA,MAAM,QAAQ,EAAE,gBAqCf,CAAC;AAEF,eAAe,QAAQ,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAOnE,QAAA,MAAM,QAAQ,EAAE,gBAqCf,CAAC;AAEF,eAAe,QAAQ,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,KAAK,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,yBAAyB,EACzB,iCAAiC,EACjC,oBAAoB,GACrB,MAAM,kCAAkC,CAAC"}
|
|
@@ -30,6 +30,7 @@ export declare class RedisRateLimitStore implements RateLimitStore {
|
|
|
30
30
|
private loadClientFactory;
|
|
31
31
|
private disconnectBestEffort;
|
|
32
32
|
private disconnectClient;
|
|
33
|
+
private markDisconnected;
|
|
33
34
|
private withTimeout;
|
|
34
35
|
private connectClient;
|
|
35
36
|
private storageKey;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit-store.d.ts","sourceRoot":"","sources":["../src/rate-limit-store.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"rate-limit-store.d.ts","sourceRoot":"","sources":["../src/rate-limit-store.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,cAAc,EAKpB,MAAM,qDAAqD,CAAC;AA+B7D,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wCAAwC;AACxC,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,uBAAuB,CAA6B;IAC5D,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6C;IAChF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA8B;IAClE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAA0B;IACnE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA8B;IACnE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEhC,OAAO,GAAE,qBAA0B;IA0B/C,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,6BAA6B;YAoBvB,iBAAiB;YAIjB,oBAAoB;IAclC,OAAO,CAAC,gBAAgB;IA2CxB,OAAO,CAAC,gBAAgB;YAMV,WAAW;YAuBX,aAAa;IAgE3B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA6BjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA8B/B"}
|
package/esm/rate-limit-store.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import * as dntShim from "./_dnt.shims.js";
|
|
2
|
-
import { createError, toError } from "veryfront/errors";
|
|
2
|
+
import { createError, isVeryfrontError, TIMEOUT_ERROR, toError } from "veryfront/errors";
|
|
3
3
|
import { serverLogger } from "veryfront/utils/logger";
|
|
4
|
-
import { createClient } from "redis";
|
|
5
|
-
import { MAX_TIMER_DELAY_MS, requireRateLimitKey, requireRateLimitWindowMs, } from "veryfront/extensions/distributed/rate-limit-support";
|
|
4
|
+
import { ClientClosedError, createClient } from "redis";
|
|
5
|
+
import { MAX_TIMER_DELAY_MS, REDIS_RATE_LIMIT_INCREMENT_WITH_TTL_SCRIPT, requireRateLimitKey, requireRateLimitWindowMs, unrefTimer, } from "veryfront/extensions/distributed/rate-limit-support";
|
|
6
6
|
const logger = serverLogger.component("redis-ratelimit");
|
|
7
7
|
const DEFAULT_REDIS_CONNECT_TIMEOUT_MS = 5_000;
|
|
8
8
|
const DEFAULT_REDIS_OPERATION_TIMEOUT_MS = 5_000;
|
|
9
|
-
const INCREMENT_WITH_TTL_SCRIPT = `
|
|
10
|
-
local count = redis.call("INCR", KEYS[1])
|
|
11
|
-
local ttl = redis.call("PTTL", KEYS[1])
|
|
12
|
-
if ttl < 0 then
|
|
13
|
-
redis.call("PEXPIRE", KEYS[1], ARGV[1])
|
|
14
|
-
ttl = tonumber(ARGV[1])
|
|
15
|
-
end
|
|
16
|
-
return { count, ttl }
|
|
17
|
-
`;
|
|
18
9
|
/** Implement redis rate limit store. */
|
|
19
10
|
export class RedisRateLimitStore {
|
|
20
11
|
client = null;
|
|
@@ -93,6 +84,10 @@ export class RedisRateLimitStore {
|
|
|
93
84
|
await this.disconnectClient(client);
|
|
94
85
|
}
|
|
95
86
|
catch (error) {
|
|
87
|
+
if (isAlreadyClosedClientError(error)) {
|
|
88
|
+
this.markDisconnected(client);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
96
91
|
logger.warn("client disconnect failed", {
|
|
97
92
|
errorName: error instanceof Error ? error.name : typeof error,
|
|
98
93
|
});
|
|
@@ -114,27 +109,42 @@ export class RedisRateLimitStore {
|
|
|
114
109
|
this.pendingDisconnectClients.add(client);
|
|
115
110
|
try {
|
|
116
111
|
Promise.resolve(client.disconnect()).then(() => {
|
|
117
|
-
this.
|
|
118
|
-
this.pendingDisconnectClients.delete(client);
|
|
119
|
-
this.disconnectedClients.add(client);
|
|
112
|
+
this.markDisconnected(client);
|
|
120
113
|
resolveDisconnect();
|
|
121
114
|
}, (error) => {
|
|
122
115
|
this.disconnectPromises.delete(client);
|
|
116
|
+
if (isAlreadyClosedClientError(error)) {
|
|
117
|
+
this.markDisconnected(client);
|
|
118
|
+
resolveDisconnect();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
123
121
|
rejectDisconnect(error);
|
|
124
122
|
});
|
|
125
123
|
}
|
|
126
124
|
catch (error) {
|
|
127
125
|
this.disconnectPromises.delete(client);
|
|
128
|
-
|
|
126
|
+
if (isAlreadyClosedClientError(error)) {
|
|
127
|
+
this.markDisconnected(client);
|
|
128
|
+
resolveDisconnect();
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
rejectDisconnect(error);
|
|
132
|
+
}
|
|
129
133
|
}
|
|
130
134
|
return pending;
|
|
131
135
|
}
|
|
136
|
+
markDisconnected(client) {
|
|
137
|
+
this.disconnectPromises.delete(client);
|
|
138
|
+
this.pendingDisconnectClients.delete(client);
|
|
139
|
+
this.disconnectedClients.add(client);
|
|
140
|
+
}
|
|
132
141
|
async withTimeout(operation, timeoutMs, operationName, cancellation) {
|
|
133
142
|
let timeoutId;
|
|
134
143
|
const timeout = new Promise((_, reject) => {
|
|
135
144
|
timeoutId = dntShim.setTimeout(() => {
|
|
136
145
|
reject(createTimeoutError(operationName, timeoutMs));
|
|
137
146
|
}, timeoutMs);
|
|
147
|
+
unrefTimer(timeoutId);
|
|
138
148
|
});
|
|
139
149
|
try {
|
|
140
150
|
return await Promise.race(cancellation === undefined ? [operation, timeout] : [operation, timeout, cancellation]);
|
|
@@ -209,7 +219,7 @@ export class RedisRateLimitStore {
|
|
|
209
219
|
const redisKey = this.storageKey(normalizedKey);
|
|
210
220
|
let result;
|
|
211
221
|
try {
|
|
212
|
-
result = await this.withTimeout(client.eval(
|
|
222
|
+
result = await this.withTimeout(client.eval(REDIS_RATE_LIMIT_INCREMENT_WITH_TTL_SCRIPT, {
|
|
213
223
|
keys: [redisKey],
|
|
214
224
|
arguments: [String(normalizedWindowMs)],
|
|
215
225
|
}), this.operationTimeoutMs, "increment");
|
|
@@ -225,10 +235,11 @@ export class RedisRateLimitStore {
|
|
|
225
235
|
return { count, resetAt: Date.now() + ttl };
|
|
226
236
|
}
|
|
227
237
|
async reset(key) {
|
|
238
|
+
const normalizedKey = requireRateLimitKey(key);
|
|
228
239
|
const client = await this.ensureClient();
|
|
229
240
|
const generation = this.clientGeneration;
|
|
230
241
|
try {
|
|
231
|
-
await this.withTimeout(client.del(this.storageKey(
|
|
242
|
+
await this.withTimeout(client.del(this.storageKey(normalizedKey)), this.operationTimeoutMs, "reset");
|
|
232
243
|
}
|
|
233
244
|
catch (error) {
|
|
234
245
|
if (isTimeoutError(error)) {
|
|
@@ -241,6 +252,9 @@ export class RedisRateLimitStore {
|
|
|
241
252
|
const client = this.client;
|
|
242
253
|
const connectingClient = this.connectingClient;
|
|
243
254
|
const pending = this.clientPromise;
|
|
255
|
+
// Mark the pending connection rejection as observed before cancellation;
|
|
256
|
+
// disconnect work below may otherwise leave an unhandled-rejection window.
|
|
257
|
+
pending?.catch(() => { });
|
|
244
258
|
const cancelPendingConnection = this.cancelPendingConnection;
|
|
245
259
|
const clientsToDisconnect = new Set(this.pendingDisconnectClients);
|
|
246
260
|
if (client)
|
|
@@ -259,7 +273,6 @@ export class RedisRateLimitStore {
|
|
|
259
273
|
disconnectFailed = true;
|
|
260
274
|
disconnectError ??= error;
|
|
261
275
|
})));
|
|
262
|
-
pending?.catch(() => { });
|
|
263
276
|
if (disconnectFailed)
|
|
264
277
|
throw disconnectError;
|
|
265
278
|
}
|
|
@@ -274,12 +287,15 @@ function requireTimeoutMs(value, name) {
|
|
|
274
287
|
return value;
|
|
275
288
|
}
|
|
276
289
|
function createTimeoutError(operationName, timeoutMs) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
290
|
+
return TIMEOUT_ERROR.create({
|
|
291
|
+
detail: `Redis rate limit ${operationName} timed out after ${timeoutMs}ms`,
|
|
292
|
+
});
|
|
280
293
|
}
|
|
281
294
|
function isTimeoutError(error) {
|
|
282
|
-
return error
|
|
295
|
+
return isVeryfrontError(error) && error.slug === TIMEOUT_ERROR.slug;
|
|
296
|
+
}
|
|
297
|
+
function isAlreadyClosedClientError(error) {
|
|
298
|
+
return error instanceof ClientClosedError;
|
|
283
299
|
}
|
|
284
300
|
function parseIncrementResult(result) {
|
|
285
301
|
if (!Array.isArray(result) || result.length < 2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veryfront/ext-redis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1192",
|
|
4
4
|
"description": "Veryfront first-party extension package for ext-redis",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"veryfront",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"redis": "5.11.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"veryfront": "^0.1.
|
|
69
|
+
"veryfront": "^0.1.1192"
|
|
70
70
|
},
|
|
71
71
|
"type": "module",
|
|
72
72
|
"types": "./esm/index.d.ts",
|