@frontmcp/utils 1.4.1 → 1.5.0

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.
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/async-context/browser-async-context.ts
4
4
  var AsyncLocalStorage = class {
5
5
  stack = [];
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/async-context/node-async-context.ts
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  export {
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/crypto/browser.ts
4
4
  import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
5
5
  import { hmac } from "@noble/hashes/hmac.js";
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/crypto/node.ts
4
4
  import crypto from "node:crypto";
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/env/browser-env.ts
4
4
  function getEnv(_key, defaultValue) {
5
5
  return defaultValue;
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/env/node-env.ts
4
4
  function getEnv(key, defaultValue) {
5
5
  return process.env[key] ?? defaultValue;
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/event-emitter/browser-event-emitter.ts
4
4
  var EventEmitter = class {
5
5
  events = /* @__PURE__ */ new Map();
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  // libs/utils/src/event-emitter/node-event-emitter.ts
4
4
  import { EventEmitter } from "events";
5
5
  export {
package/esm/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
- const require = __frontmcpCreateRequire(import.meta.url);
2
+ const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -10,8 +10,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  if (typeof require !== "undefined") return require.apply(this, arguments);
11
11
  throw Error('Dynamic require of "' + x + '" is not supported');
12
12
  });
13
- var __esm = (fn, res) => function __init() {
14
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ var __esm = (fn, res, err) => function __init() {
14
+ if (err) throw err[0];
15
+ try {
16
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
17
+ } catch (e) {
18
+ throw err = [e], e;
19
+ }
15
20
  };
16
21
  var __export = (target, all) => {
17
22
  for (var name in all)
@@ -75,8 +80,8 @@ async function readFile(p, encoding = "utf8") {
75
80
  return fsp.readFile(p, encoding);
76
81
  }
77
82
  function readFileSync(p, encoding = "utf8") {
78
- const fs2 = getFs();
79
- return fs2.readFileSync(p, encoding);
83
+ const fs = getFs();
84
+ return fs.readFileSync(p, encoding);
80
85
  }
81
86
  async function readFileBuffer(p) {
82
87
  const fsp = getFsp();
@@ -91,8 +96,8 @@ async function mkdir(p, options) {
91
96
  await fsp.mkdir(p, options);
92
97
  }
93
98
  function ensureDirSync(p) {
94
- const fs2 = getFs();
95
- fs2.mkdirSync(p, { recursive: true });
99
+ const fs = getFs();
100
+ fs.mkdirSync(p, { recursive: true });
96
101
  }
97
102
  async function rename(oldPath, newPath) {
98
103
  const fsp = getFsp();
@@ -153,8 +158,8 @@ async function readdir(p) {
153
158
  return fsp.readdir(p);
154
159
  }
155
160
  function readdirSync(p) {
156
- const fs2 = getFs();
157
- return fs2.readdirSync(p);
161
+ const fs = getFs();
162
+ return fs.readdirSync(p);
158
163
  }
159
164
  async function rm(p, options) {
160
165
  const fsp = getFsp();
@@ -187,8 +192,8 @@ function runCmd(cmd, args, opts = {}) {
187
192
  });
188
193
  }
189
194
  function watchFile(p, onChange, options) {
190
- const fs2 = getFs();
191
- const watcher = fs2.watch(p, { persistent: false, recursive: options?.recursive === true }, (eventType, filename) => {
195
+ const fs = getFs();
196
+ const watcher = fs.watch(p, { persistent: false, recursive: options?.recursive === true }, (eventType, filename) => {
192
197
  onChange(eventType, filename);
193
198
  });
194
199
  return {
@@ -1016,6 +1021,7 @@ var init_errors = __esm({
1016
1021
  this.backend = backend;
1017
1022
  this.name = "StorageConnectionError";
1018
1023
  }
1024
+ backend;
1019
1025
  };
1020
1026
  StorageOperationError = class extends StorageError {
1021
1027
  constructor(operation, key, message, cause) {
@@ -1024,6 +1030,8 @@ var init_errors = __esm({
1024
1030
  this.key = key;
1025
1031
  this.name = "StorageOperationError";
1026
1032
  }
1033
+ operation;
1034
+ key;
1027
1035
  };
1028
1036
  StorageNotSupportedError = class extends StorageError {
1029
1037
  constructor(operation, backend, suggestion) {
@@ -1033,6 +1041,8 @@ var init_errors = __esm({
1033
1041
  this.backend = backend;
1034
1042
  this.name = "StorageNotSupportedError";
1035
1043
  }
1044
+ operation;
1045
+ backend;
1036
1046
  };
1037
1047
  StorageConfigError = class extends StorageError {
1038
1048
  constructor(backend, message) {
@@ -1040,6 +1050,7 @@ var init_errors = __esm({
1040
1050
  this.backend = backend;
1041
1051
  this.name = "StorageConfigError";
1042
1052
  }
1053
+ backend;
1043
1054
  };
1044
1055
  StorageTTLError = class extends StorageError {
1045
1056
  constructor(ttl, message) {
@@ -1047,6 +1058,7 @@ var init_errors = __esm({
1047
1058
  this.ttl = ttl;
1048
1059
  this.name = "StorageTTLError";
1049
1060
  }
1061
+ ttl;
1050
1062
  };
1051
1063
  StoragePatternError = class extends StorageError {
1052
1064
  constructor(pattern, message) {
@@ -1054,6 +1066,7 @@ var init_errors = __esm({
1054
1066
  this.pattern = pattern;
1055
1067
  this.name = "StoragePatternError";
1056
1068
  }
1069
+ pattern;
1057
1070
  };
1058
1071
  StorageNotConnectedError = class extends StorageError {
1059
1072
  constructor(backend) {
@@ -1061,6 +1074,7 @@ var init_errors = __esm({
1061
1074
  this.backend = backend;
1062
1075
  this.name = "StorageNotConnectedError";
1063
1076
  }
1077
+ backend;
1064
1078
  };
1065
1079
  EncryptedStorageError = class extends StorageError {
1066
1080
  constructor(message) {
@@ -3809,6 +3823,154 @@ var init_upstash = __esm({
3809
3823
  }
3810
3824
  });
3811
3825
 
3826
+ // libs/utils/src/storage/adapters/cloudflare-kv.ts
3827
+ var cloudflare_kv_exports = {};
3828
+ __export(cloudflare_kv_exports, {
3829
+ CloudflareKvStorageAdapter: () => CloudflareKvStorageAdapter
3830
+ });
3831
+ function literalPrefix(pattern) {
3832
+ const star = pattern.indexOf("*");
3833
+ const q = pattern.indexOf("?");
3834
+ const idx = star === -1 ? q : q === -1 ? star : Math.min(star, q);
3835
+ return idx === -1 ? pattern : pattern.slice(0, idx);
3836
+ }
3837
+ var CF_KV_MIN_TTL_SECONDS, CloudflareKvStorageAdapter;
3838
+ var init_cloudflare_kv = __esm({
3839
+ "libs/utils/src/storage/adapters/cloudflare-kv.ts"() {
3840
+ "use strict";
3841
+ init_errors();
3842
+ init_pattern();
3843
+ init_ttl();
3844
+ init_base();
3845
+ CF_KV_MIN_TTL_SECONDS = 60;
3846
+ CloudflareKvStorageAdapter = class extends BaseStorageAdapter {
3847
+ backendName = "cloudflare-kv";
3848
+ kv;
3849
+ keyPrefix;
3850
+ constructor(options) {
3851
+ super();
3852
+ if (!options?.namespace) {
3853
+ throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).");
3854
+ }
3855
+ this.kv = options.namespace;
3856
+ this.keyPrefix = options.keyPrefix ?? "";
3857
+ }
3858
+ // ── Lifecycle ──────────────────────────────────────────────────────────
3859
+ // A KV binding is always available; "connection" is just a readiness flag.
3860
+ async connect() {
3861
+ this.connected = true;
3862
+ }
3863
+ async disconnect() {
3864
+ this.connected = false;
3865
+ }
3866
+ async ping() {
3867
+ if (!this.connected) return false;
3868
+ try {
3869
+ await this.kv.list({ limit: 1 });
3870
+ return true;
3871
+ } catch {
3872
+ return false;
3873
+ }
3874
+ }
3875
+ // ── Core ───────────────────────────────────────────────────────────────
3876
+ async get(key) {
3877
+ this.ensureConnected();
3878
+ return this.kv.get(this.prefixKey(key), "text");
3879
+ }
3880
+ async doSet(key, value, options) {
3881
+ if (options?.ifNotExists || options?.ifExists) {
3882
+ throw new StorageNotSupportedError(
3883
+ "conditional set (ifNotExists/ifExists)",
3884
+ this.backendName,
3885
+ "Cloudflare KV has no compare-and-set; use a Durable Object for atomic conditional writes."
3886
+ );
3887
+ }
3888
+ const putOptions = options?.ttlSeconds !== void 0 ? { expirationTtl: this.toExpirationTtl(options.ttlSeconds, key) } : void 0;
3889
+ await this.kv.put(this.prefixKey(key), value, putOptions);
3890
+ }
3891
+ async delete(key) {
3892
+ this.ensureConnected();
3893
+ const existed = await this.exists(key);
3894
+ await this.kv.delete(this.prefixKey(key));
3895
+ return existed;
3896
+ }
3897
+ async exists(key) {
3898
+ this.ensureConnected();
3899
+ return await this.kv.get(this.prefixKey(key), "text") !== null;
3900
+ }
3901
+ // ── TTL ────────────────────────────────────────────────────────────────
3902
+ async expire(key, ttlSeconds) {
3903
+ this.ensureConnected();
3904
+ const ttl = this.toExpirationTtl(ttlSeconds, key);
3905
+ const value = await this.kv.get(this.prefixKey(key), "text");
3906
+ if (value === null) return false;
3907
+ await this.kv.put(this.prefixKey(key), value, { expirationTtl: ttl });
3908
+ return true;
3909
+ }
3910
+ async ttl(_key) {
3911
+ throw new StorageNotSupportedError(
3912
+ "ttl",
3913
+ this.backendName,
3914
+ "Cloudflare KV does not expose a key's remaining TTL. Track expiry alongside the value if you need it."
3915
+ );
3916
+ }
3917
+ // ── Enumeration ──────────────────────────────────────────────────────────
3918
+ async keys(pattern = "*") {
3919
+ this.ensureConnected();
3920
+ const prefixedPattern = this.prefixKey(pattern);
3921
+ const listPrefix = literalPrefix(prefixedPattern);
3922
+ const out = [];
3923
+ let cursor;
3924
+ do {
3925
+ const page = await this.kv.list({ prefix: listPrefix || void 0, cursor });
3926
+ for (const { name } of page.keys) {
3927
+ if (matchesPattern(name, prefixedPattern)) out.push(this.unprefixKey(name));
3928
+ }
3929
+ cursor = page.list_complete ? void 0 : page.cursor;
3930
+ } while (cursor);
3931
+ return out;
3932
+ }
3933
+ // ── Atomic (unsupported on KV) ───────────────────────────────────────────
3934
+ async incr(key) {
3935
+ return this.incrBy(key, 1);
3936
+ }
3937
+ async decr(key) {
3938
+ return this.incrBy(key, -1);
3939
+ }
3940
+ async incrBy(_key, _amount) {
3941
+ throw new StorageNotSupportedError(
3942
+ "incr/decr/incrBy",
3943
+ this.backendName,
3944
+ "Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters."
3945
+ );
3946
+ }
3947
+ // ── Pub/Sub (unsupported) ────────────────────────────────────────────────
3948
+ getPubSubSuggestion() {
3949
+ return "Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out.";
3950
+ }
3951
+ // ── Helpers ──────────────────────────────────────────────────────────────
3952
+ /** Validate + enforce KV's 60s minimum `expirationTtl`. */
3953
+ toExpirationTtl(ttlSeconds, key) {
3954
+ validateTTL(ttlSeconds);
3955
+ if (ttlSeconds < CF_KV_MIN_TTL_SECONDS) {
3956
+ throw new StorageOperationError(
3957
+ "set",
3958
+ key,
3959
+ `cloudflare-kv: expirationTtl must be >= ${CF_KV_MIN_TTL_SECONDS}s (Cloudflare KV minimum); got ${ttlSeconds}s.`
3960
+ );
3961
+ }
3962
+ return ttlSeconds;
3963
+ }
3964
+ prefixKey(key) {
3965
+ return this.keyPrefix + key;
3966
+ }
3967
+ unprefixKey(key) {
3968
+ return this.keyPrefix && key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key;
3969
+ }
3970
+ };
3971
+ }
3972
+ });
3973
+
3812
3974
  // libs/utils/src/regex/safe-regex.ts
3813
3975
  import { analyzeForReDoS, REDOS_THRESHOLDS } from "@enclave-vm/ast";
3814
3976
  var DEFAULT_MAX_INPUT_LENGTH = 5e4;
@@ -4532,7 +4694,6 @@ function isKnownTarget(value) {
4532
4694
  }
4533
4695
 
4534
4696
  // libs/utils/src/env/provider.ts
4535
- import * as fs from "node:fs";
4536
4697
  var cached2;
4537
4698
  function detectProvider() {
4538
4699
  if (cached2) return cached2;
@@ -4552,7 +4713,8 @@ function detectProviderUncached() {
4552
4713
  if (process.env["RENDER"]) return "render";
4553
4714
  if (process.env["RAILWAY_ENVIRONMENT"]) return "railway";
4554
4715
  try {
4555
- if (fs.existsSync("/.dockerenv")) return "docker";
4716
+ const nodeFs = __require("node:fs");
4717
+ if (nodeFs.existsSync("/.dockerenv")) return "docker";
4556
4718
  } catch {
4557
4719
  }
4558
4720
  return "bare";
@@ -4674,6 +4836,9 @@ var NamespacedStorageImpl = class _NamespacedStorageImpl {
4674
4836
  this.prefix = prefix;
4675
4837
  this.root = root;
4676
4838
  }
4839
+ adapter;
4840
+ prefix;
4841
+ root;
4677
4842
  // ============================================
4678
4843
  // Key Prefixing Helpers
4679
4844
  // ============================================
@@ -4847,6 +5012,16 @@ async function createAdapter(type, config) {
4847
5012
  const { UpstashStorageAdapter: UpstashStorageAdapter2 } = await Promise.resolve().then(() => (init_upstash(), upstash_exports));
4848
5013
  return new UpstashStorageAdapter2(config.upstash);
4849
5014
  }
5015
+ case "cloudflare-kv": {
5016
+ if (!config.cloudflareKv) {
5017
+ throw new StorageConfigError(
5018
+ "cloudflare-kv",
5019
+ "cloudflare-kv requires `cloudflareKv: { namespace }` (the KV binding can't be auto-detected)"
5020
+ );
5021
+ }
5022
+ const { CloudflareKvStorageAdapter: CloudflareKvStorageAdapter2 } = await Promise.resolve().then(() => (init_cloudflare_kv(), cloudflare_kv_exports));
5023
+ return new CloudflareKvStorageAdapter2(config.cloudflareKv);
5024
+ }
4850
5025
  case "auto":
4851
5026
  throw new StorageConfigError("auto", "Auto type should be resolved before calling createAdapter");
4852
5027
  default:
@@ -5495,8 +5670,8 @@ function loadMachineIdSync() {
5495
5670
  if (!isDevPersistenceEnabled()) return null;
5496
5671
  const machineIdPath = resolveMachineIdPath();
5497
5672
  try {
5498
- const fs2 = __require("fs");
5499
- const content = fs2.readFileSync(machineIdPath, "utf8").trim();
5673
+ const fs = __require("fs");
5674
+ const content = fs.readFileSync(machineIdPath, "utf8").trim();
5500
5675
  if (/^[0-9a-f-]{32,36}$/i.test(content)) return content;
5501
5676
  console.warn(`[MachineId] Invalid format at ${machineIdPath}, will regenerate`);
5502
5677
  return null;
@@ -5507,7 +5682,7 @@ function loadMachineIdSync() {
5507
5682
  return null;
5508
5683
  }
5509
5684
  }
5510
- function saveMachineIdAsync(machineId2) {
5685
+ function saveMachineIdAsync(machineId) {
5511
5686
  if (!isDevPersistenceEnabled()) return;
5512
5687
  const machineIdPath = resolveMachineIdPath();
5513
5688
  const path = __require("path");
@@ -5516,13 +5691,13 @@ function saveMachineIdAsync(machineId2) {
5516
5691
  try {
5517
5692
  const fsp = __require("fs").promises;
5518
5693
  await fsp.mkdir(dir, { recursive: true, mode: 448 });
5519
- await fsp.writeFile(machineIdPath, machineId2, { mode: 384 });
5694
+ await fsp.writeFile(machineIdPath, machineId, { mode: 384 });
5520
5695
  } catch (error) {
5521
5696
  console.warn(`[MachineId] Failed to save to ${machineIdPath}: ${error.message}`);
5522
5697
  }
5523
5698
  })();
5524
5699
  }
5525
- var machineId = (() => {
5700
+ function computeMachineId() {
5526
5701
  const envMachineId = getEnvDirect("MACHINE_ID");
5527
5702
  if (envMachineId) return envMachineId;
5528
5703
  const deploymentMode = getEnvDirect("FRONTMCP_DEPLOYMENT_MODE");
@@ -5542,10 +5717,15 @@ var machineId = (() => {
5542
5717
  const newId = randomUUID();
5543
5718
  saveMachineIdAsync(newId);
5544
5719
  return newId;
5545
- })();
5720
+ }
5721
+ var cachedMachineId;
5546
5722
  var machineIdOverride;
5547
5723
  function getMachineId() {
5548
- return machineIdOverride ?? machineId;
5724
+ if (machineIdOverride !== void 0) return machineIdOverride;
5725
+ if (cachedMachineId === void 0) {
5726
+ cachedMachineId = computeMachineId();
5727
+ }
5728
+ return cachedMachineId;
5549
5729
  }
5550
5730
  function setMachineIdOverride(id) {
5551
5731
  machineIdOverride = id;
package/index.js CHANGED
@@ -5,8 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ var __esm = (fn, res, err) => function __init() {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
10
15
  };
11
16
  var __export = (target, all) => {
12
17
  for (var name in all)
@@ -78,8 +83,8 @@ async function readFile(p, encoding = "utf8") {
78
83
  return fsp.readFile(p, encoding);
79
84
  }
80
85
  function readFileSync(p, encoding = "utf8") {
81
- const fs2 = getFs();
82
- return fs2.readFileSync(p, encoding);
86
+ const fs = getFs();
87
+ return fs.readFileSync(p, encoding);
83
88
  }
84
89
  async function readFileBuffer(p) {
85
90
  const fsp = getFsp();
@@ -94,8 +99,8 @@ async function mkdir(p, options) {
94
99
  await fsp.mkdir(p, options);
95
100
  }
96
101
  function ensureDirSync(p) {
97
- const fs2 = getFs();
98
- fs2.mkdirSync(p, { recursive: true });
102
+ const fs = getFs();
103
+ fs.mkdirSync(p, { recursive: true });
99
104
  }
100
105
  async function rename(oldPath, newPath) {
101
106
  const fsp = getFsp();
@@ -156,8 +161,8 @@ async function readdir(p) {
156
161
  return fsp.readdir(p);
157
162
  }
158
163
  function readdirSync(p) {
159
- const fs2 = getFs();
160
- return fs2.readdirSync(p);
164
+ const fs = getFs();
165
+ return fs.readdirSync(p);
161
166
  }
162
167
  async function rm(p, options) {
163
168
  const fsp = getFsp();
@@ -190,8 +195,8 @@ function runCmd(cmd, args, opts = {}) {
190
195
  });
191
196
  }
192
197
  function watchFile(p, onChange, options) {
193
- const fs2 = getFs();
194
- const watcher = fs2.watch(p, { persistent: false, recursive: options?.recursive === true }, (eventType, filename) => {
198
+ const fs = getFs();
199
+ const watcher = fs.watch(p, { persistent: false, recursive: options?.recursive === true }, (eventType, filename) => {
195
200
  onChange(eventType, filename);
196
201
  });
197
202
  return {
@@ -1019,6 +1024,7 @@ var init_errors = __esm({
1019
1024
  this.backend = backend;
1020
1025
  this.name = "StorageConnectionError";
1021
1026
  }
1027
+ backend;
1022
1028
  };
1023
1029
  StorageOperationError = class extends StorageError {
1024
1030
  constructor(operation, key, message, cause) {
@@ -1027,6 +1033,8 @@ var init_errors = __esm({
1027
1033
  this.key = key;
1028
1034
  this.name = "StorageOperationError";
1029
1035
  }
1036
+ operation;
1037
+ key;
1030
1038
  };
1031
1039
  StorageNotSupportedError = class extends StorageError {
1032
1040
  constructor(operation, backend, suggestion) {
@@ -1036,6 +1044,8 @@ var init_errors = __esm({
1036
1044
  this.backend = backend;
1037
1045
  this.name = "StorageNotSupportedError";
1038
1046
  }
1047
+ operation;
1048
+ backend;
1039
1049
  };
1040
1050
  StorageConfigError = class extends StorageError {
1041
1051
  constructor(backend, message) {
@@ -1043,6 +1053,7 @@ var init_errors = __esm({
1043
1053
  this.backend = backend;
1044
1054
  this.name = "StorageConfigError";
1045
1055
  }
1056
+ backend;
1046
1057
  };
1047
1058
  StorageTTLError = class extends StorageError {
1048
1059
  constructor(ttl, message) {
@@ -1050,6 +1061,7 @@ var init_errors = __esm({
1050
1061
  this.ttl = ttl;
1051
1062
  this.name = "StorageTTLError";
1052
1063
  }
1064
+ ttl;
1053
1065
  };
1054
1066
  StoragePatternError = class extends StorageError {
1055
1067
  constructor(pattern, message) {
@@ -1057,6 +1069,7 @@ var init_errors = __esm({
1057
1069
  this.pattern = pattern;
1058
1070
  this.name = "StoragePatternError";
1059
1071
  }
1072
+ pattern;
1060
1073
  };
1061
1074
  StorageNotConnectedError = class extends StorageError {
1062
1075
  constructor(backend) {
@@ -1064,6 +1077,7 @@ var init_errors = __esm({
1064
1077
  this.backend = backend;
1065
1078
  this.name = "StorageNotConnectedError";
1066
1079
  }
1080
+ backend;
1067
1081
  };
1068
1082
  EncryptedStorageError = class extends StorageError {
1069
1083
  constructor(message) {
@@ -3812,6 +3826,154 @@ var init_upstash = __esm({
3812
3826
  }
3813
3827
  });
3814
3828
 
3829
+ // libs/utils/src/storage/adapters/cloudflare-kv.ts
3830
+ var cloudflare_kv_exports = {};
3831
+ __export(cloudflare_kv_exports, {
3832
+ CloudflareKvStorageAdapter: () => CloudflareKvStorageAdapter
3833
+ });
3834
+ function literalPrefix(pattern) {
3835
+ const star = pattern.indexOf("*");
3836
+ const q = pattern.indexOf("?");
3837
+ const idx = star === -1 ? q : q === -1 ? star : Math.min(star, q);
3838
+ return idx === -1 ? pattern : pattern.slice(0, idx);
3839
+ }
3840
+ var CF_KV_MIN_TTL_SECONDS, CloudflareKvStorageAdapter;
3841
+ var init_cloudflare_kv = __esm({
3842
+ "libs/utils/src/storage/adapters/cloudflare-kv.ts"() {
3843
+ "use strict";
3844
+ init_errors();
3845
+ init_pattern();
3846
+ init_ttl();
3847
+ init_base();
3848
+ CF_KV_MIN_TTL_SECONDS = 60;
3849
+ CloudflareKvStorageAdapter = class extends BaseStorageAdapter {
3850
+ backendName = "cloudflare-kv";
3851
+ kv;
3852
+ keyPrefix;
3853
+ constructor(options) {
3854
+ super();
3855
+ if (!options?.namespace) {
3856
+ throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).");
3857
+ }
3858
+ this.kv = options.namespace;
3859
+ this.keyPrefix = options.keyPrefix ?? "";
3860
+ }
3861
+ // ── Lifecycle ──────────────────────────────────────────────────────────
3862
+ // A KV binding is always available; "connection" is just a readiness flag.
3863
+ async connect() {
3864
+ this.connected = true;
3865
+ }
3866
+ async disconnect() {
3867
+ this.connected = false;
3868
+ }
3869
+ async ping() {
3870
+ if (!this.connected) return false;
3871
+ try {
3872
+ await this.kv.list({ limit: 1 });
3873
+ return true;
3874
+ } catch {
3875
+ return false;
3876
+ }
3877
+ }
3878
+ // ── Core ───────────────────────────────────────────────────────────────
3879
+ async get(key) {
3880
+ this.ensureConnected();
3881
+ return this.kv.get(this.prefixKey(key), "text");
3882
+ }
3883
+ async doSet(key, value, options) {
3884
+ if (options?.ifNotExists || options?.ifExists) {
3885
+ throw new StorageNotSupportedError(
3886
+ "conditional set (ifNotExists/ifExists)",
3887
+ this.backendName,
3888
+ "Cloudflare KV has no compare-and-set; use a Durable Object for atomic conditional writes."
3889
+ );
3890
+ }
3891
+ const putOptions = options?.ttlSeconds !== void 0 ? { expirationTtl: this.toExpirationTtl(options.ttlSeconds, key) } : void 0;
3892
+ await this.kv.put(this.prefixKey(key), value, putOptions);
3893
+ }
3894
+ async delete(key) {
3895
+ this.ensureConnected();
3896
+ const existed = await this.exists(key);
3897
+ await this.kv.delete(this.prefixKey(key));
3898
+ return existed;
3899
+ }
3900
+ async exists(key) {
3901
+ this.ensureConnected();
3902
+ return await this.kv.get(this.prefixKey(key), "text") !== null;
3903
+ }
3904
+ // ── TTL ────────────────────────────────────────────────────────────────
3905
+ async expire(key, ttlSeconds) {
3906
+ this.ensureConnected();
3907
+ const ttl = this.toExpirationTtl(ttlSeconds, key);
3908
+ const value = await this.kv.get(this.prefixKey(key), "text");
3909
+ if (value === null) return false;
3910
+ await this.kv.put(this.prefixKey(key), value, { expirationTtl: ttl });
3911
+ return true;
3912
+ }
3913
+ async ttl(_key) {
3914
+ throw new StorageNotSupportedError(
3915
+ "ttl",
3916
+ this.backendName,
3917
+ "Cloudflare KV does not expose a key's remaining TTL. Track expiry alongside the value if you need it."
3918
+ );
3919
+ }
3920
+ // ── Enumeration ──────────────────────────────────────────────────────────
3921
+ async keys(pattern = "*") {
3922
+ this.ensureConnected();
3923
+ const prefixedPattern = this.prefixKey(pattern);
3924
+ const listPrefix = literalPrefix(prefixedPattern);
3925
+ const out = [];
3926
+ let cursor;
3927
+ do {
3928
+ const page = await this.kv.list({ prefix: listPrefix || void 0, cursor });
3929
+ for (const { name } of page.keys) {
3930
+ if (matchesPattern(name, prefixedPattern)) out.push(this.unprefixKey(name));
3931
+ }
3932
+ cursor = page.list_complete ? void 0 : page.cursor;
3933
+ } while (cursor);
3934
+ return out;
3935
+ }
3936
+ // ── Atomic (unsupported on KV) ───────────────────────────────────────────
3937
+ async incr(key) {
3938
+ return this.incrBy(key, 1);
3939
+ }
3940
+ async decr(key) {
3941
+ return this.incrBy(key, -1);
3942
+ }
3943
+ async incrBy(_key, _amount) {
3944
+ throw new StorageNotSupportedError(
3945
+ "incr/decr/incrBy",
3946
+ this.backendName,
3947
+ "Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters."
3948
+ );
3949
+ }
3950
+ // ── Pub/Sub (unsupported) ────────────────────────────────────────────────
3951
+ getPubSubSuggestion() {
3952
+ return "Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out.";
3953
+ }
3954
+ // ── Helpers ──────────────────────────────────────────────────────────────
3955
+ /** Validate + enforce KV's 60s minimum `expirationTtl`. */
3956
+ toExpirationTtl(ttlSeconds, key) {
3957
+ validateTTL(ttlSeconds);
3958
+ if (ttlSeconds < CF_KV_MIN_TTL_SECONDS) {
3959
+ throw new StorageOperationError(
3960
+ "set",
3961
+ key,
3962
+ `cloudflare-kv: expirationTtl must be >= ${CF_KV_MIN_TTL_SECONDS}s (Cloudflare KV minimum); got ${ttlSeconds}s.`
3963
+ );
3964
+ }
3965
+ return ttlSeconds;
3966
+ }
3967
+ prefixKey(key) {
3968
+ return this.keyPrefix + key;
3969
+ }
3970
+ unprefixKey(key) {
3971
+ return this.keyPrefix && key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key;
3972
+ }
3973
+ };
3974
+ }
3975
+ });
3976
+
3815
3977
  // libs/utils/src/index.ts
3816
3978
  var index_exports = {};
3817
3979
  __export(index_exports, {
@@ -4746,7 +4908,6 @@ function isKnownTarget(value) {
4746
4908
  }
4747
4909
 
4748
4910
  // libs/utils/src/env/provider.ts
4749
- var fs = __toESM(require("node:fs"));
4750
4911
  var cached2;
4751
4912
  function detectProvider() {
4752
4913
  if (cached2) return cached2;
@@ -4766,7 +4927,8 @@ function detectProviderUncached() {
4766
4927
  if (process.env["RENDER"]) return "render";
4767
4928
  if (process.env["RAILWAY_ENVIRONMENT"]) return "railway";
4768
4929
  try {
4769
- if (fs.existsSync("/.dockerenv")) return "docker";
4930
+ const nodeFs = require("node:fs");
4931
+ if (nodeFs.existsSync("/.dockerenv")) return "docker";
4770
4932
  } catch {
4771
4933
  }
4772
4934
  return "bare";
@@ -4888,6 +5050,9 @@ var NamespacedStorageImpl = class _NamespacedStorageImpl {
4888
5050
  this.prefix = prefix;
4889
5051
  this.root = root;
4890
5052
  }
5053
+ adapter;
5054
+ prefix;
5055
+ root;
4891
5056
  // ============================================
4892
5057
  // Key Prefixing Helpers
4893
5058
  // ============================================
@@ -5061,6 +5226,16 @@ async function createAdapter(type, config) {
5061
5226
  const { UpstashStorageAdapter: UpstashStorageAdapter2 } = await Promise.resolve().then(() => (init_upstash(), upstash_exports));
5062
5227
  return new UpstashStorageAdapter2(config.upstash);
5063
5228
  }
5229
+ case "cloudflare-kv": {
5230
+ if (!config.cloudflareKv) {
5231
+ throw new StorageConfigError(
5232
+ "cloudflare-kv",
5233
+ "cloudflare-kv requires `cloudflareKv: { namespace }` (the KV binding can't be auto-detected)"
5234
+ );
5235
+ }
5236
+ const { CloudflareKvStorageAdapter: CloudflareKvStorageAdapter2 } = await Promise.resolve().then(() => (init_cloudflare_kv(), cloudflare_kv_exports));
5237
+ return new CloudflareKvStorageAdapter2(config.cloudflareKv);
5238
+ }
5064
5239
  case "auto":
5065
5240
  throw new StorageConfigError("auto", "Auto type should be resolved before calling createAdapter");
5066
5241
  default:
@@ -5709,8 +5884,8 @@ function loadMachineIdSync() {
5709
5884
  if (!isDevPersistenceEnabled()) return null;
5710
5885
  const machineIdPath = resolveMachineIdPath();
5711
5886
  try {
5712
- const fs2 = require("fs");
5713
- const content = fs2.readFileSync(machineIdPath, "utf8").trim();
5887
+ const fs = require("fs");
5888
+ const content = fs.readFileSync(machineIdPath, "utf8").trim();
5714
5889
  if (/^[0-9a-f-]{32,36}$/i.test(content)) return content;
5715
5890
  console.warn(`[MachineId] Invalid format at ${machineIdPath}, will regenerate`);
5716
5891
  return null;
@@ -5721,7 +5896,7 @@ function loadMachineIdSync() {
5721
5896
  return null;
5722
5897
  }
5723
5898
  }
5724
- function saveMachineIdAsync(machineId2) {
5899
+ function saveMachineIdAsync(machineId) {
5725
5900
  if (!isDevPersistenceEnabled()) return;
5726
5901
  const machineIdPath = resolveMachineIdPath();
5727
5902
  const path = require("path");
@@ -5730,13 +5905,13 @@ function saveMachineIdAsync(machineId2) {
5730
5905
  try {
5731
5906
  const fsp = require("fs").promises;
5732
5907
  await fsp.mkdir(dir, { recursive: true, mode: 448 });
5733
- await fsp.writeFile(machineIdPath, machineId2, { mode: 384 });
5908
+ await fsp.writeFile(machineIdPath, machineId, { mode: 384 });
5734
5909
  } catch (error) {
5735
5910
  console.warn(`[MachineId] Failed to save to ${machineIdPath}: ${error.message}`);
5736
5911
  }
5737
5912
  })();
5738
5913
  }
5739
- var machineId = (() => {
5914
+ function computeMachineId() {
5740
5915
  const envMachineId = getEnvDirect("MACHINE_ID");
5741
5916
  if (envMachineId) return envMachineId;
5742
5917
  const deploymentMode = getEnvDirect("FRONTMCP_DEPLOYMENT_MODE");
@@ -5756,10 +5931,15 @@ var machineId = (() => {
5756
5931
  const newId = randomUUID();
5757
5932
  saveMachineIdAsync(newId);
5758
5933
  return newId;
5759
- })();
5934
+ }
5935
+ var cachedMachineId;
5760
5936
  var machineIdOverride;
5761
5937
  function getMachineId() {
5762
- return machineIdOverride ?? machineId;
5938
+ if (machineIdOverride !== void 0) return machineIdOverride;
5939
+ if (cachedMachineId === void 0) {
5940
+ cachedMachineId = computeMachineId();
5941
+ }
5942
+ return cachedMachineId;
5763
5943
  }
5764
5944
  function setMachineIdOverride(id) {
5765
5945
  machineIdOverride = id;
@@ -22,7 +22,8 @@
22
22
  */
23
23
  /**
24
24
  * Get the current machine ID.
25
- * Returns the override (if set via `setMachineIdOverride`) or the computed value.
25
+ * Returns the override (if set via `setMachineIdOverride`) or the lazily
26
+ * computed-and-memoized value.
26
27
  */
27
28
  export declare function getMachineId(): string;
28
29
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
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",
@@ -27,12 +27,12 @@
27
27
  "node": ">=24.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@enclave-vm/ast": "^2.13.0",
30
+ "@enclave-vm/ast": "^2.14.1",
31
31
  "@noble/ciphers": "^2.1.1",
32
32
  "@noble/hashes": "^2.0.1"
33
33
  },
34
34
  "peerDependencies": {
35
- "@frontmcp/lazy-zod": "1.4.1",
35
+ "@frontmcp/lazy-zod": "1.5.0",
36
36
  "@vercel/kv": "^2.0.0 || ^3.0.0",
37
37
  "ioredis": "^5.0.0"
38
38
  },
@@ -46,26 +46,32 @@
46
46
  },
47
47
  "imports": {
48
48
  "#crypto-provider": {
49
+ "worker": "./crypto/node.js",
49
50
  "browser": "./crypto/browser.js",
50
51
  "default": "./crypto/node.js"
51
52
  },
52
53
  "#async-context": {
54
+ "worker": "./async-context/node-async-context.js",
53
55
  "browser": "./async-context/browser-async-context.js",
54
56
  "default": "./async-context/node-async-context.js"
55
57
  },
56
58
  "#event-emitter": {
59
+ "worker": "./event-emitter/node-event-emitter.js",
57
60
  "browser": "./event-emitter/browser-event-emitter.js",
58
61
  "default": "./event-emitter/node-event-emitter.js"
59
62
  },
60
63
  "#env": {
64
+ "worker": "./env/node-env.js",
61
65
  "browser": "./env/browser-env.js",
62
66
  "default": "./env/node-env.js"
63
67
  },
64
68
  "#runtime-context": {
69
+ "worker": "./env/runtime-context.js",
65
70
  "browser": "./env/browser-runtime-context.js",
66
71
  "default": "./env/runtime-context.js"
67
72
  },
68
73
  "#path": {
74
+ "worker": "./path/node-path.js",
69
75
  "browser": "./path/browser-path.js",
70
76
  "default": "./path/node-path.js"
71
77
  }
@@ -0,0 +1,26 @@
1
+ import type { CloudflareKvAdapterOptions, SetOptions } from '../types';
2
+ import { BaseStorageAdapter } from './base';
3
+ export declare class CloudflareKvStorageAdapter extends BaseStorageAdapter {
4
+ protected readonly backendName = "cloudflare-kv";
5
+ private readonly kv;
6
+ private readonly keyPrefix;
7
+ constructor(options: CloudflareKvAdapterOptions);
8
+ connect(): Promise<void>;
9
+ disconnect(): Promise<void>;
10
+ ping(): Promise<boolean>;
11
+ get(key: string): Promise<string | null>;
12
+ protected doSet(key: string, value: string, options?: SetOptions): Promise<void>;
13
+ delete(key: string): Promise<boolean>;
14
+ exists(key: string): Promise<boolean>;
15
+ expire(key: string, ttlSeconds: number): Promise<boolean>;
16
+ ttl(_key: string): Promise<number | null>;
17
+ keys(pattern?: string): Promise<string[]>;
18
+ incr(key: string): Promise<number>;
19
+ decr(key: string): Promise<number>;
20
+ incrBy(_key: string, _amount: number): Promise<number>;
21
+ protected getPubSubSuggestion(): string;
22
+ /** Validate + enforce KV's 60s minimum `expirationTtl`. */
23
+ private toExpirationTtl;
24
+ private prefixKey;
25
+ private unprefixKey;
26
+ }
@@ -8,6 +8,7 @@ export { MemoryStorageAdapter } from './memory';
8
8
  export { RedisStorageAdapter } from './redis';
9
9
  export { VercelKvStorageAdapter } from './vercel-kv';
10
10
  export { UpstashStorageAdapter } from './upstash';
11
+ export { CloudflareKvStorageAdapter } from './cloudflare-kv';
11
12
  export { FileSystemStorageAdapter } from './filesystem';
12
13
  export type { FileSystemAdapterOptions } from './filesystem';
13
14
  export { LocalStorageAdapter } from './localstorage';
@@ -378,10 +378,44 @@ export interface UpstashAdapterOptions {
378
378
  */
379
379
  enablePubSub?: boolean;
380
380
  }
381
+ /**
382
+ * Minimal structural subset of the Cloudflare `KVNamespace` binding the
383
+ * `cloudflare-kv` adapter uses. Declared here rather than importing
384
+ * `@cloudflare/workers-types` so any runtime exposing this shape works.
385
+ */
386
+ export interface CloudflareKvNamespace {
387
+ get(key: string, type?: 'text'): Promise<string | null>;
388
+ put(key: string, value: string, options?: {
389
+ expirationTtl?: number;
390
+ }): Promise<void>;
391
+ delete(key: string): Promise<void>;
392
+ list(options?: {
393
+ prefix?: string;
394
+ limit?: number;
395
+ cursor?: string;
396
+ }): Promise<{
397
+ keys: {
398
+ name: string;
399
+ }[];
400
+ list_complete: boolean;
401
+ cursor?: string;
402
+ }>;
403
+ }
404
+ /**
405
+ * Cloudflare Workers KV adapter options (when type='cloudflare-kv'). The bound
406
+ * KV namespace is supplied explicitly (resolved from the Worker `env`), since
407
+ * bindings don't exist as env vars and can't be auto-detected.
408
+ */
409
+ export interface CloudflareKvAdapterOptions {
410
+ /** The bound KV namespace. */
411
+ namespace: CloudflareKvNamespace;
412
+ /** Key prefix applied to all keys. @default '' */
413
+ keyPrefix?: string;
414
+ }
381
415
  /**
382
416
  * Storage backend type.
383
417
  */
384
- export type StorageType = 'memory' | 'redis' | 'vercel-kv' | 'upstash' | 'auto';
418
+ export type StorageType = 'memory' | 'redis' | 'vercel-kv' | 'upstash' | 'cloudflare-kv' | 'auto';
385
419
  /**
386
420
  * Configuration for createStorage factory.
387
421
  */
@@ -408,6 +442,10 @@ export interface StorageConfig {
408
442
  * Vercel KV adapter options (when type='vercel-kv').
409
443
  */
410
444
  vercelKv?: VercelKvAdapterOptions;
445
+ /**
446
+ * Cloudflare KV adapter options (when type='cloudflare-kv').
447
+ */
448
+ cloudflareKv?: CloudflareKvAdapterOptions;
411
449
  /**
412
450
  * Upstash adapter options (when type='upstash').
413
451
  */