@frontmcp/utils 1.9.1 → 1.9.3

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/index.js CHANGED
@@ -2541,180 +2541,19 @@ var init_key_persistence2 = __esm({
2541
2541
  }
2542
2542
  });
2543
2543
 
2544
- // libs/utils/src/crypto/node.ts
2545
- var node_exports = {};
2546
- __export(node_exports, {
2547
- createSignedJwt: () => createSignedJwt,
2548
- cryptoProvider: () => nodeCrypto,
2549
- generateRsaKeyPair: () => generateRsaKeyPair,
2550
- isRsaPssAlg: () => isRsaPssAlg,
2551
- jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
2552
- nodeCrypto: () => nodeCrypto,
2553
- pemToPublicJwk: () => pemToPublicJwk,
2554
- rsaSign: () => rsaSign,
2555
- rsaSignBase64Url: () => rsaSignBase64Url,
2556
- rsaVerify: () => rsaVerify,
2557
- rsaVerifySync: () => rsaVerifySync
2558
- });
2559
- function toUint8Array(buf) {
2560
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
2561
- }
2562
- function toBuffer(data) {
2563
- if (typeof data === "string") {
2564
- return Buffer.from(data, "utf8");
2565
- }
2566
- return Buffer.from(data);
2567
- }
2568
- function generateRsaKeyPair(modulusLength = 2048, alg = "RS256") {
2569
- const kid = `rsa-key-${Date.now()}-${import_node_crypto.default.randomBytes(8).toString("hex")}`;
2570
- const { privateKey, publicKey } = import_node_crypto.default.generateKeyPairSync("rsa", {
2571
- modulusLength
2572
- });
2573
- const exported = publicKey.export({ format: "jwk" });
2574
- const publicJwk = {
2575
- ...exported,
2576
- kid,
2577
- alg,
2578
- use: "sig",
2579
- kty: "RSA"
2580
- };
2581
- return { privateKey, publicKey, publicJwk };
2582
- }
2583
- function rsaSign(algorithm, data, privateKey, options) {
2584
- const signingKey = options ? { key: privateKey, ...options } : privateKey;
2585
- return import_node_crypto.default.sign(algorithm, data, signingKey);
2586
- }
2587
- function rsaVerify(jwtAlg, data, publicJwk, signature) {
2588
- const publicKey = import_node_crypto.default.createPublicKey({ key: publicJwk, format: "jwk" });
2589
- const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2590
- const verifyKey = isRsaPssAlg(jwtAlg) ? {
2591
- key: publicKey,
2592
- padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
2593
- saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
2594
- } : publicKey;
2595
- return import_node_crypto.default.verify(nodeAlgorithm, data, verifyKey, signature);
2596
- }
2597
- function rsaSignBase64Url(jwtAlg, data, privateJwk) {
2598
- const privateKey = import_node_crypto.default.createPrivateKey({ key: privateJwk, format: "jwk" });
2599
- const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2600
- const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2601
- const sig = isRsaPssAlg(jwtAlg) ? rsaSign(nodeAlgorithm, buf, privateKey, {
2602
- padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
2603
- saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
2604
- }) : rsaSign(nodeAlgorithm, buf, privateKey);
2605
- return sig.toString("base64url");
2606
- }
2607
- function rsaVerifySync(jwtAlg, data, publicJwk, signature) {
2608
- try {
2609
- const publicKey = import_node_crypto.default.createPublicKey({ key: publicJwk, format: "jwk" });
2610
- const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
2611
- const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
2612
- if (jwtAlg === "EdDSA") {
2613
- return import_node_crypto.default.verify(null, dataBuf, publicKey, sigBuf);
2614
- }
2615
- const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
2616
- const verifyKey = isRsaPssAlg(jwtAlg) ? {
2617
- key: publicKey,
2618
- padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
2619
- saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
2620
- } : publicKey;
2621
- return import_node_crypto.default.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
2622
- } catch {
2623
- return false;
2624
- }
2625
- }
2626
- function pemToPublicJwk(pem) {
2627
- const key = import_node_crypto.default.createPublicKey({ key: pem, format: "pem" });
2628
- return key.export({ format: "jwk" });
2629
- }
2630
- function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
2631
- const header = { alg, typ: "JWT", kid };
2632
- const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
2633
- const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
2634
- const signatureInput = `${headerB64}.${payloadB64}`;
2635
- const nodeAlgorithm = jwtAlgToNodeAlg(alg);
2636
- const signature = rsaSign(
2637
- nodeAlgorithm,
2638
- Buffer.from(signatureInput),
2639
- privateKey,
2640
- isRsaPssAlg(alg) ? {
2641
- padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
2642
- saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
2643
- } : void 0
2644
- );
2645
- const signatureB64 = signature.toString("base64url");
2646
- return `${headerB64}.${payloadB64}.${signatureB64}`;
2647
- }
2648
- var import_node_crypto, nodeCrypto;
2649
- var init_node = __esm({
2650
- "libs/utils/src/crypto/node.ts"() {
2651
- "use strict";
2652
- import_node_crypto = __toESM(require("node:crypto"));
2653
- init_jwt_alg();
2654
- init_jwt_alg();
2655
- nodeCrypto = {
2656
- randomUUID() {
2657
- return import_node_crypto.default.randomUUID();
2658
- },
2659
- randomBytes(length) {
2660
- return toUint8Array(import_node_crypto.default.randomBytes(length));
2661
- },
2662
- sha256(data) {
2663
- const hash = import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest();
2664
- return toUint8Array(hash);
2665
- },
2666
- sha256Hex(data) {
2667
- return import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest("hex");
2668
- },
2669
- hmacSha256(key, data) {
2670
- const hmac2 = import_node_crypto.default.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
2671
- return toUint8Array(hmac2);
2672
- },
2673
- hkdfSha256(ikm, salt, info, length) {
2674
- const ikmBuf = Buffer.from(ikm);
2675
- const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
2676
- const prk = import_node_crypto.default.createHmac("sha256", saltBuf).update(ikmBuf).digest();
2677
- const hashLen = 32;
2678
- const n = Math.ceil(length / hashLen);
2679
- const chunks = [];
2680
- let prev = Buffer.alloc(0);
2681
- for (let i = 1; i <= n; i++) {
2682
- prev = import_node_crypto.default.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
2683
- chunks.push(prev);
2684
- }
2685
- return toUint8Array(Buffer.concat(chunks).subarray(0, length));
2686
- },
2687
- encryptAesGcm(key, plaintext, iv) {
2688
- const cipher = import_node_crypto.default.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
2689
- const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
2690
- const tag = cipher.getAuthTag();
2691
- return {
2692
- ciphertext: toUint8Array(encrypted),
2693
- tag: toUint8Array(tag)
2694
- };
2695
- },
2696
- decryptAesGcm(key, ciphertext, iv, tag) {
2697
- const decipher = import_node_crypto.default.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
2698
- decipher.setAuthTag(Buffer.from(tag));
2699
- const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
2700
- return toUint8Array(decrypted);
2701
- },
2702
- timingSafeEqual(a, b) {
2703
- if (a.length !== b.length) return false;
2704
- return import_node_crypto.default.timingSafeEqual(Buffer.from(a), Buffer.from(b));
2705
- }
2706
- };
2707
- }
2708
- });
2709
-
2710
2544
  // libs/utils/src/crypto/browser.ts
