@burdenoff/be-sdk 2026.716.1 → 2026.716.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.
@@ -439,8 +439,31 @@ var MemoryCacheProvider = class {
439
439
  };
440
440
 
441
441
  // src/cache/implementations/valkey/ValkeyGlideProvider.ts
442
- var CONNECT_RETRY_COOLDOWN_MS = 8e3;
443
- var CONNECT_ATTEMPT_TIMEOUT_MS = 2500;
442
+ function readPositiveIntEnv(name, fallback) {
443
+ const raw = process.env[name];
444
+ if (!raw) return fallback;
445
+ const parsed = parseInt(raw, 10);
446
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
447
+ }
448
+ __name(readPositiveIntEnv, "readPositiveIntEnv");
449
+ var CONNECT_RETRY_COOLDOWN_MS = readPositiveIntEnv("VALKEY_CONNECT_RETRY_COOLDOWN_MS", 8e3);
450
+ var CONNECT_ATTEMPT_TIMEOUT_MS = readPositiveIntEnv("VALKEY_CONNECT_ATTEMPT_TIMEOUT_MS", 4500);
451
+ var CONNECTION_TIMEOUT_MS = readPositiveIntEnv("VALKEY_CONNECTION_TIMEOUT_MS", 4e3);
452
+ var REQUEST_TIMEOUT_MS = readPositiveIntEnv("VALKEY_REQUEST_TIMEOUT_MS", 2e3);
453
+ var MAX_CONSECUTIVE_OP_FAILURES = readPositiveIntEnv("VALKEY_MAX_CONSECUTIVE_OP_FAILURES", 3);
454
+ function isConnectionLevelError(error) {
455
+ if (!(error instanceof Error)) return false;
456
+ const name = error.name;
457
+ if (name === "ClosingError" || name === "ConnectionError" || name === "TimeoutError") {
458
+ return true;
459
+ }
460
+ const message = error.message.toLowerCase();
461
+ if (message.includes("cooldown") || message.includes("budget")) {
462
+ return false;
463
+ }
464
+ return /connection|closing|closed|timed out|timeout|disconnect|socket|econnreset|broken pipe|not connected/.test(message);
465
+ }
466
+ __name(isConnectionLevelError, "isConnectionLevelError");
444
467
  var ValkeyGlideProvider = class {
445
468
  static {
446
469
  __name(this, "ValkeyGlideProvider");
@@ -457,12 +480,23 @@ var ValkeyGlideProvider = class {
457
480
  // Timestamp (ms) of the last failed connect attempt; 0 when the last attempt
458
481
  // succeeded. Drives the connect circuit-breaker in ensureConnected().
459
482
  lastConnectFailureAt = 0;
483
+ // Count of consecutive connection-level operation failures on an already-
484
+ // "connected" client, tracked across every command (any success resets it,
485
+ // any op's connection error increments it). When it reaches
486
+ // MAX_CONSECUTIVE_OP_FAILURES the wedged client is torn down so the next op
487
+ // rebuilds a fresh one instead of reusing a socket GLIDE's internal reconnect
488
+ // cannot recover. See noteOpError().
489
+ consecutiveOpFailures = 0;
460
490
  config;
461
491
  // Mutable: when a URL like `…/4` lands on Azure Managed Redis we fold the
462
492
  // DB index into the namespace at connect time to preserve per-service
463
- // isolation on the forced-DB-0 connection. Not const, but only ever
464
- // appended to once during connect().
493
+ // isolation on the forced-DB-0 connection. Appended exactly once across the
494
+ // instance's lifetime, guarded by `dbNamespaceSuffixApplied` (connect() can
495
+ // run repeatedly now that the self-heal path rebuilds the client).
465
496
  namespace;
497
+ // Set once the Azure DB-index `:db{N}` suffix has been folded into
498
+ // `namespace`, so reconnects do not re-append it.
499
+ dbNamespaceSuffixApplied = false;
466
500
  defaultTTL;
467
501
  enableLogging;
468
502
  // Statistics
@@ -616,8 +650,11 @@ var ValkeyGlideProvider = class {
616
650
  databaseFromUrl = parseInt(dbMatch[1], 10);
617
651
  const isAzureManagedRedis = /\.redis\.azure\.net$/i.test(host);
618
652
  if (isAzureManagedRedis && databaseFromUrl > 0) {
619
- logger.warn(`[ValkeyGlideProvider] Azure Managed Redis only supports DB 0; ignoring /${String(databaseFromUrl)} in URL and folding it into namespace.`);
620
- this.namespace = `${this.namespace}:db${String(databaseFromUrl)}`;
653
+ if (!this.dbNamespaceSuffixApplied) {
654
+ logger.warn(`[ValkeyGlideProvider] Azure Managed Redis only supports DB 0; ignoring /${String(databaseFromUrl)} in URL and folding it into namespace.`);
655
+ this.namespace = `${this.namespace}:db${String(databaseFromUrl)}`;
656
+ this.dbNamespaceSuffixApplied = true;
657
+ }
621
658
  databaseFromUrl = 0;
622
659
  }
623
660
  }
@@ -637,7 +674,10 @@ var ValkeyGlideProvider = class {
637
674
  } : void 0,
638
675
  clientName: this.config.name || `valkey-glide-${this.namespace}`,
639
676
  databaseId: this.config.db ?? databaseFromUrl,
640
- connectionTimeout: this.config.connectionTimeout,
677
+ // Bound every command (incl. reconnection during the request) so a
678
+ // dropped/reconnecting socket fails fast to a cache miss instead of
679
+ // hanging the calling resolver. THE fix for the "glide hangs" failure.
680
+ requestTimeout: REQUEST_TIMEOUT_MS,
641
681
  // Honour the configured reconnect tuning. Previously
642
682
  // maxReconnectAttempts/reconnectTimeWait were read into config but never
643
683
  // handed to GLIDE, so the client used its built-in default backoff. A
@@ -648,7 +688,13 @@ var ValkeyGlideProvider = class {
648
688
  factor: this.config.reconnectTimeWait ?? 1e3,
649
689
  exponentBase: 2
650
690
  },
651
- readFrom: "primary"
691
+ readFrom: "primary",
692
+ // GLIDE 2.x reads connectionTimeout from advancedConfiguration; a
693
+ // top-level value is ignored. Bounds the initial connect and every
694
+ // internal reconnect.
695
+ advancedConfiguration: {
696
+ connectionTimeout: this.config.connectionTimeout ?? CONNECTION_TIMEOUT_MS
697
+ }
652
698
  };
653
699
  if (this.config.cluster === true) {
654
700
  if (!GlideClusterClient) {
@@ -660,6 +706,7 @@ var ValkeyGlideProvider = class {
660
706
  duration_in_sec: 30
661
707
  },
662
708
  advancedConfiguration: {
709
+ ...baseClientConfig.advancedConfiguration,
663
710
  refreshTopologyFromInitialNodes: true
664
711
  }
665
712
  }));
@@ -677,6 +724,7 @@ var ValkeyGlideProvider = class {
677
724
  duration_in_sec: 30
678
725
  },
679
726
  advancedConfiguration: {
727
+ ...baseClientConfig.advancedConfiguration,
680
728
  refreshTopologyFromInitialNodes: true
681
729
  }
682
730
  }));
