@frontmcp/utils 1.8.5 → 1.8.6

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.mjs CHANGED
@@ -2982,25 +2982,29 @@ function getRedisClass() {
2982
2982
  const msg = error instanceof Error ? error.message : String(error);
2983
2983
  if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
2984
2984
  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.`
2985
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
2986
+ { cause: error }
2986
2987
  );
2987
2988
  }
2988
- throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
2989
+ throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
2990
+ cause: error
2991
+ });
2989
2992
  }
2990
2993
  }
2991
2994
  var RedisStorageAdapter;
2992
2995
  var init_redis = __esm({
2993
2996
  "libs/utils/src/storage/adapters/redis.ts"() {
2994
2997
  "use strict";
2995
- init_base();
2996
2998
  init_errors();
2997
2999
  init_utils();
3000
+ init_base();
2998
3001
  RedisStorageAdapter = class extends BaseStorageAdapter {
2999
3002
  backendName = "redis";
3000
3003
  client;
3001
3004
  subscriber;
3002
3005
  options;
3003
3006
  ownsClient;
3007
+ connecting;
3004
3008
  keyPrefix;
3005
3009
  subscriptionHandlers = /* @__PURE__ */ new Map();
3006
3010
  constructor(options = {}) {
@@ -3030,21 +3034,42 @@ var init_redis = __esm({
3030
3034
  // ============================================
3031
3035
  async connect() {
3032
3036
  if (this.connected) return;
3037
+ this.connecting ??= this.openConnection().finally(() => {
3038
+ this.connecting = void 0;
3039
+ });
3040
+ return this.connecting;
3041
+ }
3042
+ async openConnection() {
3043
+ let socketError;
3044
+ const recordSocketError = (error) => {
3045
+ socketError = error;
3046
+ };
3047
+ let client;
3033
3048
  try {
3034
3049
  if (this.options.client) {
3035
- this.client = this.options.client;
3050
+ client = this.options.client;
3036
3051
  } else {
3037
3052
  const RedisClass = getRedisClass();
3038
3053
  if (this.options.url) {
3039
- this.client = new RedisClass(this.options.url, this.buildRedisOptions());
3054
+ client = new RedisClass(this.options.url, this.buildRedisOptions());
3040
3055
  } else {
3041
- this.client = new RedisClass(this.buildRedisOptions());
3056
+ client = new RedisClass(this.buildRedisOptions());
3042
3057
  }
3058
+ client.on("error", recordSocketError);
3043
3059
  }
3044
- await this.client.ping();
3060
+ await client.ping();
3061
+ this.client = client;
3045
3062
  this.connected = true;
3063
+ if (this.ownsClient) client.removeListener("error", recordSocketError);
3046
3064
  } catch (e) {
3047
- throw new StorageConnectionError("Failed to connect to Redis", e instanceof Error ? e : void 0, "redis");
3065
+ if (this.ownsClient && client) {
3066
+ try {
3067
+ client.disconnect();
3068
+ } catch {
3069
+ }
3070
+ }
3071
+ const cause = socketError ?? (e instanceof Error ? e : void 0);
3072
+ throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
3048
3073
  }
3049
3074
  }
3050
3075
  async disconnect() {
package/index.js CHANGED
@@ -2985,25 +2985,29 @@ function getRedisClass() {
2985
2985
  const msg = error instanceof Error ? error.message : String(error);
2986
2986
  if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
2987
2987
  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.`
2988
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
2989
+ { cause: error }
2989
2990
  );
2990
2991
  }
2991
- throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
2992
+ throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
2993
+ cause: error
2994
+ });
2992
2995
  }
2993
2996
  }
2994
2997
  var RedisStorageAdapter;
2995
2998
  var init_redis = __esm({
2996
2999
  "libs/utils/src/storage/adapters/redis.ts"() {
2997
3000
  "use strict";
2998
- init_base();
2999
3001
  init_errors();
3000
3002
  init_utils();
3003
+ init_base();
3001
3004
  RedisStorageAdapter = class extends BaseStorageAdapter {
3002
3005
  backendName = "redis";
3003
3006
  client;
3004
3007
  subscriber;
3005
3008
  options;
3006
3009
  ownsClient;
3010
+ connecting;
3007
3011
  keyPrefix;
3008
3012
  subscriptionHandlers = /* @__PURE__ */ new Map();
3009
3013
  constructor(options = {}) {
@@ -3033,21 +3037,42 @@ var init_redis = __esm({
3033
3037
  // ============================================
3034
3038
  async connect() {
3035
3039
  if (this.connected) return;
3040
+ this.connecting ??= this.openConnection().finally(() => {
3041
+ this.connecting = void 0;
3042
+ });
3043
+ return this.connecting;
3044
+ }
3045
+ async openConnection() {
3046
+ let socketError;
3047
+ const recordSocketError = (error) => {
3048
+ socketError = error;
3049
+ };
3050
+ let client;
3036
3051
  try {
3037
3052
  if (this.options.client) {
3038
- this.client = this.options.client;
3053
+ client = this.options.client;
3039
3054
  } else {
3040
3055
  const RedisClass = getRedisClass();
3041
3056
  if (this.options.url) {
3042
- this.client = new RedisClass(this.options.url, this.buildRedisOptions());
3057
+ client = new RedisClass(this.options.url, this.buildRedisOptions());
3043
3058
  } else {
3044
- this.client = new RedisClass(this.buildRedisOptions());
3059
+ client = new RedisClass(this.buildRedisOptions());
3045
3060
  }
3061
+ client.on("error", recordSocketError);
3046
3062
  }
3047
- await this.client.ping();
3063
+ await client.ping();
3064
+ this.client = client;
3048
3065
  this.connected = true;
3066
+ if (this.ownsClient) client.removeListener("error", recordSocketError);
3049
3067
  } catch (e) {
3050
- throw new StorageConnectionError("Failed to connect to Redis", e instanceof Error ? e : void 0, "redis");
3068
+ if (this.ownsClient && client) {
3069
+ try {
3070
+ client.disconnect();
3071
+ } catch {
3072
+ }
3073
+ }
3074
+ const cause = socketError ?? (e instanceof Error ? e : void 0);
3075
+ throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
3051
3076
  }
3052
3077
  }
3053
3078
  async disconnect() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.8.5",
3
+ "version": "1.8.6",
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.5",
35
+ "@frontmcp/lazy-zod": "1.8.6",
36
36
  "@vercel/kv": "^2.0.0 || ^3.0.0",
37
37
  "ioredis": "^5.0.0"
38
38
  },
@@ -4,8 +4,8 @@
4
4
  * Redis-based storage implementation for production use.
5
5
  * Uses ioredis with dynamic import for browser compatibility.
6
6
  */
7
+ import type { MessageHandler, RedisAdapterOptions, SetOptions, Unsubscribe } from '../types';
7
8
  import { BaseStorageAdapter } from './base';
8
- import type { RedisAdapterOptions, SetOptions, MessageHandler, Unsubscribe } from '../types';
9
9
  type Redis = import('ioredis').Redis;
10
10
  /**
11
11
  * Redis storage adapter.
@@ -34,10 +34,12 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
34
34
  private subscriber?;
35
35
  private readonly options;
36
36
  private readonly ownsClient;
37
+ private connecting?;
37
38
  private readonly keyPrefix;
38
39
  private readonly subscriptionHandlers;
39
40
  constructor(options?: RedisAdapterOptions);
40
41
  connect(): Promise<void>;
42
+ private openConnection;
41
43
  disconnect(): Promise<void>;
42
44
  ping(): Promise<boolean>;
43
45
  /**