@frontmcp/utils 1.8.6 → 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 +74 -5
- package/index.d.ts +2 -2
- package/index.js +74 -3
- package/package.json +2 -2
- package/storage/adapters/index.d.ts +2 -1
- package/storage/adapters/redis.d.ts +18 -0
- 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 {
|
|
@@ -2991,11 +3028,26 @@ function getRedisClass() {
|
|
|
2991
3028
|
});
|
|
2992
3029
|
}
|
|
2993
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
|
+
}
|
|
2994
3045
|
var RedisStorageAdapter;
|
|
2995
3046
|
var init_redis = __esm({
|
|
2996
3047
|
"libs/utils/src/storage/adapters/redis.ts"() {
|
|
2997
3048
|
"use strict";
|
|
2998
3049
|
init_errors();
|
|
3050
|
+
init_redis_error_listener();
|
|
2999
3051
|
init_utils();
|
|
3000
3052
|
init_base();
|
|
3001
3053
|
RedisStorageAdapter = class extends BaseStorageAdapter {
|
|
@@ -3004,6 +3056,7 @@ var init_redis = __esm({
|
|
|
3004
3056
|
subscriber;
|
|
3005
3057
|
options;
|
|
3006
3058
|
ownsClient;
|
|
3059
|
+
detachErrorListener;
|
|
3007
3060
|
connecting;
|
|
3008
3061
|
keyPrefix;
|
|
3009
3062
|
subscriptionHandlers = /* @__PURE__ */ new Map();
|
|
@@ -3060,7 +3113,10 @@ var init_redis = __esm({
|
|
|
3060
3113
|
await client.ping();
|
|
3061
3114
|
this.client = client;
|
|
3062
3115
|
this.connected = true;
|
|
3063
|
-
if (this.ownsClient)
|
|
3116
|
+
if (this.ownsClient) {
|
|
3117
|
+
client.removeListener("error", recordSocketError);
|
|
3118
|
+
this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
|
|
3119
|
+
}
|
|
3064
3120
|
} catch (e) {
|
|
3065
3121
|
if (this.ownsClient && client) {
|
|
3066
3122
|
try {
|
|
@@ -3081,6 +3137,8 @@ var init_redis = __esm({
|
|
|
3081
3137
|
if (this.ownsClient && this.client) {
|
|
3082
3138
|
await this.client.quit();
|
|
3083
3139
|
}
|
|
3140
|
+
this.detachErrorListener?.();
|
|
3141
|
+
this.detachErrorListener = void 0;
|
|
3084
3142
|
this.client = void 0;
|
|
3085
3143
|
this.connected = false;
|
|
3086
3144
|
this.subscriptionHandlers.clear();
|
|
@@ -3308,6 +3366,7 @@ var init_redis = __esm({
|
|
|
3308
3366
|
} else {
|
|
3309
3367
|
throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
|
|
3310
3368
|
}
|
|
3369
|
+
attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
|
|
3311
3370
|
subscriber.on("message", (channel, message) => {
|
|
3312
3371
|
const handlers = this.subscriptionHandlers.get(channel);
|
|
3313
3372
|
if (handlers) {
|
|
@@ -4977,7 +5036,13 @@ function detectRuntimeContext() {
|
|
|
4977
5036
|
var cached3;
|
|
4978
5037
|
function getRuntimeContext() {
|
|
4979
5038
|
if (!cached3) {
|
|
4980
|
-
|
|
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;
|
|
4981
5046
|
}
|
|
4982
5047
|
return cached3;
|
|
4983
5048
|
}
|
|
@@ -5248,6 +5313,7 @@ function getDetectedStorageType() {
|
|
|
5248
5313
|
}
|
|
5249
5314
|
|
|
5250
5315
|
// libs/utils/src/storage/index.ts
|
|
5316
|
+
init_redis_error_listener();
|
|
5251
5317
|
init_errors();
|
|
5252
5318
|
|
|
5253
5319
|
// libs/utils/src/storage/typed-storage.ts
|
|
@@ -5902,6 +5968,7 @@ export {
|
|
|
5902
5968
|
BaseStorageAdapter,
|
|
5903
5969
|
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
5904
5970
|
DEFAULT_MAX_INPUT_LENGTH,
|
|
5971
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS,
|
|
5905
5972
|
EncryptedBlobError,
|
|
5906
5973
|
EncryptedStorageError,
|
|
5907
5974
|
EncryptedTypedStorage,
|
|
@@ -5934,6 +6001,7 @@ export {
|
|
|
5934
6001
|
assertNode,
|
|
5935
6002
|
asymmetricAlgSchema,
|
|
5936
6003
|
asymmetricKeyDataSchema,
|
|
6004
|
+
attachRedisErrorListener,
|
|
5937
6005
|
awaitOutsideRequest,
|
|
5938
6006
|
base64Decode,
|
|
5939
6007
|
base64Encode,
|
|
@@ -5954,6 +6022,7 @@ export {
|
|
|
5954
6022
|
createKeyPersistenceWithStorage,
|
|
5955
6023
|
createMemoryStorage,
|
|
5956
6024
|
createNamespacedStorage,
|
|
6025
|
+
createRedisClient,
|
|
5957
6026
|
createRootStorage,
|
|
5958
6027
|
createSafeRegExp,
|
|
5959
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 {
|
|
@@ -2994,11 +3030,26 @@ function getRedisClass() {
|
|
|
2994
3030
|
});
|
|
2995
3031
|
}
|
|
2996
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
|
+
}
|
|
2997
3047
|
var RedisStorageAdapter;
|
|
2998
3048
|
var init_redis = __esm({
|
|
2999
3049
|
"libs/utils/src/storage/adapters/redis.ts"() {
|
|
3000
3050
|
"use strict";
|
|
3001
3051
|
init_errors();
|
|
3052
|
+
init_redis_error_listener();
|
|
3002
3053
|
init_utils();
|
|
3003
3054
|
init_base();
|
|
3004
3055
|
RedisStorageAdapter = class extends BaseStorageAdapter {
|
|
@@ -3007,6 +3058,7 @@ var init_redis = __esm({
|
|
|
3007
3058
|
subscriber;
|
|
3008
3059
|
options;
|
|
3009
3060
|
ownsClient;
|
|
3061
|
+
detachErrorListener;
|
|
3010
3062
|
connecting;
|
|
3011
3063
|
keyPrefix;
|
|
3012
3064
|
subscriptionHandlers = /* @__PURE__ */ new Map();
|
|
@@ -3063,7 +3115,10 @@ var init_redis = __esm({
|
|
|
3063
3115
|
await client.ping();
|
|
3064
3116
|
this.client = client;
|
|
3065
3117
|
this.connected = true;
|
|
3066
|
-
if (this.ownsClient)
|
|
3118
|
+
if (this.ownsClient) {
|
|
3119
|
+
client.removeListener("error", recordSocketError);
|
|
3120
|
+
this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
|
|
3121
|
+
}
|
|
3067
3122
|
} catch (e) {
|
|
3068
3123
|
if (this.ownsClient && client) {
|
|
3069
3124
|
try {
|
|
@@ -3084,6 +3139,8 @@ var init_redis = __esm({
|
|
|
3084
3139
|
if (this.ownsClient && this.client) {
|
|
3085
3140
|
await this.client.quit();
|
|
3086
3141
|
}
|
|
3142
|
+
this.detachErrorListener?.();
|
|
3143
|
+
this.detachErrorListener = void 0;
|
|
3087
3144
|
this.client = void 0;
|
|
3088
3145
|
this.connected = false;
|
|
3089
3146
|
this.subscriptionHandlers.clear();
|
|
@@ -3311,6 +3368,7 @@ var init_redis = __esm({
|
|
|
3311
3368
|
} else {
|
|
3312
3369
|
throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
|
|
3313
3370
|
}
|
|
3371
|
+
attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
|
|
3314
3372
|
subscriber.on("message", (channel, message) => {
|
|
3315
3373
|
const handlers = this.subscriptionHandlers.get(channel);
|
|
3316
3374
|
if (handlers) {
|
|
@@ -4007,6 +4065,7 @@ __export(index_exports, {
|
|
|
4007
4065
|
BaseStorageAdapter: () => BaseStorageAdapter,
|
|
4008
4066
|
DEFAULT_CODE_VERIFIER_LENGTH: () => DEFAULT_CODE_VERIFIER_LENGTH,
|
|
4009
4067
|
DEFAULT_MAX_INPUT_LENGTH: () => DEFAULT_MAX_INPUT_LENGTH,
|
|
4068
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS: () => DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS,
|
|
4010
4069
|
EncryptedBlobError: () => EncryptedBlobError,
|
|
4011
4070
|
EncryptedStorageError: () => EncryptedStorageError,
|
|
4012
4071
|
EncryptedTypedStorage: () => EncryptedTypedStorage,
|
|
@@ -4039,6 +4098,7 @@ __export(index_exports, {
|
|
|
4039
4098
|
assertNode: () => assertNode,
|
|
4040
4099
|
asymmetricAlgSchema: () => asymmetricAlgSchema,
|
|
4041
4100
|
asymmetricKeyDataSchema: () => asymmetricKeyDataSchema,
|
|
4101
|
+
attachRedisErrorListener: () => attachRedisErrorListener,
|
|
4042
4102
|
awaitOutsideRequest: () => import_async_context.awaitOutsideRequest,
|
|
4043
4103
|
base64Decode: () => base64Decode,
|
|
4044
4104
|
base64Encode: () => base64Encode,
|
|
@@ -4059,6 +4119,7 @@ __export(index_exports, {
|
|
|
4059
4119
|
createKeyPersistenceWithStorage: () => createKeyPersistenceWithStorage,
|
|
4060
4120
|
createMemoryStorage: () => createMemoryStorage,
|
|
4061
4121
|
createNamespacedStorage: () => createNamespacedStorage,
|
|
4122
|
+
createRedisClient: () => createRedisClient,
|
|
4062
4123
|
createRootStorage: () => createRootStorage,
|
|
4063
4124
|
createSafeRegExp: () => createSafeRegExp,
|
|
4064
4125
|
createSecretData: () => createSecretData,
|
|
@@ -5194,7 +5255,13 @@ function detectRuntimeContext() {
|
|
|
5194
5255
|
var cached3;
|
|
5195
5256
|
function getRuntimeContext() {
|
|
5196
5257
|
if (!cached3) {
|
|
5197
|
-
|
|
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;
|
|
5198
5265
|
}
|
|
5199
5266
|
return cached3;
|
|
5200
5267
|
}
|
|
@@ -5465,6 +5532,7 @@ function getDetectedStorageType() {
|
|
|
5465
5532
|
}
|
|
5466
5533
|
|
|
5467
5534
|
// libs/utils/src/storage/index.ts
|
|
5535
|
+
init_redis_error_listener();
|
|
5468
5536
|
init_errors();
|
|
5469
5537
|
|
|
5470
5538
|
// libs/utils/src/storage/typed-storage.ts
|
|
@@ -6120,6 +6188,7 @@ function setMachineIdOverride(id) {
|
|
|
6120
6188
|
BaseStorageAdapter,
|
|
6121
6189
|
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
6122
6190
|
DEFAULT_MAX_INPUT_LENGTH,
|
|
6191
|
+
DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS,
|
|
6123
6192
|
EncryptedBlobError,
|
|
6124
6193
|
EncryptedStorageError,
|
|
6125
6194
|
EncryptedTypedStorage,
|
|
@@ -6152,6 +6221,7 @@ function setMachineIdOverride(id) {
|
|
|
6152
6221
|
assertNode,
|
|
6153
6222
|
asymmetricAlgSchema,
|
|
6154
6223
|
asymmetricKeyDataSchema,
|
|
6224
|
+
attachRedisErrorListener,
|
|
6155
6225
|
awaitOutsideRequest,
|
|
6156
6226
|
base64Decode,
|
|
6157
6227
|
base64Encode,
|
|
@@ -6172,6 +6242,7 @@ function setMachineIdOverride(id) {
|
|
|
6172
6242
|
createKeyPersistenceWithStorage,
|
|
6173
6243
|
createMemoryStorage,
|
|
6174
6244
|
createNamespacedStorage,
|
|
6245
|
+
createRedisClient,
|
|
6175
6246
|
createRootStorage,
|
|
6176
6247
|
createSafeRegExp,
|
|
6177
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,6 +4,7 @@
|
|
|
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';
|
|
7
8
|
import type { MessageHandler, RedisAdapterOptions, SetOptions, Unsubscribe } from '../types';
|
|
8
9
|
import { BaseStorageAdapter } from './base';
|
|
9
10
|
type Redis = import('ioredis').Redis;
|
|
@@ -34,6 +35,7 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
|
|
|
34
35
|
private subscriber?;
|
|
35
36
|
private readonly options;
|
|
36
37
|
private readonly ownsClient;
|
|
38
|
+
private detachErrorListener?;
|
|
37
39
|
private connecting?;
|
|
38
40
|
private readonly keyPrefix;
|
|
39
41
|
private readonly subscriptionHandlers;
|
|
@@ -87,4 +89,20 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
|
|
|
87
89
|
*/
|
|
88
90
|
getClient(): Redis | undefined;
|
|
89
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;
|
|
90
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;
|