@@ -688,6 +736,7 @@ var ValkeyGlideProvider = class {
688
736
  }
689
737
  this.connected = true;
690
738
  this.lastConnectFailureAt = 0;
739
+ this.consecutiveOpFailures = 0;
691
740
  if (this.enableLogging) {
692
741
  logger.info(`[ValkeyGlideProvider:${this.namespace}] Connected to Valkey`, {
693
742
  host,
@@ -718,6 +767,47 @@ var ValkeyGlideProvider = class {
718
767
  }
719
768
  }
720
769
  /**
770
+ * A command completed — the connection is proven alive, so clear the
771
+ * teardown counter. Called from every command's success path.
772
+ */
773
+ recordOpSuccess() {
774
+ if (this.consecutiveOpFailures !== 0) {
775
+ this.consecutiveOpFailures = 0;
776
+ }
777
+ }
778
+ /**
779
+ * Record an operation error and, if the client looks wedged (enough
780
+ * consecutive connection-level failures), tear it down so the next op builds a
781
+ * fresh one. Non-connection errors (bad command, serialization) are ignored so
782
+ * they never trip the teardown.
783
+ */
784
+ noteOpError(error) {
785
+ if (!isConnectionLevelError(error)) {
786
+ return;
787
+ }
788
+ this.consecutiveOpFailures += 1;
789
+ if (this.consecutiveOpFailures >= MAX_CONSECUTIVE_OP_FAILURES) {
790
+ this.resetClientForReconnect();
791
+ }
792
+ }
793
+ /**
794
+ * Drop the current client so the next ensureConnected() creates a new one.
795
+ * Arms the connect circuit-breaker so ops fast-fail (cache miss) during the
796
+ * cooldown rather than racing a fresh connect on every call, and closes the
797
+ * dead client in the background so its socket is not leaked.
798
+ */
799
+ resetClientForReconnect() {
800
+ const dead = this.client;
801
+ this.client = null;
802
+ this.connected = false;
803
+ this.consecutiveOpFailures = 0;
804
+ this.lastConnectFailureAt = Date.now();
805
+ logger.warn(`[ValkeyGlideProvider:${this.namespace}] Tearing down wedged client after ${String(MAX_CONSECUTIVE_OP_FAILURES)} consecutive connection failures; rebuilding on next op`);
806
+ if (dead) {
807
+ void dead.close().catch(() => void 0);
808
+ }
809
+ }
810
+ /**
721
811
  * Check if connected
722
812
  */
723
813
  isConnected() {
@@ -776,6 +866,7 @@ var ValkeyGlideProvider = class {
776
866
  const memoryMatch = memoryRegex.exec(info);
777
867
  const memoryUsed = memoryMatch && memoryMatch[1] ? parseInt(memoryMatch[1], 10) : void 0;
778
868
  const keyCount = await client.dbsize();
869
+ this.recordOpSuccess();
779
870
  return {
780
871
  hits: this.stats.hits,
781
872
  misses: this.stats.misses,
@@ -790,6 +881,7 @@ var ValkeyGlideProvider = class {
790
881
  }
791
882
  };
792
883
  } catch (error) {
884
+ this.noteOpError(error);
793
885
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Failed to get statistics:`, error);
794
886
  return {
795
887
  hits: this.stats.hits,
@@ -818,6 +910,7 @@ var ValkeyGlideProvider = class {
818
910
  const client = await this.ensureConnected();
819
911
  const cacheKey = this.buildKey(key);
820
912
  const value = await client.get(cacheKey);
913
+ this.recordOpSuccess();
821
914
  if (value === null) {
822
915
  this.stats.misses++;
823
916
  if (this.enableLogging) {
@@ -837,6 +930,7 @@ var ValkeyGlideProvider = class {
837
930
  return entry.data;
838
931
  } catch (error) {
839
932
  this.stats.errors++;
933
+ this.noteOpError(error);
840
934
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error getting cache key:`, error);
841
935
  return null;
842
936
  }
@@ -861,6 +955,7 @@ var ValkeyGlideProvider = class {
861
955
  count: ttlSeconds
862
956
  }
863
957
  });
