@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 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";
@@ -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;AAC5D,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"}
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,EAGpB,MAAM,qDAAqD,CAAC;AAyC7D,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;IAUlC,OAAO,CAAC,gBAAgB;YAmCV,WAAW;YAsBX,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;IAiBjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B/B"}
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"}
@@ -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.disconnectPromises.delete(client);
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
- rejectDisconnect(error);
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(INCREMENT_WITH_TTL_SCRIPT, {
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(requireRateLimitKey(key))), this.operationTimeoutMs, "reset");
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
- const error = new Error(`Redis rate limit ${operationName} timed out after ${timeoutMs}ms`);
278
- error.name = "TimeoutError";
279
- return error;
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 instanceof Error && error.name === "TimeoutError";
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.1190",
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.1190"
69
+ "veryfront": "^0.1.1192"
70
70
  },
71
71
  "type": "module",
72
72
  "types": "./esm/index.d.ts",