@frontmcp/utils 1.8.5 → 1.8.7
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/env/runtime-context.d.ts +6 -2
- package/esm/async-context/browser-async-context.mjs +3 -2
- package/esm/async-context/node-async-context.mjs +3 -2
- package/esm/crypto/browser.mjs +3 -2
- package/esm/crypto/node.mjs +3 -2
- package/esm/env/browser-env.mjs +3 -2
- package/esm/env/node-env.mjs +3 -2
- package/esm/event-emitter/browser-event-emitter.mjs +3 -2
- package/esm/event-emitter/node-event-emitter.mjs +3 -2
- package/esm/index.mjs +106 -12
- package/index.d.ts +2 -2
- package/index.js +106 -10
- package/package.json +2 -2
- package/storage/adapters/index.d.ts +2 -1
- package/storage/adapters/redis.d.ts +21 -1
- package/storage/index.d.ts +4 -2
- package/storage/redis-error-listener.d.ts +27 -0
package/env/runtime-context.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
18
|
* Snapshot of the current runtime environment. Detected once and cached
|
|
19
|
-
* for the lifetime of the process.
|
|
19
|
+
* for the lifetime of the process (`env` is read live from `NODE_ENV`).
|
|
20
20
|
*/
|
|
21
21
|
export interface RuntimeContext {
|
|
22
22
|
/** OS platform: 'darwin', 'linux', 'win32', etc. (issue #417: renamed from platform) */
|
|
@@ -134,7 +134,11 @@ export declare function checkEntryAvailability(availability: EntryAvailability |
|
|
|
134
134
|
export declare function detectRuntimeContext(): RuntimeContext;
|
|
135
135
|
/**
|
|
136
136
|
* Get the current runtime context (lazy singleton).
|
|
137
|
-
* The context is detected on first call and cached for the process lifetime
|
|
137
|
+
* The context is detected on first call and cached for the process lifetime,
|
|
138
|
+
* except `env`, which always reflects the live `NODE_ENV`. A Cloudflare Worker's
|
|
139
|
+
* `[vars]` reach `process.env` on the first request, after module evaluation has
|
|
140
|
+
* already read this context, so a frozen `env` would report `development` in a
|
|
141
|
+
* production deployment.
|
|
138
142
|
*/
|
|
139
143
|
export declare function getRuntimeContext(): RuntimeContext;
|
|
140
144
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/async-context/overlap-error.ts
|
|
4
5
|
var AsyncContextOverlapError = class extends Error {
|
|
5
6
|
constructor(message) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/async-context/node-async-context.ts
|
|
4
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
6
|
|
package/esm/crypto/browser.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/crypto/browser.ts
|
|
4
5
|
import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
|
|
5
6
|
import { hmac } from "@noble/hashes/hmac.js";
|
package/esm/crypto/node.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/crypto/node.ts
|
|
4
5
|
import crypto from "node:crypto";
|
|
5
6
|
|
package/esm/env/browser-env.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/env/edge-scope.ts
|
|
4
5
|
function hasEdgeGlobalScope() {
|
|
5
6
|
if (typeof globalThis === "undefined") return false;
|
package/esm/env/node-env.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/env/edge-scope.ts
|
|
4
5
|
function hasEdgeGlobalScope() {
|
|
5
6
|
if (typeof globalThis === "undefined") return false;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/event-emitter/browser-event-emitter.ts
|
|
4
5
|
var EventEmitter = class {
|
|
5
6
|
events = /* @__PURE__ */ new Map();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
// libs/utils/src/event-emitter/node-event-emitter.ts
|
|
4
5
|
import { EventEmitter } from "events";
|
|
5
6
|
export {
|
package/esm/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const require =
|
|
1
|
+
const __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -2962,6 +2963,41 @@ var init_crypto = __esm({
|
|
|
2962
2963
|
}
|
|
2963
2964
|
});
|
|
2964
2965
|
|
|
2966
|
+
// libs/utils/src/storage/redis-error-listener.ts
|
|
2967
|
+
function attachRedisErrorListener(client, options = {}) {
|
|
2968
|
+
const label = options.label ?? "redis";
|
|
2969
|
+
const intervalMs = options.intervalMs ?? DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
|
|
2970
|
+
let lastLoggedAt = 0;
|
|
2971
|
+
let suppressed = 0;
|
|
2972
|
+
const listener = (error) => {
|
|
2973
|
+
const now = Date.now();
|
|
2974
|
+
if (lastLoggedAt !== 0 && now - lastLoggedAt < intervalMs) {
|
|
2975
|
+
suppressed++;
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const extra = suppressed > 0 ? ` (${suppressed} similar error(s) suppressed)` : "";
|
|
2979
|
+
suppressed = 0;
|
|
2980
|
+
lastLoggedAt = now;
|
|
2981
|
+
const message = `[${label}] Redis connection error: ${error?.message ?? String(error)}${extra}`;
|
|
2982
|
+
if (options.logger) {
|
|
2983
|
+
options.logger.warn(message);
|
|
2984
|
+
} else {
|
|
2985
|
+
console.warn(message);
|
|
2986
|
+
}
|
|
2987
|
+
};
|
|
2988
|
+
client.on("error", listener);
|
|
2989
|
+
return () => {
|
|
2990
|
+
client.removeListener("error", listener);
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
|
|
2994
|
+
var init_redis_error_listener = __esm({
|
|
2995
|
+
"libs/utils/src/storage/redis-error-listener.ts"() {
|
|
2996
|
+
"use strict";
|
|
2997
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
|
|
2965
3001
|
// libs/utils/src/storage/utils/index.ts
|
|
2966
3002
|
var init_utils = __esm({
|
|
2967
3003
|
"libs/utils/src/storage/utils/index.ts"() {
|
|
@@ -2973,7 +3009,8 @@ var init_utils = __esm({
|
|
|
2973
3009
|
// libs/utils/src/storage/adapters/redis.ts
|
|
2974
3010
|
var redis_exports = {};
|
|
2975
3011
|
__export(redis_exports, {
|
|
2976
|
-
RedisStorageAdapter: () => RedisStorageAdapter
|
|
3012
|
+
RedisStorageAdapter: () => RedisStorageAdapter,
|
|
3013
|
+
createRedisClient: () => createRedisClient
|
|
2977
3014
|
});
|
|
2978
3015
|
function getRedisClass() {
|
|
2979
3016
|
try {
|
|
@@ -2982,25 +3019,45 @@ function getRedisClass() {
|
|
|
2982
3019
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2983
3020
|
if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
|
|
2984
3021
|
throw new Error(
|
|
2985
|
-
`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode
|
|
3022
|
+
`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
|
|
3023
|
+
{ cause: error }
|
|
2986
3024
|
);
|
|
2987
3025
|
}
|
|
2988
|
-
throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis"
|
|
3026
|
+
throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
|
|
3027
|
+
cause: error
|
|
3028
|
+
});
|
|
2989
3029
|
}
|
|
2990
3030
|
}
|
|
3031
|
+
function createRedisClient(options) {
|
|
3032
|
+
const RedisClass = getRedisClass();
|
|
3033
|
+
const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
|
|
3034
|
+
const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
|
|
3035
|
+
...baseOptions,
|
|
3036
|
+
host: options.host ?? "localhost",
|
|
3037
|
+
port: options.port ?? 6379,
|
|
3038
|
+
password: options.password,
|
|
3039
|
+
db: options.db ?? 0,
|
|
3040
|
+
tls: options.tls ? {} : void 0
|
|
3041
|
+
});
|
|
3042
|
+
attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
|
|
3043
|
+
return client;
|
|
3044
|
+
}
|
|
2991
3045
|
var RedisStorageAdapter;
|
|
2992
3046
|
var init_redis = __esm({
|
|
2993
3047
|
"libs/utils/src/storage/adapters/redis.ts"() {
|
|
2994
3048
|
"use strict";
|
|
2995
|
-
init_base();
|
|
2996
3049
|
init_errors();
|
|
3050
|
+
init_redis_error_listener();
|
|
2997
3051
|
init_utils();
|
|
3052
|
+
init_base();
|
|
2998
3053
|
RedisStorageAdapter = class extends BaseStorageAdapter {
|
|
2999
3054
|
backendName = "redis";
|
|
3000
3055
|
client;
|
|
3001
3056
|
subscriber;
|
|
3002
3057
|
options;
|
|
3003
3058
|
ownsClient;
|
|
3059
|
+
detachErrorListener;
|
|
3060
|
+
connecting;
|
|
3004
3061
|
keyPrefix;
|
|
3005
3062
|
subscriptionHandlers = /* @__PURE__ */ new Map();
|
|
3006
3063
|
constructor(options = {}) {
|
|
@@ -3030,21 +3087,45 @@ var init_redis = __esm({
|
|
|
3030
3087
|
// ============================================
|
|
3031
3088
|
async connect() {
|
|
3032
3089
|
if (this.connected) return;
|
|
3090
|
+
this.connecting ??= this.openConnection().finally(() => {
|
|
3091
|
+
this.connecting = void 0;
|
|
3092
|
+
});
|
|
3093
|
+
return this.connecting;
|
|
3094
|
+
}
|
|
3095
|
+
async openConnection() {
|
|
3096
|
+
let socketError;
|
|
3097
|
+
const recordSocketError = (error) => {
|
|
3098
|
+
socketError = error;
|
|
3099
|
+
};
|
|
3100
|
+
let client;
|
|
3033
3101
|
try {
|
|
3034
3102
|
if (this.options.client) {
|
|
3035
|
-
|
|
3103
|
+
client = this.options.client;
|
|
3036
3104
|
} else {
|
|
3037
3105
|
const RedisClass = getRedisClass();
|
|
3038
3106
|
if (this.options.url) {
|
|
3039
|
-
|
|
3107
|
+
client = new RedisClass(this.options.url, this.buildRedisOptions());
|
|
3040
3108
|
} else {
|
|
3041
|
-
|
|
3109
|
+
client = new RedisClass(this.buildRedisOptions());
|
|
3042
3110
|
}
|
|
3111
|
+
client.on("error", recordSocketError);
|
|
3043
3112
|
}
|
|
3044
|
-
await
|
|
3113
|
+
await client.ping();
|
|
3114
|
+
this.client = client;
|
|
3045
3115
|
this.connected = true;
|
|
3116
|
+
if (this.ownsClient) {
|
|
3117
|
+
client.removeListener("error", recordSocketError);
|
|
3118
|
+
this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
|
|
3119
|
+
}
|
|
3046
3120
|
} catch (e) {
|
|
3047
|
-
|
|
3121
|
+
if (this.ownsClient && client) {
|
|
3122
|
+
try {
|
|
3123
|
+
client.disconnect();
|
|
3124
|
+
} catch {
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
const cause = socketError ?? (e instanceof Error ? e : void 0);
|
|
3128
|
+
throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
|
|
3048
3129
|
}
|
|
3049
3130
|
}
|
|
3050
3131
|
async disconnect() {
|
|
@@ -3056,6 +3137,8 @@ var init_redis = __esm({
|
|
|
3056
3137
|
if (this.ownsClient && this.client) {
|
|
3057
3138
|
await this.client.quit();
|
|
3058
3139
|
}
|
|
3140
|
+
this.detachErrorListener?.();
|
|
3141
|
+
this.detachErrorListener = void 0;
|
|
3059
3142
|
this.client = void 0;
|
|
3060
3143
|
this.connected = false;
|
|
3061
3144
|
this.subscriptionHandlers.clear();
|
|
@@ -3283,6 +3366,7 @@ var init_redis = __esm({
|
|
|
3283
3366
|
} else {
|
|
3284
3367
|
throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
|
|
3285
3368
|
}
|
|
3369
|
+
attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
|
|
3286
3370
|
subscriber.on("message", (channel, message) => {
|
|
3287
3371
|
const handlers = this.subscriptionHandlers.get(channel);
|
|
3288
3372
|
if (handlers) {
|
|
@@ -4952,7 +5036,13 @@ function detectRuntimeContext() {
|
|
|
4952
5036
|
var cached3;
|
|
4953
5037
|
function getRuntimeContext() {
|
|
4954
5038
|
if (!cached3) {
|
|
4955
|
-
|
|
5039
|
+
const detected = detectRuntimeContext();
|
|
5040
|
+
Object.defineProperty(detected, "env", {
|
|
5041
|
+
get: () => process.env["NODE_ENV"] || "development",
|
|
5042
|
+
enumerable: true,
|
|
5043
|
+
configurable: true
|
|
5044
|
+
});
|
|
5045
|
+
cached3 = detected;
|
|
4956
5046
|
}
|
|
4957
5047
|
return cached3;
|
|
4958
5048
|
}
|
|
@@ -5223,6 +5313,7 @@ function getDetectedStorageType() {
|
|
|
5223
5313
|
}
|
|
5224
5314
|
|
|
5225
5315
|
// libs/utils/src/storage/index.ts
|
|
5316
|
+
init_redis_error_listener();
|
|
5226
5317
|
init_errors();
|
|
5227
5318
|
|
|
5228
5319
|
// libs/utils/src/storage/typed-storage.ts
|
|
@@ -5877,6 +5968,7 @@ export {
|
|
|
5877
5968
|
BaseStorageAdapter,
|
|
5878
5969
|
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
5879
5970
|
DEFAULT_MAX_INPUT_LENGTH,
|
|
5971
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS,
|
|
5880
5972
|
EncryptedBlobError,
|
|
5881
5973
|
EncryptedStorageError,
|
|
5882
5974
|
EncryptedTypedStorage,
|
|
@@ -5909,6 +6001,7 @@ export {
|
|
|
5909
6001
|
assertNode,
|
|
5910
6002
|
asymmetricAlgSchema,
|
|
5911
6003
|
asymmetricKeyDataSchema,
|
|
6004
|
+
attachRedisErrorListener,
|
|
5912
6005
|
awaitOutsideRequest,
|
|
5913
6006
|
base64Decode,
|
|
5914
6007
|
base64Encode,
|
|
@@ -5929,6 +6022,7 @@ export {
|
|
|
5929
6022
|
createKeyPersistenceWithStorage,
|
|
5930
6023
|
createMemoryStorage,
|
|
5931
6024
|
createNamespacedStorage,
|
|
6025
|
+
createRedisClient,
|
|
5932
6026
|
createRootStorage,
|
|
5933
6027
|
createSafeRegExp,
|
|
5934
6028
|
createSecretData,
|
package/index.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export { EventEmitter } from './event-emitter';
|
|
|
26
26
|
export { getEnv, getCwd, isProduction, isDevelopment, getEnvFlag, isDebug, setEnv, isEdgeRuntime, isServerless, supportsAnsi, getRuntimeContext, resetRuntimeContext, detectRuntimeContext, isEntryAvailable, checkEntryAvailability, entryAvailabilitySchema, } from './env';
|
|
27
27
|
export type { RuntimeContext, EntryAvailability } from './env';
|
|
28
28
|
export { analyzePattern, isPatternSafe, createSafeRegExp, safeTest, safeMatch, safeReplace, safeExec, isInputLengthSafe, DEFAULT_MAX_INPUT_LENGTH, REDOS_THRESHOLDS, trimLeading, trimTrailing, trimBoth, trimChars, extractBracedParams, expandTemplate, hasTemplatePlaceholders, collapseChar, collapseWhitespace, type SafeRegexOptions, type PatternAnalysisResult, } from './regex';
|
|
29
|
-
export { createStorage, createMemoryStorage, getDetectedStorageType, NamespacedStorageImpl, createRootStorage, createNamespacedStorage, buildPrefix, NAMESPACE_SEPARATOR, StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError, BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, type FileSystemAdapterOptions, TypedStorage, type TypedStorageOptions, type TypedSetOptions, type TypedSetEntry, EncryptedTypedStorage, type EncryptedTypedStorageOptions, type EncryptedSetOptions, type EncryptedSetEntry, type EncryptionKey, type StoredEncryptedBlob, type ClientKeyBinding, globToRegex, matchesPattern, validatePattern, escapeGlob, MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './storage';
|
|
30
|
-
export type { StorageAdapter, NamespacedStorage, RootStorage, SetOptions, SetEntry, MessageHandler, Unsubscribe, MemoryAdapterOptions, RedisAdapterOptions, VercelKvAdapterOptions, UpstashAdapterOptions, StorageType, StorageConfig, } from './storage';
|
|
29
|
+
export { createStorage, createMemoryStorage, getDetectedStorageType, NamespacedStorageImpl, createRootStorage, createNamespacedStorage, buildPrefix, NAMESPACE_SEPARATOR, StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError, BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, createRedisClient, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, type FileSystemAdapterOptions, TypedStorage, type TypedStorageOptions, type TypedSetOptions, type TypedSetEntry, EncryptedTypedStorage, type EncryptedTypedStorageOptions, type EncryptedSetOptions, type EncryptedSetEntry, type EncryptionKey, type StoredEncryptedBlob, type ClientKeyBinding, globToRegex, matchesPattern, validatePattern, escapeGlob, MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, attachRedisErrorListener, DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS, } from './storage';
|
|
30
|
+
export type { StorageAdapter, NamespacedStorage, RootStorage, SetOptions, SetEntry, MessageHandler, Unsubscribe, MemoryAdapterOptions, RedisAdapterOptions, VercelKvAdapterOptions, UpstashAdapterOptions, StorageType, StorageConfig, RedisErrorListenerOptions, CreateRedisClientOptions, ErrorEmitterClient, } from './storage';
|
|
31
31
|
export { processPlatformToolCalls } from './llm';
|
|
32
32
|
export type { CallToolFn, OpenAIToolCallItem, OpenAIToolResponse, ClaudeToolUseBlock, ClaudeToolResultBlock, VercelToolCallInfo, PlatformToolCallsInput, PlatformToolCallsOutput, SupportedPlatform, } from './llm';
|
|
33
33
|
export { getMachineId, setMachineIdOverride } from './machine-id';
|
package/index.js
CHANGED
|
@@ -2965,6 +2965,41 @@ var init_crypto = __esm({
|
|
|
2965
2965
|
}
|
|
2966
2966
|
});
|
|
2967
2967
|
|
|
2968
|
+
// libs/utils/src/storage/redis-error-listener.ts
|
|
2969
|
+
function attachRedisErrorListener(client, options = {}) {
|
|
2970
|
+
const label = options.label ?? "redis";
|
|
2971
|
+
const intervalMs = options.intervalMs ?? DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
|
|
2972
|
+
let lastLoggedAt = 0;
|
|
2973
|
+
let suppressed = 0;
|
|
2974
|
+
const listener = (error) => {
|
|
2975
|
+
const now = Date.now();
|
|
2976
|
+
if (lastLoggedAt !== 0 && now - lastLoggedAt < intervalMs) {
|
|
2977
|
+
suppressed++;
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
const extra = suppressed > 0 ? ` (${suppressed} similar error(s) suppressed)` : "";
|
|
2981
|
+
suppressed = 0;
|
|
2982
|
+
lastLoggedAt = now;
|
|
2983
|
+
const message = `[${label}] Redis connection error: ${error?.message ?? String(error)}${extra}`;
|
|
2984
|
+
if (options.logger) {
|
|
2985
|
+
options.logger.warn(message);
|
|
2986
|
+
} else {
|
|
2987
|
+
console.warn(message);
|
|
2988
|
+
}
|
|
2989
|
+
};
|
|
2990
|
+
client.on("error", listener);
|
|
2991
|
+
return () => {
|
|
2992
|
+
client.removeListener("error", listener);
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
|
|
2996
|
+
var init_redis_error_listener = __esm({
|
|
2997
|
+
"libs/utils/src/storage/redis-error-listener.ts"() {
|
|
2998
|
+
"use strict";
|
|
2999
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
|
|
2968
3003
|
// libs/utils/src/storage/utils/index.ts
|
|
2969
3004
|
var init_utils = __esm({
|
|
2970
3005
|
"libs/utils/src/storage/utils/index.ts"() {
|
|
@@ -2976,7 +3011,8 @@ var init_utils = __esm({
|
|
|
2976
3011
|
// libs/utils/src/storage/adapters/redis.ts
|
|
2977
3012
|
var redis_exports = {};
|
|
2978
3013
|
__export(redis_exports, {
|
|
2979
|
-
RedisStorageAdapter: () => RedisStorageAdapter
|
|
3014
|
+
RedisStorageAdapter: () => RedisStorageAdapter,
|
|
3015
|
+
createRedisClient: () => createRedisClient
|
|
2980
3016
|
});
|
|
2981
3017
|
function getRedisClass() {
|
|
2982
3018
|
try {
|
|
@@ -2985,25 +3021,45 @@ function getRedisClass() {
|
|
|
2985
3021
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2986
3022
|
if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
|
|
2987
3023
|
throw new Error(
|
|
2988
|
-
`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode
|
|
3024
|
+
`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
|
|
3025
|
+
{ cause: error }
|
|
2989
3026
|
);
|
|
2990
3027
|
}
|
|
2991
|
-
throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis"
|
|
3028
|
+
throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
|
|
3029
|
+
cause: error
|
|
3030
|
+
});
|
|
2992
3031
|
}
|
|
2993
3032
|
}
|
|
3033
|
+
function createRedisClient(options) {
|
|
3034
|
+
const RedisClass = getRedisClass();
|
|
3035
|
+
const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
|
|
3036
|
+
const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
|
|
3037
|
+
...baseOptions,
|
|
3038
|
+
host: options.host ?? "localhost",
|
|
3039
|
+
port: options.port ?? 6379,
|
|
3040
|
+
password: options.password,
|
|
3041
|
+
db: options.db ?? 0,
|
|
3042
|
+
tls: options.tls ? {} : void 0
|
|
3043
|
+
});
|
|
3044
|
+
attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
|
|
3045
|
+
return client;
|
|
3046
|
+
}
|
|
2994
3047
|
var RedisStorageAdapter;
|
|
2995
3048
|
var init_redis = __esm({
|
|
2996
3049
|
"libs/utils/src/storage/adapters/redis.ts"() {
|
|
2997
3050
|
"use strict";
|
|
2998
|
-
init_base();
|
|
2999
3051
|
init_errors();
|
|
3052
|
+
init_redis_error_listener();
|
|
3000
3053
|
init_utils();
|
|
3054
|
+
init_base();
|
|
3001
3055
|
RedisStorageAdapter = class extends BaseStorageAdapter {
|
|
3002
3056
|
backendName = "redis";
|
|
3003
3057
|
client;
|
|
3004
3058
|
subscriber;
|
|
3005
3059
|
options;
|
|
3006
3060
|
ownsClient;
|
|
3061
|
+
detachErrorListener;
|
|
3062
|
+
connecting;
|
|
3007
3063
|
keyPrefix;
|
|
3008
3064
|
subscriptionHandlers = /* @__PURE__ */ new Map();
|
|
3009
3065
|
constructor(options = {}) {
|
|
@@ -3033,21 +3089,45 @@ var init_redis = __esm({
|
|
|
3033
3089
|
// ============================================
|
|
3034
3090
|
async connect() {
|
|
3035
3091
|
if (this.connected) return;
|
|
3092
|
+
this.connecting ??= this.openConnection().finally(() => {
|
|
3093
|
+
this.connecting = void 0;
|
|
3094
|
+
});
|
|
3095
|
+
return this.connecting;
|
|
3096
|
+
}
|
|
3097
|
+
async openConnection() {
|
|
3098
|
+
let socketError;
|
|
3099
|
+
const recordSocketError = (error) => {
|
|
3100
|
+
socketError = error;
|
|
3101
|
+
};
|
|
3102
|
+
let client;
|
|
3036
3103
|
try {
|
|
3037
3104
|
if (this.options.client) {
|
|
3038
|
-
|
|
3105
|
+
client = this.options.client;
|
|
3039
3106
|
} else {
|
|
3040
3107
|
const RedisClass = getRedisClass();
|
|
3041
3108
|
if (this.options.url) {
|
|
3042
|
-
|
|
3109
|
+
client = new RedisClass(this.options.url, this.buildRedisOptions());
|
|
3043
3110
|
} else {
|
|
3044
|
-
|
|
3111
|
+
client = new RedisClass(this.buildRedisOptions());
|
|
3045
3112
|
}
|
|
3113
|
+
client.on("error", recordSocketError);
|
|
3046
3114
|
}
|
|
3047
|
-
await
|
|
3115
|
+
await client.ping();
|
|
3116
|
+
this.client = client;
|
|
3048
3117
|
this.connected = true;
|
|
3118
|
+
if (this.ownsClient) {
|
|
3119
|
+
client.removeListener("error", recordSocketError);
|
|
3120
|
+
this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
|
|
3121
|
+
}
|
|
3049
3122
|
} catch (e) {
|
|
3050
|
-
|
|
3123
|
+
if (this.ownsClient && client) {
|
|
3124
|
+
try {
|
|
3125
|
+
client.disconnect();
|
|
3126
|
+
} catch {
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
const cause = socketError ?? (e instanceof Error ? e : void 0);
|
|
3130
|
+
throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
|
|
3051
3131
|
}
|
|
3052
3132
|
}
|
|
3053
3133
|
async disconnect() {
|
|
@@ -3059,6 +3139,8 @@ var init_redis = __esm({
|
|
|
3059
3139
|
if (this.ownsClient && this.client) {
|
|
3060
3140
|
await this.client.quit();
|
|
3061
3141
|
}
|
|
3142
|
+
this.detachErrorListener?.();
|
|
3143
|
+
this.detachErrorListener = void 0;
|
|
3062
3144
|
this.client = void 0;
|
|
3063
3145
|
this.connected = false;
|
|
3064
3146
|
this.subscriptionHandlers.clear();
|
|
@@ -3286,6 +3368,7 @@ var init_redis = __esm({
|
|
|
3286
3368
|
} else {
|
|
3287
3369
|
throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
|
|
3288
3370
|
}
|
|
3371
|
+
attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
|
|
3289
3372
|
subscriber.on("message", (channel, message) => {
|
|
3290
3373
|
const handlers = this.subscriptionHandlers.get(channel);
|
|
3291
3374
|
if (handlers) {
|
|
@@ -3982,6 +4065,7 @@ __export(index_exports, {
|
|
|
3982
4065
|
BaseStorageAdapter: () => BaseStorageAdapter,
|
|
3983
4066
|
DEFAULT_CODE_VERIFIER_LENGTH: () => DEFAULT_CODE_VERIFIER_LENGTH,
|
|
3984
4067
|
DEFAULT_MAX_INPUT_LENGTH: () => DEFAULT_MAX_INPUT_LENGTH,
|
|
4068
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS: () => DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS,
|
|
3985
4069
|
EncryptedBlobError: () => EncryptedBlobError,
|
|
3986
4070
|
EncryptedStorageError: () => EncryptedStorageError,
|
|
3987
4071
|
EncryptedTypedStorage: () => EncryptedTypedStorage,
|
|
@@ -4014,6 +4098,7 @@ __export(index_exports, {
|
|
|
4014
4098
|
assertNode: () => assertNode,
|
|
4015
4099
|
asymmetricAlgSchema: () => asymmetricAlgSchema,
|
|
4016
4100
|
asymmetricKeyDataSchema: () => asymmetricKeyDataSchema,
|
|
4101
|
+
attachRedisErrorListener: () => attachRedisErrorListener,
|
|
4017
4102
|
awaitOutsideRequest: () => import_async_context.awaitOutsideRequest,
|
|
4018
4103
|
base64Decode: () => base64Decode,
|
|
4019
4104
|
base64Encode: () => base64Encode,
|
|
@@ -4034,6 +4119,7 @@ __export(index_exports, {
|
|
|
4034
4119
|
createKeyPersistenceWithStorage: () => createKeyPersistenceWithStorage,
|
|
4035
4120
|
createMemoryStorage: () => createMemoryStorage,
|
|
4036
4121
|
createNamespacedStorage: () => createNamespacedStorage,
|
|
4122
|
+
createRedisClient: () => createRedisClient,
|
|
4037
4123
|
createRootStorage: () => createRootStorage,
|
|
4038
4124
|
createSafeRegExp: () => createSafeRegExp,
|
|
4039
4125
|
createSecretData: () => createSecretData,
|
|
@@ -5169,7 +5255,13 @@ function detectRuntimeContext() {
|
|
|
5169
5255
|
var cached3;
|
|
5170
5256
|
function getRuntimeContext() {
|
|
5171
5257
|
if (!cached3) {
|
|
5172
|
-
|
|
5258
|
+
const detected = detectRuntimeContext();
|
|
5259
|
+
Object.defineProperty(detected, "env", {
|
|
5260
|
+
get: () => process.env["NODE_ENV"] || "development",
|
|
5261
|
+
enumerable: true,
|
|
5262
|
+
configurable: true
|
|
5263
|
+
});
|
|
5264
|
+
cached3 = detected;
|
|
5173
5265
|
}
|
|
5174
5266
|
return cached3;
|
|
5175
5267
|
}
|
|
@@ -5440,6 +5532,7 @@ function getDetectedStorageType() {
|
|
|
5440
5532
|
}
|
|
5441
5533
|
|
|
5442
5534
|
// libs/utils/src/storage/index.ts
|
|
5535
|
+
init_redis_error_listener();
|
|
5443
5536
|
init_errors();
|
|
5444
5537
|
|
|
5445
5538
|
// libs/utils/src/storage/typed-storage.ts
|
|
@@ -6095,6 +6188,7 @@ function setMachineIdOverride(id) {
|
|
|
6095
6188
|
BaseStorageAdapter,
|
|
6096
6189
|
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
6097
6190
|
DEFAULT_MAX_INPUT_LENGTH,
|
|
6191
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS,
|
|
6098
6192
|
EncryptedBlobError,
|
|
6099
6193
|
EncryptedStorageError,
|
|
6100
6194
|
EncryptedTypedStorage,
|
|
@@ -6127,6 +6221,7 @@ function setMachineIdOverride(id) {
|
|
|
6127
6221
|
assertNode,
|
|
6128
6222
|
asymmetricAlgSchema,
|
|
6129
6223
|
asymmetricKeyDataSchema,
|
|
6224
|
+
attachRedisErrorListener,
|
|
6130
6225
|
awaitOutsideRequest,
|
|
6131
6226
|
base64Decode,
|
|
6132
6227
|
base64Encode,
|
|
@@ -6147,6 +6242,7 @@ function setMachineIdOverride(id) {
|
|
|
6147
6242
|
createKeyPersistenceWithStorage,
|
|
6148
6243
|
createMemoryStorage,
|
|
6149
6244
|
createNamespacedStorage,
|
|
6245
|
+
createRedisClient,
|
|
6150
6246
|
createRootStorage,
|
|
6151
6247
|
createSafeRegExp,
|
|
6152
6248
|
createSecretData,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/utils",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.7",
|
|
4
4
|
"description": "Shared utility functions for FrontMCP - string manipulation, URI handling, path utilities, and more",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@noble/hashes": "^2.0.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@frontmcp/lazy-zod": "1.8.
|
|
35
|
+
"@frontmcp/lazy-zod": "1.8.7",
|
|
36
36
|
"@vercel/kv": "^2.0.0 || ^3.0.0",
|
|
37
37
|
"ioredis": "^5.0.0"
|
|
38
38
|
},
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { BaseStorageAdapter } from './base';
|
|
7
7
|
export { MemoryStorageAdapter } from './memory';
|
|
8
|
-
export { RedisStorageAdapter } from './redis';
|
|
8
|
+
export { RedisStorageAdapter, createRedisClient } from './redis';
|
|
9
|
+
export type { CreateRedisClientOptions } from './redis';
|
|
9
10
|
export { VercelKvStorageAdapter } from './vercel-kv';
|
|
10
11
|
export { UpstashStorageAdapter } from './upstash';
|
|
11
12
|
export { CloudflareKvStorageAdapter } from './cloudflare-kv';
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Redis-based storage implementation for production use.
|
|
5
5
|
* Uses ioredis with dynamic import for browser compatibility.
|
|
6
6
|
*/
|
|
7
|
+
import { type RedisErrorListenerOptions } from '../redis-error-listener';
|
|
8
|
+
import type { MessageHandler, RedisAdapterOptions, SetOptions, Unsubscribe } from '../types';
|
|
7
9
|
import { BaseStorageAdapter } from './base';
|
|
8
|
-
import type { RedisAdapterOptions, SetOptions, MessageHandler, Unsubscribe } from '../types';
|
|
9
10
|
type Redis = import('ioredis').Redis;
|
|
10
11
|
/**
|
|
11
12
|
* Redis storage adapter.
|
|
@@ -34,10 +35,13 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
|
|
|
34
35
|
private subscriber?;
|
|
35
36
|
private readonly options;
|
|
36
37
|
private readonly ownsClient;
|
|
38
|
+
private detachErrorListener?;
|
|
39
|
+
private connecting?;
|
|
37
40
|
private readonly keyPrefix;
|
|
38
41
|
private readonly subscriptionHandlers;
|
|
39
42
|
constructor(options?: RedisAdapterOptions);
|
|
40
43
|
connect(): Promise<void>;
|
|
44
|
+
private openConnection;
|
|
41
45
|
disconnect(): Promise<void>;
|
|
42
46
|
ping(): Promise<boolean>;
|
|
43
47
|
/**
|
|
@@ -85,4 +89,20 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
|
|
|
85
89
|
*/
|
|
86
90
|
getClient(): Redis | undefined;
|
|
87
91
|
}
|
|
92
|
+
export interface CreateRedisClientOptions {
|
|
93
|
+
url?: string;
|
|
94
|
+
host?: string;
|
|
95
|
+
port?: number;
|
|
96
|
+
password?: string;
|
|
97
|
+
db?: number;
|
|
98
|
+
tls?: boolean;
|
|
99
|
+
/** Prefix for the rate-limited connection-error log line. */
|
|
100
|
+
label?: string;
|
|
101
|
+
logger?: RedisErrorListenerOptions['logger'];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Create a plain ioredis client that reconnects on its own and never emits an unhandled
|
|
105
|
+
* 'error' event. The caller owns the client and must `quit()`/`disconnect()` it.
|
|
106
|
+
*/
|
|
107
|
+
export declare function createRedisClient(options: CreateRedisClientOptions): Redis;
|
|
88
108
|
export {};
|
package/storage/index.d.ts
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export type { StorageAdapter, NamespacedStorage, RootStorage, SetOptions, SetEntry, MessageHandler, Unsubscribe, MemoryAdapterOptions, RedisAdapterOptions, VercelKvAdapterOptions, UpstashAdapterOptions, StorageType, StorageConfig, } from './types';
|
|
8
8
|
export { createStorage, createMemoryStorage, getDetectedStorageType } from './factory';
|
|
9
|
+
export { attachRedisErrorListener, DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS } from './redis-error-listener';
|
|
10
|
+
export type { RedisErrorListenerOptions, ErrorEmitterClient } from './redis-error-listener';
|
|
9
11
|
export { NamespacedStorageImpl, createRootStorage, createNamespacedStorage, buildPrefix, NAMESPACE_SEPARATOR, } from './namespace';
|
|
10
12
|
export { StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError, } from './errors';
|
|
11
13
|
export { TypedStorage } from './typed-storage';
|
|
12
14
|
export type { TypedStorageOptions, TypedSetOptions, TypedSetEntry } from './typed-storage.types';
|
|
13
15
|
export { EncryptedTypedStorage } from './encrypted-typed-storage';
|
|
14
16
|
export type { EncryptedTypedStorageOptions, EncryptedSetOptions, EncryptedSetEntry, EncryptionKey, StoredEncryptedBlob, ClientKeyBinding, } from './encrypted-typed-storage.types';
|
|
15
|
-
export { BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, LocalStorageAdapter, IndexedDBStorageAdapter, } from './adapters';
|
|
16
|
-
export type { FileSystemAdapterOptions, LocalStorageAdapterOptions, IndexedDBAdapterOptions } from './adapters';
|
|
17
|
+
export { BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, createRedisClient, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, LocalStorageAdapter, IndexedDBStorageAdapter, } from './adapters';
|
|
18
|
+
export type { CreateRedisClientOptions, FileSystemAdapterOptions, LocalStorageAdapterOptions, IndexedDBAdapterOptions, } from './adapters';
|
|
17
19
|
export { globToRegex, matchesPattern, validatePattern, escapeGlob } from './utils/pattern';
|
|
18
20
|
export { MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './utils/ttl';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permanent, rate-limited 'error' listener for ioredis clients.
|
|
3
|
+
*
|
|
4
|
+
* ioredis emits 'error' for every failed reconnect attempt. With no listener it
|
|
5
|
+
* prints "[ioredis] Unhandled error event" once per attempt, which floods logs
|
|
6
|
+
* while Redis is down. This listener swallows the event and logs at most once
|
|
7
|
+
* per interval, counting the errors it suppressed.
|
|
8
|
+
*/
|
|
9
|
+
export interface RedisErrorListenerOptions {
|
|
10
|
+
/** Name used in the log line, e.g. 'HA'. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Logger to write to. Falls back to `console.warn`. */
|
|
13
|
+
logger?: {
|
|
14
|
+
warn: (message: string, ...meta: unknown[]) => void;
|
|
15
|
+
};
|
|
16
|
+
/** Minimum time between two log lines. Defaults to 30 seconds. */
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ErrorEmitterClient {
|
|
20
|
+
on(event: 'error', listener: (error: Error) => void): unknown;
|
|
21
|
+
removeListener(event: 'error', listener: (error: Error) => void): unknown;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 30000;
|
|
24
|
+
/**
|
|
25
|
+
* Attach the listener and return a function that removes it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function attachRedisErrorListener(client: ErrorEmitterClient, options?: RedisErrorListenerOptions): () => void;
|