958
+ this.recordOpSuccess();
864
959
  this.stats.sets++;
865
960
  if (this.enableLogging) {
866
961
  logger.debug(`[ValkeyGlideProvider:${this.namespace}] Cache set`, {
@@ -871,6 +966,7 @@ var ValkeyGlideProvider = class {
871
966
  return true;
872
967
  } catch (error) {
873
968
  this.stats.errors++;
969
+ this.noteOpError(error);
874
970
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error setting cache key:`, error);
875
971
  return false;
876
972
  }
@@ -885,6 +981,7 @@ var ValkeyGlideProvider = class {
885
981
  const result = await client.del([
886
982
  cacheKey
887
983
  ]);
984
+ this.recordOpSuccess();
888
985
  this.stats.deletes++;
889
986
  if (this.enableLogging) {
890
987
  logger.debug(`[ValkeyGlideProvider:${this.namespace}] Cache delete`, {
@@ -895,6 +992,7 @@ var ValkeyGlideProvider = class {
895
992
  return result > 0;
896
993
  } catch (error) {
897
994
  this.stats.errors++;
995
+ this.noteOpError(error);
898
996
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error deleting cache key:`, error);
899
997
  return false;
900
998
  }
@@ -909,9 +1007,11 @@ var ValkeyGlideProvider = class {
909
1007
  const exists = await client.exists([
910
1008
  cacheKey
911
1009
  ]);
1010
+ this.recordOpSuccess();
912
1011
  return exists === 1;
913
1012
  } catch (error) {
914
1013
  this.stats.errors++;
1014
+ this.noteOpError(error);
915
1015
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error checking cache key:`, error);
916
1016
  return false;
917
1017
  }
@@ -923,9 +1023,12 @@ var ValkeyGlideProvider = class {
923
1023
  try {
924
1024
  const client = await this.ensureConnected();
925
1025
  const cacheKey = this.buildKey(key);
926
- return await client.ttl(cacheKey);
1026
+ const result = await client.ttl(cacheKey);
1027
+ this.recordOpSuccess();
1028
+ return result;
927
1029
  } catch (error) {
928
1030
  this.stats.errors++;
1031
+ this.noteOpError(error);
929
1032
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error getting TTL:`, error);
930
1033
  return -2;
931
1034
  }
@@ -937,9 +1040,12 @@ var ValkeyGlideProvider = class {
937
1040
  try {
938
1041
  const client = await this.ensureConnected();
939
1042
  const cacheKey = this.buildKey(key);
940
- return await client.expire(cacheKey, ttl);
1043
+ const result = await client.expire(cacheKey, ttl);
1044
+ this.recordOpSuccess();
1045
+ return result;
941
1046
  } catch (error) {
942
1047
  this.stats.errors++;
1048
+ this.noteOpError(error);
943
1049
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error setting expiration:`, error);
944
1050
  return false;
945
1051
  }
@@ -956,6 +1062,7 @@ var ValkeyGlideProvider = class {
956
1062
  const client = await this.ensureConnected();
957
1063
  const cacheKeys = keys.map((key) => this.buildKey(key));
958
1064
  const values = await client.mget(cacheKeys);
1065
+ this.recordOpSuccess();
959
1066
  for (let i = 0; i < keys.length; i++) {
960
1067
  const key = keys[i];
961
1068
  if (!key) continue;
@@ -976,6 +1083,7 @@ var ValkeyGlideProvider = class {
976
1083
  return result;
977
1084
  } catch (error) {
978
1085
  this.stats.errors++;
1086
+ this.noteOpError(error);
979
1087
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error in batch get:`, error);
980
1088
  return result;
981
1089
  }
@@ -1002,6 +1110,7 @@ var ValkeyGlideProvider = class {
1002
1110
  keyValuePairs[cacheKey] = JSON.stringify(entry);
1003
1111
  }
1004
1112
  await client.mset(keyValuePairs);
1113
+ this.recordOpSuccess();
1005
1114
  for (const cacheKey of Object.keys(keyValuePairs)) {
1006
1115
  await client.expire(cacheKey, ttlSeconds);
1007
1116
  }
@@ -1015,6 +1124,7 @@ var ValkeyGlideProvider = class {
1015
1124
  return true;
1016
1125
  } catch (error) {
1017
1126
  this.stats.errors++;
1127
+ this.noteOpError(error);
1018
1128
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error in batch set:`, error);
1019
1129
  return false;
1020
1130
  }
@@ -1030,6 +1140,7 @@ var ValkeyGlideProvider = class {
1030
1140
  const client = await this.ensureConnected();
1031
1141
  const cacheKeys = keys.map((key) => this.buildKey(key));
1032
1142
  const result = await client.del(cacheKeys);
1143
+ this.recordOpSuccess();
1033
1144
  this.stats.deletes += result;
1034
1145
  if (this.enableLogging) {
1035
1146
  logger.debug(`[ValkeyGlideProvider:${this.namespace}] Batch delete`, {
@@ -1040,6 +1151,7 @@ var ValkeyGlideProvider = class {
1040
1151
  return result;
1041
1152
  } catch (error) {
1042
1153
  this.stats.errors++;
1154
+ this.noteOpError(error);
1043
1155
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error in batch delete:`, error);
1044
1156
  return 0;
1045
1157
  }