2711
2545
  var browser_exports = {};
2712
2546
  __export(browser_exports, {
2713
2547
  browserCrypto: () => browserCrypto,
2714
2548
  cryptoProvider: () => browserCrypto,
2549
+ generateRsaKeyPair: () => generateRsaKeyPair,
2715
2550
  isRsaPssAlg: () => isRsaPssAlg,
2716
2551
  jwtAlgToWebCryptoAlg: () => jwtAlgToWebCryptoAlg,
2717
- rsaVerifyBrowser: () => rsaVerifyBrowser
2552
+ pemToPublicJwk: () => pemToPublicJwk,
2553
+ rsaSignBase64Url: () => rsaSignBase64Url,
2554
+ rsaVerify: () => rsaVerify,
2555
+ rsaVerifyBrowser: () => rsaVerifyBrowser,
2556
+ rsaVerifySync: () => rsaVerifySync
2718
2557
  });
2719
2558
  function toBytes(data) {
2720
2559
  if (typeof data === "string") {
@@ -2743,6 +2582,24 @@ function constantTimeEqual(a, b) {
2743
2582
  }
2744
2583
  return result === 0;
2745
2584
  }
2585
+ function nodeOnly(name) {
2586
+ throw new Error(`${name} is only available in Node.js runtimes`);
2587
+ }
2588
+ function rsaVerify() {
2589
+ return nodeOnly("rsaVerify");
2590
+ }
2591
+ function rsaSignBase64Url() {
2592
+ return nodeOnly("rsaSignBase64Url");
2593
+ }
2594
+ function rsaVerifySync() {
2595
+ return nodeOnly("rsaVerifySync");
2596
+ }
2597
+ function pemToPublicJwk() {
2598
+ return nodeOnly("pemToPublicJwk");
2599
+ }
2600
+ function generateRsaKeyPair() {
2601
+ return nodeOnly("generateRsaKeyPair");
2602
+ }
2746
2603
  async function rsaVerifyBrowser(jwtAlg, data, publicJwk, signature) {
2747
2604
  if (typeof globalThis.crypto?.subtle === "undefined") {
2748
2605
  throw new Error("WebCrypto API (crypto.subtle) is not available in this environment");
@@ -2774,15 +2631,15 @@ function getSaltLength(jwtAlg) {
2774
2631
  return 32;
2775
2632
  }
2776
2633
  }
2777
- var import_sha2, import_hmac, import_hkdf, import_utils, import_aes, browserCrypto;
2634
+ var import_aes, import_hkdf, import_hmac, import_sha2, import_utils, browserCrypto;
2778
2635
  var init_browser = __esm({
2779
2636
  "libs/utils/src/crypto/browser.ts"() {
2780
2637
  "use strict";
2781
- import_sha2 = require("@noble/hashes/sha2.js");
2782
- import_hmac = require("@noble/hashes/hmac.js");
2638
+ import_aes = require("@noble/ciphers/aes.js");
2783
2639
  import_hkdf = require("@noble/hashes/hkdf.js");
2640
+ import_hmac = require("@noble/hashes/hmac.js");
2641
+ import_sha2 = require("@noble/hashes/sha2.js");
2784
2642
  import_utils = require("@noble/hashes/utils.js");
2785
- import_aes = require("@noble/ciphers/aes.js");
2786
2643
  init_jwt_alg();
2787
2644
  init_jwt_alg();
2788
2645
  browserCrypto = {
@@ -2828,38 +2685,38 @@ var init_browser = __esm({
2828
2685
 
2829
2686
  // libs/utils/src/crypto/index.ts
2830
2687
  function getCrypto() {
2831
- return import_crypto_provider2.cryptoProvider;
2688
+ return platformCrypto.cryptoProvider;
2832
2689
  }
2833
- function rsaVerify2(jwtAlg, data, publicJwk, signature) {
2690
+ function rsaVerify3(jwtAlg, data, publicJwk, signature) {
2834
2691
  if (isNode()) {
2835
- return Promise.resolve((init_node(), __toCommonJS(node_exports)).rsaVerify(jwtAlg, data, publicJwk, signature));
2692
+ return Promise.resolve(platformCrypto.rsaVerify(jwtAlg, data, publicJwk, signature));
2836
2693
  }
2837
2694
  const { rsaVerifyBrowser: rsaVerifyBrowser2 } = (init_browser(), __toCommonJS(browser_exports));
2838
2695
  return rsaVerifyBrowser2(jwtAlg, data, publicJwk, signature);
2839
2696
  }
2840
- function rsaSignBase64Url2(jwtAlg, data, privateJwk) {
2697
+ function rsaSignBase64Url3(jwtAlg, data, privateJwk) {
2841
2698
  if (!isNode()) {
2842
2699
  throw new Error("rsaSignBase64Url is only available in Node.js runtimes");
2843
2700
  }
2844
- return (init_node(), __toCommonJS(node_exports)).rsaSignBase64Url(jwtAlg, data, privateJwk);
2701
+ return platformCrypto.rsaSignBase64Url(jwtAlg, data, privateJwk);
2845
2702
  }
2846
- function rsaVerifySync2(jwtAlg, data, publicJwk, signature) {
2703
+ function rsaVerifySync3(jwtAlg, data, publicJwk, signature) {
2847
2704
  if (!isNode()) {
2848
2705
  throw new Error("rsaVerifySync is only available in Node.js runtimes; use the async rsaVerify in browsers");
2849
2706
  }
2850
- return (init_node(), __toCommonJS(node_exports)).rsaVerifySync(jwtAlg, data, publicJwk, signature);
2707
+ return platformCrypto.rsaVerifySync(jwtAlg, data, publicJwk, signature);
2851
2708
  }
2852
- function pemToPublicJwk2(pem) {
2709
+ function pemToPublicJwk3(pem) {
2853
2710
  if (!isNode()) {
2854
2711
  throw new Error("pemToPublicJwk is only available in Node.js runtimes");
2855
2712
  }
2856
- return (init_node(), __toCommonJS(node_exports)).pemToPublicJwk(pem);
2713
+ return platformCrypto.pemToPublicJwk(pem);
2857
2714
  }
2858
- function generateRsaKeyPair2(modulusLength, alg) {
2715
+ function generateRsaKeyPair3(modulusLength, alg) {
2859
2716
  if (!isNode()) {
2860
2717
  throw new Error("generateRsaKeyPair is only available in Node.js runtimes");
2861
2718
  }
2862
- return (init_node(), __toCommonJS(node_exports)).generateRsaKeyPair(modulusLength, alg);
2719
+ return platformCrypto.generateRsaKeyPair(modulusLength, alg);
2863
2720
  }
2864
2721
  function randomUUID() {
2865
2722
  return getCrypto().randomUUID();
@@ -2965,11 +2822,11 @@ function base64Decode(data) {
2965
2822
  function sha256Base64url(data) {
2966
2823
  return base64urlEncode(sha256(data));
2967
2824
  }
2968
- var import_crypto_provider2, HKDF_SHA256_MAX_LENGTH;
2825
+ var platformCrypto, HKDF_SHA256_MAX_LENGTH;
2969
2826
  var init_crypto = __esm({
2970
2827
  "libs/utils/src/crypto/index.ts"() {
2971
2828
  "use strict";
2972
- import_crypto_provider2 = require("#crypto-provider");
2829
+ platformCrypto = __toESM(require("#crypto-provider"));
2973
2830
  init_runtime();
2974
2831
  init_jwt_alg();
2975
2832
  init_runtime();
@@ -3017,6 +2874,73 @@ var init_redis_error_listener = __esm({
3017
2874
  }
3018
2875
  });
3019
2876
 
2877
+ // libs/utils/src/storage/redis-url.ts
2878
+ function decodeOrRaw(encoded) {
2879
+ try {
2880
+ return decodeURIComponent(encoded);
2881
+ } catch {
2882
+ return encoded;
2883
+ }
2884
+ }
2885
+ function integerOrUndefined(text) {
2886
+ const value = Number.parseInt(text ?? "", 10);
2887
+ return Number.isNaN(value) ? void 0 : value;
2888
+ }
2889
+ function readRedisUrl(rawUrl) {
2890
+ let url;
2891
+ try {
2892
+ url = new URL(rawUrl);
2893
+ } catch {
2894
+ return void 0;
2895
+ }
2896
+ if (url.protocol !== "redis:" && url.protocol !== "rediss:") return void 0;
2897
+ const query = Object.fromEntries(url.searchParams);
2898
+ const { host, port, username, password, db, ...otherOptions } = query;
2899
+ const queryOptions = {};
2900
+ for (const [name, value = ""] of Object.entries(otherOptions)) {
2901
+ queryOptions[name] = name === "family" ? integerOrUndefined(value) ?? value : value;
2902
+ }
2903
+ return {
2904
+ host: url.hostname.replace(/^\[(.*)\]$/, "$1") || host,
2905
+ port: integerOrUndefined(url.port || port),
2906
+ username: decodeOrRaw(url.username) || username || void 0,
2907
+ password: decodeOrRaw(url.password) || password || void 0,
2908
+ db: integerOrUndefined(url.pathname.replace(/^\/+/, "") || db),
2909
+ tls: url.protocol === "rediss:",
2910
+ queryOptions
2911
+ };
2912
+ }
2913
+ function mergeRedisUrlFields(rawUrl, fields) {
2914
+ const connection = readRedisUrl(rawUrl);
2915
+ if (!connection) return void 0;
2916
+ const fillIns = {};
2917
+ const conflicts = [];
2918
+ if (fields.host !== void 0 && fields.host !== connection.host) conflicts.push("host");
2919
+ if (fields.port !== void 0) {
2920
+ if (connection.port === void 0) fillIns.port = fields.port;
2921
+ else if (fields.port !== connection.port) conflicts.push("port");
2922
+ }
2923
+ if (fields.password !== void 0) {
2924
+ if (connection.password === void 0) fillIns.password = fields.password;
2925
+ else if (fields.password !== connection.password) conflicts.push("password");
2926
+ }
2927
+ if (fields.db !== void 0) {
2928
+ if (connection.db === void 0) fillIns.db = fields.db;
2929
+ else if (fields.db !== connection.db) conflicts.push("db");
2930
+ }
2931
+ if (fields.tls === true && !connection.tls) fillIns.tls = true;
2932
+ if (fields.tls === false && connection.tls) conflicts.push("tls");
2933
+ return { connection, fillIns, conflicts };
2934
+ }
2935
+ function describeRedisUrlConflicts(conflicts) {
2936
+ return `redis ${conflicts.join(", ")} contradict${conflicts.length === 1 ? "s" : ""} redis.url. Fields beside a url only fill in what the URL leaves out (port, password, db, tls); put the value in the URL or drop the field.`;
2937
+ }
2938
+ var init_redis_url = __esm({
2939
+ "libs/utils/src/storage/redis-url.ts"() {
2940
+ "use strict";
2941
+ }
2942
+ });
2943
+
3020
2944
  // libs/utils/src/storage/utils/index.ts
3021
2945
  var init_utils = __esm({
3022
2946
  "libs/utils/src/storage/utils/index.ts"() {
@@ -3056,17 +2980,49 @@ function getRedisClass() {
3056
2980
  });
3057
2981
  }
3058
2982
  }
2983
+ function resolveUrlOptions(connectionUrl, options) {
2984
+ const { url: configUrl, ...siblingFields } = { url: void 0, ...options.config };
2985
+ if (options.url && configUrl && configUrl !== options.url) {
2986
+ throw new StorageConfigError("redis", "redis.url and redis.config.url name different servers; set one of them.");
2987
+ }
2988
+ return urlConnectionOptions(connectionUrl, siblingFields);
2989
+ }
2990
+ function urlConnectionOptions(url, fields) {
2991
+ const merge = mergeRedisUrlFields(url, fields);
2992
+ if (!merge) return void 0;
2993
+ if (merge.conflicts.length > 0) {
2994
+ throw new StorageConfigError("redis", describeRedisUrlConflicts(merge.conflicts));
2995
+ }
2996
+ const { queryOptions, tls, ...stated } = merge.connection;
2997
+ return { ...queryOptions, ...stated, ...toFillInOptions(merge.fillIns), ...tls ? { tls: {} } : {} };
2998
+ }
2999
+ function toFillInOptions({ port, password, db, tls }) {
3000
+ return {
3001
+ ...port !== void 0 ? { port } : {},
3002
+ ...password !== void 0 ? { password } : {},
3003
+ ...db !== void 0 ? { db } : {},
3004
+ ...tls ? { tls: {} } : {}
3005
+ };
3006
+ }
3059
3007
  function createRedisClient(options) {
3008
+ const urlOptions = options.url ? urlConnectionOptions(options.url, options) : void 0;
3060
3009
  const RedisClass = getRedisClass();
3061
3010
  const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
3062
- const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
3063
- ...baseOptions,
3064
- host: options.host ?? "localhost",
3065
- port: options.port ?? 6379,
3066
- password: options.password,
3067
- db: options.db ?? 0,
3068
- tls: options.tls ? {} : void 0
3069
- });
3011
+ let client;
3012
+ if (urlOptions) {
3013
+ client = new RedisClass({ ...baseOptions, ...urlOptions });
3014
+ } else if (options.url) {
3015
+ client = new RedisClass(options.url, baseOptions);
3016
+ } else {
3017
+ client = new RedisClass({
3018
+ ...baseOptions,
3019
+ host: options.host ?? "localhost",
3020
+ port: options.port ?? 6379,
3021
+ password: options.password,
3022
+ db: options.db ?? 0,
3023
+ tls: options.tls ? {} : void 0
3024
+ });
3025
+ }
3070
3026
  attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
3071
3027
  return client;
3072
3028
  }
@@ -3076,6 +3032,7 @@ var init_redis = __esm({
3076
3032
  "use strict";
3077
3033
  init_errors();
3078
3034
  init_redis_error_listener();
3035
+ init_redis_url();
3079
3036
  init_utils();
3080
3037
  init_compare_and_delete();
3081
3038
  init_base();
@@ -3089,6 +3046,9 @@ var init_redis = __esm({
3089
3046
  connecting;
3090
3047
  keyPrefix;
3091
3048
  subscriptionHandlers = /* @__PURE__ */ new Map();
3049
+ connectionUrl;
3050
+ /** The connection options of a URL this adapter reads itself; undefined for one only ioredis reads. */
3051
+ urlOptions;
3092
3052
  constructor(options = {}) {
3093
3053
  super();
3094
3054
  const hasClient = options.client !== void 0;
@@ -3110,6 +3070,9 @@ var init_redis = __esm({
3110
3070
  this.options = options;
3111
3071
  this.ownsClient = !hasClient;
3112
3072
  this.keyPrefix = options.keyPrefix ?? "";
3073
+ const configUrl = options.config?.url;
3074
+ this.connectionUrl = options.url ?? (typeof configUrl === "string" ? configUrl : void 0);
3075
+ this.urlOptions = this.connectionUrl ? resolveUrlOptions(this.connectionUrl, options) : void 0;
3113
3076
  }
3114
3077
  // ============================================
3115
3078
  // Connection Lifecycle
@@ -3131,12 +3094,7 @@ var init_redis = __esm({
3131
3094
  if (this.options.client) {
3132
3095
  client = this.options.client;
3133
3096
  } else {
3134
- const RedisClass = getRedisClass();
3135
- if (this.options.url) {
3136
- client = new RedisClass(this.options.url, this.buildRedisOptions());
3137
- } else {
3138
- client = new RedisClass(this.buildRedisOptions());
3139
- }
3097
+ client = this.newClient();
3140
3098
  client.on("error", recordSocketError);
3141
3099
  }
3142
3100
  await client.ping();
@@ -3369,14 +3327,11 @@ var init_redis = __esm({
3369
3327
  * Build Redis options from config.
3370
3328
  */
3371
3329
  buildRedisOptions() {
3372
- if (this.options.url) {
3373
- return {
3374
- lazyConnect: false,
3375
- maxRetriesPerRequest: 3
3376
- };
3330
+ if (this.connectionUrl) {
3331
+ return { lazyConnect: false, maxRetriesPerRequest: 3, ...this.urlOptions };
3377
3332
  }
3378
3333
  const config = this.options.config;
3379
- if (!config) {
3334
+ if (!config || typeof config.url === "string" || config.host === void 0) {
3380
3335
  throw new StorageConfigError("redis", "Redis config is required when URL is not provided");
3381
3336
  }
3382
3337
  return {
@@ -3389,16 +3344,23 @@ var init_redis = __esm({
3389
3344
  maxRetriesPerRequest: 3
3390
3345
  };
3391
3346
  }
3347
+ /**
3348
+ * Create a client this adapter owns. A URL only ioredis reads goes to it as is.
3349
+ */
3350
+ newClient() {
3351
+ const RedisClass = getRedisClass();
3352
+ if (this.connectionUrl && !this.urlOptions) {
3353
+ return new RedisClass(this.connectionUrl, this.buildRedisOptions());
3354
+ }
3355
+ return new RedisClass(this.buildRedisOptions());
3356
+ }
3392
3357
  /**
3393
3358
  * Create subscriber connection.
3394
3359
  */
3395
3360
  async createSubscriber() {
3396
- const RedisClass = getRedisClass();
3397
3361
  let subscriber;
3398
- if (this.options.url) {
3399
- subscriber = new RedisClass(this.options.url);
3400
- } else if (this.options.config) {
3401
- subscriber = new RedisClass(this.buildRedisOptions());
3362
+ if (this.connectionUrl || this.options.config) {
3363
+ subscriber = this.newClient();
3402
3364
  } else if (this.options.client) {
3403
3365
  subscriber = this.options.client.duplicate();
3404
3366
  } else {
@@ -3447,7 +3409,8 @@ var init_redis = __esm({
3447
3409
  // libs/utils/src/storage/adapters/vercel-kv.ts
3448
3410
  var vercel_kv_exports = {};
3449
3411
  __export(vercel_kv_exports, {
3450
- VercelKvStorageAdapter: () => VercelKvStorageAdapter
3412
+ VercelKvStorageAdapter: () => VercelKvStorageAdapter,
3413
+ createVercelKvClient: () => createVercelKvClient
3451
3414
  });
3452
3415
  async function loadVercelKv() {
3453
3416
  try {
@@ -3459,6 +3422,15 @@ async function loadVercelKv() {
3459
3422
  throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv");
3460
3423
  }
3461
3424
  }
3425
+ async function createVercelKvClient(connection = {}) {
3426
+ const url = connection.url ?? process.env["KV_REST_API_URL"];
3427
+ const token = connection.token ?? process.env["KV_REST_API_TOKEN"];
3428
+ if (!url || !token) {
3429
+ throw new StorageConfigError("vercel-kv", "Pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.");
3430
+ }
3431
+ const { createClient } = await loadVercelKv();
3432
+ return createClient({ url, token, cache: void 0, automaticDeserialization: false });
3433
+ }
3462
3434
  var VercelKvStorageAdapter;
3463
3435
  var init_vercel_kv = __esm({
3464
3436
  "libs/utils/src/storage/adapters/vercel-kv.ts"() {
@@ -3491,13 +3463,7 @@ var init_vercel_kv = __esm({
3491
3463
  async connect() {
3492
3464
  if (this.connected) return;
3493
3465
  try {
3494
- const { createClient } = await loadVercelKv();
3495
- const url = this.options.url;
3496
- const token = this.options.token;
3497
- if (!url || !token) {
3498
- throw new StorageConfigError("vercel-kv", "URL and token are required");
3499
- }
3500
- this.client = createClient({ url, token, cache: void 0, automaticDeserialization: false });
3466
+ this.client = await createVercelKvClient({ url: this.options.url, token: this.options.token });
3501
3467
  await this.client.exists("__healthcheck__");
3502
3468
  this.connected = true;
3503
3469
  } catch (e) {
@@ -3670,9 +3636,12 @@ var upstash_exports = {};
3670
3636
  __export(upstash_exports, {
3671
3637
  UpstashStorageAdapter: () => UpstashStorageAdapter
3672
3638
  });
3673
- function getUpstashRedis() {
3639
+ async function loadUpstashRedis() {
3674
3640
  try {
3675
- return require("@upstash/redis");
3641
+ const mod = await import("@upstash/redis");
3642
+ const resolved = typeof mod.Redis === "function" ? mod : mod.default;
3643
+ if (!resolved || typeof resolved.Redis !== "function") throw new Error("Redis missing");
3644
+ return resolved;
3676
3645
  } catch {
3677
3646
  throw new Error(
3678
3647
  "@upstash/redis is required for Upstash storage adapter. Install it with: npm install @upstash/redis"
@@ -3717,7 +3686,7 @@ var init_upstash = __esm({
3717
3686
  async connect() {
3718
3687
  if (this.connected) return;
3719
3688
  try {
3720
- const { Redis } = getUpstashRedis();
3689
+ const { Redis } = await loadUpstashRedis();
3721
3690
  const url = this.options.url;
3722
3691
  const token = this.options.token;
3723
3692
  if (!url || !token) {
@@ -4184,9 +4153,11 @@ __export(index_exports, {
4184
4153
  createSafeRegExp: () => createSafeRegExp,
4185
4154
  createSecretData: () => createSecretData,
4186
4155
  createStorage: () => createStorage,
4156
+ createVercelKvClient: () => createVercelKvClient,
4187
4157
  decryptAesGcm: () => decryptAesGcm,
4188
4158
  decryptValue: () => decryptValue,
4189
4159
  deleteSecret: () => deleteSecret,
4160
+ describeRedisUrlConflicts: () => describeRedisUrlConflicts,
4190
4161
  deserializeAndDecrypt: () => deserializeAndDecrypt,
4191
4162
  deserializeBlob: () => deserializeBlob,
4192
4163
  detectRuntimeContext: () => import_runtime_context.detectRuntimeContext,
@@ -4217,7 +4188,7 @@ __export(index_exports, {
4217
4188
  generateCodeChallenge: () => generateCodeChallenge,
4218
4189
  generateCodeVerifier: () => generateCodeVerifier,
4219
4190
  generatePkcePair: () => generatePkcePair,
4220
- generateRsaKeyPair: () => generateRsaKeyPair2,
4191
+ generateRsaKeyPair: () => generateRsaKeyPair3,
4221
4192
  generateSecret: () => generateSecret,
4222
4193
  getAsyncContextMode: () => import_async_context.getAsyncContextMode,
4223
4194
  getCookie: () => getCookie,
@@ -4270,6 +4241,7 @@ __export(index_exports, {
4270
4241
  loadSecret: () => loadSecret,
4271
4242
  matchUriTemplate: () => matchUriTemplate,
4272
4243
  matchesPattern: () => matchesPattern,
4244
+ mergeRedisUrlFields: () => mergeRedisUrlFields,
4273
4245
  mkdir: () => mkdir,
4274
4246
  mkdtemp: () => mkdtemp,
4275
4247
  normalizeTTL: () => normalizeTTL,
@@ -4281,7 +4253,7 @@ __export(index_exports, {
4281
4253
  pathJoin: () => import_path2.join,
4282
4254
  pathResolve: () => import_path2.resolve,
4283
4255
  pathToFileURL: () => import_path2.pathToFileURL,
4284
- pemToPublicJwk: () => pemToPublicJwk2,
4256
+ pemToPublicJwk: () => pemToPublicJwk3,
4285
4257
  processPlatformToolCalls: () => processPlatformToolCalls,
4286
4258
  randomBytes: () => randomBytes,
4287
4259
  randomUUID: () => randomUUID,
@@ -4296,9 +4268,9 @@ __export(index_exports, {
4296
4268
  resetRuntimeContext: () => import_runtime_context.resetRuntimeContext,
4297
4269
  resolveSecretPath: () => resolveSecretPath,
4298
4270
  rm: () => rm,
4299
- rsaSignBase64Url: () => rsaSignBase64Url2,
4300
- rsaVerify: () => rsaVerify2,
4301
- rsaVerifySync: () => rsaVerifySync2,
4271
+ rsaSignBase64Url: () => rsaSignBase64Url3,
4272
+ rsaVerify: () => rsaVerify3,
4273
+ rsaVerifySync: () => rsaVerifySync3,
4302
4274
  runCmd: () => runCmd,
4303
4275
  runRequestExclusive: () => import_async_context.runRequestExclusive,
4304
4276
  safeExec: () => safeExec,
@@ -5446,6 +5418,7 @@ function getDetectedStorageType() {
5446
5418
 
5447
5419
  // libs/utils/src/storage/index.ts
5448
5420
  init_redis_error_listener();
5421
+ init_redis_url();
5449
5422
  init_errors();
5450
5423
 
5451
5424
  // libs/utils/src/storage/typed-storage.ts
@@ -6160,9 +6133,11 @@ function setMachineIdOverride(id) {
6160
6133
  createSafeRegExp,
6161
6134
  createSecretData,
6162
6135
  createStorage,
6136
+ createVercelKvClient,
6163
6137
  decryptAesGcm,
6164
6138
  decryptValue,
6165
6139
  deleteSecret,
6140
+ describeRedisUrlConflicts,
6166
6141
  deserializeAndDecrypt,
6167
6142
  deserializeBlob,
6168
6143
  detectRuntimeContext,
@@ -6246,6 +6221,7 @@ function setMachineIdOverride(id) {
6246
6221
  loadSecret,
6247
6222
  matchUriTemplate,
6248
6223
  matchesPattern,
6224
+ mergeRedisUrlFields,
6249
6225
  mkdir,
6250
6226
  mkdtemp,
6251
6227
  normalizeTTL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
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,11 +32,15 @@
32
32
  "@noble/hashes": "^2.0.1"
33
33
  },
34
34
  "peerDependencies": {
35
- "@frontmcp/lazy-zod": "1.9.1",
35
+ "@frontmcp/lazy-zod": "1.9.3",
36
+ "@upstash/redis": "^1.34.0",
36
37
  "@vercel/kv": "^2.0.0 || ^3.0.0",
37
38
  "ioredis": "^5.0.0"
38
39
  },
39
40
  "peerDependenciesMeta": {
41
+ "@upstash/redis": {
42
+ "optional": true
43
+ },
40
44
  "@vercel/kv": {
41
45
  "optional": true
42
46
  },
@@ -7,7 +7,7 @@ export { BaseStorageAdapter } from './base';
7
7
  export { MemoryStorageAdapter } from './memory';
8
8
  export { RedisStorageAdapter, createRedisClient } from './redis';
9
9
  export type { CreateRedisClientOptions } from './redis';
10
- export { VercelKvStorageAdapter } from './vercel-kv';
10
+ export { VercelKvStorageAdapter, createVercelKvClient, type VercelKvConnection } from './vercel-kv';
11
11
  export { UpstashStorageAdapter } from './upstash';
12
12
  export { CloudflareKvStorageAdapter } from './cloudflare-kv';
13
13
  export { FileSystemStorageAdapter } from './filesystem';