@@ -1052,6 +1164,7 @@ var ValkeyGlideProvider = class {
1052
1164
  const client = await this.ensureConnected();
1053
1165
  const searchPattern = this.buildKey(pattern);
1054
1166
  const keys = await this.getKeys(searchPattern);
1167
+ this.recordOpSuccess();
1055
1168
  if (keys.length === 0) {
1056
1169
  return 0;
1057
1170
  }
@@ -1065,6 +1178,7 @@ var ValkeyGlideProvider = class {
1065
1178
  return result;
1066
1179
  } catch (error) {
1067
1180
  this.stats.errors++;
1181
+ this.noteOpError(error);
1068
1182
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error clearing pattern:`, error);
1069
1183
  return 0;
1070
1184
  }
@@ -1077,6 +1191,7 @@ var ValkeyGlideProvider = class {
1077
1191
  await this.ensureConnected();
1078
1192
  const searchPattern = this.buildKey(options.pattern);
1079
1193
  const keys = await this.getKeys(searchPattern);
1194
+ this.recordOpSuccess();
1080
1195
  const prefix = `${this.namespace}:`;
1081
1196
  const cleanKeys = keys.map((key) => key.replace(prefix, ""));
1082
1197
  const limitedKeys = options.limit ? cleanKeys.slice(0, options.limit) : cleanKeys;
@@ -1087,6 +1202,7 @@ var ValkeyGlideProvider = class {
1087
1202
  };
1088
1203
  } catch (error) {
1089
1204
  this.stats.errors++;
1205
+ this.noteOpError(error);
1090
1206
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error getting keys:`, error);
1091
1207
  return {
1092
1208
  keys: [],
@@ -1103,6 +1219,7 @@ var ValkeyGlideProvider = class {
1103
1219
  const client = await this.ensureConnected();
1104
1220
  const pattern = this.buildKey("*");
1105
1221
  const keys = await this.getKeys(pattern);
1222
+ this.recordOpSuccess();
1106
1223
  if (keys.length > 0) {
1107
1224
  await client.del(keys);
1108
1225
  if (this.enableLogging) {
@@ -1112,6 +1229,7 @@ var ValkeyGlideProvider = class {
1112
1229
  return true;
1113
1230
  } catch (error) {
1114
1231
  this.stats.errors++;
1232
+ this.noteOpError(error);
1115
1233
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error clearing cache:`, error);
1116
1234
  return false;
1117
1235
  }
@@ -1123,9 +1241,12 @@ var ValkeyGlideProvider = class {
1123
1241
  try {
1124
1242
  const client = await this.ensureConnected();
1125
1243
  const cacheKey = this.buildKey(key);
1126
- return await client.incrBy(cacheKey, amount);
1244
+ const result = await client.incrBy(cacheKey, amount);
1245
+ this.recordOpSuccess();
1246
+ return result;
1127
1247
  } catch (error) {
1128
1248
  this.stats.errors++;
1249
+ this.noteOpError(error);
1129
1250
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error incrementing:`, error);
1130
1251
  throw error;
1131
1252
  }
@@ -1137,9 +1258,12 @@ var ValkeyGlideProvider = class {
1137
1258
  try {
1138
1259
  const client = await this.ensureConnected();
1139
1260
  const cacheKey = this.buildKey(key);
1140
- return await client.decrBy(cacheKey, amount);
1261
+ const result = await client.decrBy(cacheKey, amount);
1262
+ this.recordOpSuccess();
1263
+ return result;
1141
1264
  } catch (error) {
1142
1265
  this.stats.errors++;
1266
+ this.noteOpError(error);
1143
1267
  logger.error(`[ValkeyGlideProvider:${this.namespace}] Error decrementing:`, error);
1144
1268
  throw error;
1145
1269
  }
@@ -1475,5 +1599,5 @@ async function closeAllCaches() {
1475
1599
  __name(closeAllCaches, "closeAllCaches");
1476
1600
 
1477
1601
  export { CacheFactory, CacheProviderType, MemoryCacheProvider, ValkeyGlideProvider, cache_exports, closeAllCaches, createCache, getCache, getValkeyClient };
1478
- //# sourceMappingURL=chunk-CAI5GJJ6.js.map
1479
- //# sourceMappingURL=chunk-CAI5GJJ6.js.map
1602
+ //# sourceMappingURL=chunk-CJCO3OWF.js.map
1603
+ //# sourceMappingURL=chunk-CJCO3OWF.js.map