@frontmcp/utils 1.8.2 → 1.8.4

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,12 +1,30 @@
1
+ export { AsyncContextOverlapError } from './overlap-error';
2
+ export type AsyncContextMode = 'native' | 'serialized';
3
+ export declare function getAsyncContextMode(): AsyncContextMode;
1
4
  /**
2
- * Browser polyfill for AsyncLocalStorage.
3
- *
4
- * Uses a simple stack-based approach, which is safe in single-threaded
5
- * browser environments. Does not support `enterWith()` or `disable()`
6
- * (neither is used in the FrontMCP codebase).
5
+ * Run one top-level request. With native `AsyncContext` it only calls `fn`. Without it, the request
6
+ * waits for its turn, runs with its own stacks, and keeps the turn until `fn` settles and every
7
+ * store it opened has unwound, so work it left running cannot read the next request's context.
7
8
  */
9
+ export declare function runRequestExclusive<R>(fn: () => R | Promise<R>): Promise<R>;
10
+ /**
11
+ * Await something the running request waits on from outside, such as its client's answer to an
12
+ * elicitation. Without native `AsyncContext` the request steps aside while it waits, so the client
13
+ * can call the server before answering, and takes its turn back afterwards. Outside a request, or
14
+ * with native `AsyncContext`, it only awaits.
15
+ */
16
+ export declare function awaitOutsideRequest<T>(promise: PromiseLike<T>): Promise<T>;
8
17
  export declare class AsyncLocalStorage<T> {
9
- private stack;
18
+ private readonly native?;
19
+ private readonly stacks;
20
+ constructor();
10
21
  run<R>(store: T, callback: (...args: unknown[]) => R, ...args: unknown[]): R;
11
22
  getStore(): T | undefined;
23
+ /**
24
+ * Remove `frame` from its request's stack. Returns false when it was not the innermost frame: a
25
+ * later run of this storage in the same request was still open, so the two overlapped and reads
26
+ * in between may have seen the other run's store.
27
+ */
28
+ private leave;
29
+ private overlapError;
12
30
  }
@@ -20,30 +20,195 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // libs/utils/src/async-context/browser-async-context.ts
21
21
  var browser_async_context_exports = {};
22
22
  __export(browser_async_context_exports, {
23
- AsyncLocalStorage: () => AsyncLocalStorage
23
+ AsyncContextOverlapError: () => AsyncContextOverlapError,
24
+ AsyncLocalStorage: () => AsyncLocalStorage,
25
+ awaitOutsideRequest: () => awaitOutsideRequest,
26
+ getAsyncContextMode: () => getAsyncContextMode,
27
+ runRequestExclusive: () => runRequestExclusive
24
28
  });
25
29
  module.exports = __toCommonJS(browser_async_context_exports);
30
+
31
+ // libs/utils/src/async-context/overlap-error.ts
32
+ var AsyncContextOverlapError = class extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "AsyncContextOverlapError";
36
+ }
37
+ };
38
+
39
+ // libs/utils/src/async-context/browser-async-context.ts
40
+ var nativeAsyncContext = (() => {
41
+ const candidate = globalThis.AsyncContext;
42
+ return typeof candidate?.Variable === "function" ? candidate : void 0;
43
+ })();
44
+ function getAsyncContextMode() {
45
+ return nativeAsyncContext ? "native" : "serialized";
46
+ }
47
+ var SLOW_TURN_WARNING_MS = 1e4;
48
+ var RequestSlot = class {
49
+ /** Frames of every storage that are still running in this request. */
50
+ live = 0;
51
+ /** Storages whose runs overlapped in this request; they refuse to answer until they unwind. */
52
+ tainted = /* @__PURE__ */ new Set();
53
+ idleWaiters = [];
54
+ enter() {
55
+ this.live++;
56
+ }
57
+ leave() {
58
+ this.live--;
59
+ if (this.live === 0) {
60
+ for (const resolve of this.idleWaiters.splice(0)) resolve();
61
+ }
62
+ }
63
+ /** Resolves once no frame of this request is running any more. */
64
+ idle() {
65
+ return this.live === 0 ? Promise.resolve() : new Promise((resolve) => this.idleWaiters.push(resolve));
66
+ }
67
+ };
68
+ var ROOT = new RequestSlot();
69
+ var active = ROOT;
70
+ var owner;
71
+ var turnQueue = [];
72
+ var slowTurnWarned = false;
73
+ var overlapReported = false;
74
+ function takeTurn(slot) {
75
+ if (!owner) {
76
+ owner = slot;
77
+ active = slot;
78
+ return Promise.resolve();
79
+ }
80
+ return new Promise((resolve) => {
81
+ const timer = setTimeout(() => {
82
+ if (slowTurnWarned) return;
83
+ slowTurnWarned = true;
84
+ console.warn(
85
+ "[frontmcp] A request has waited more than 10s for another one to finish. This runtime has no AsyncContext, so FrontMCP runs requests one at a time to keep their contexts apart. A tool that calls its own server through a client (instead of this.scope flows) waits for itself."
86
+ );
87
+ }, SLOW_TURN_WARNING_MS);
88
+ timer.unref?.();
89
+ turnQueue.push({
90
+ slot,
91
+ start: () => {
92
+ clearTimeout(timer);
93
+ resolve();
94
+ }
95
+ });
96
+ });
97
+ }
98
+ function passTurn(slot) {
99
+ if (owner !== slot) return;
100
+ const next = turnQueue.shift();
101
+ if (next) {
102
+ owner = next.slot;
103
+ active = next.slot;
104
+ next.start();
105
+ } else {
106
+ owner = void 0;
107
+ active = ROOT;
108
+ }
109
+ }
110
+ async function runRequestExclusive(fn) {
111
+ if (nativeAsyncContext) return fn();
112
+ const slot = new RequestSlot();
113
+ await takeTurn(slot);
114
+ try {
115
+ return await fn();
116
+ } finally {
117
+ await slot.idle();
118
+ passTurn(slot);
119
+ }
120
+ }
121
+ async function awaitOutsideRequest(promise) {
122
+ if (nativeAsyncContext) return promise;
123
+ const slot = owner;
124
+ if (!slot || active !== slot) return promise;
125
+ passTurn(slot);
126
+ try {
127
+ return await promise;
128
+ } finally {
129
+ await takeTurn(slot);
130
+ }
131
+ }
26
132
  var AsyncLocalStorage = class {
27
- stack = [];
133
+ native;
134
+ stacks = /* @__PURE__ */ new WeakMap();
135
+ constructor() {
136
+ if (nativeAsyncContext) this.native = new nativeAsyncContext.Variable();
137
+ }
28
138
  run(store, callback, ...args) {
29
- this.stack.push(store);
139
+ if (this.native) return this.native.run(store, callback, ...args);
140
+ const slot = active;
141
+ let stack = this.stacks.get(slot);
142
+ if (!stack) {
143
+ stack = [];
144
+ this.stacks.set(slot, stack);
145
+ }
146
+ const frame = { store };
147
+ stack.push(frame);
148
+ slot.enter();
149
+ let result;
30
150
  try {
31
- const result = callback(...args);
32
- if (result instanceof Promise) {
33
- return result.finally(() => this.stack.pop());
34
- }
35
- this.stack.pop();
36
- return result;
37
- } catch (err) {
38
- this.stack.pop();
39
- throw err;
151
+ result = callback(...args);
152
+ } catch (error) {
153
+ this.leave(slot, frame);
154
+ throw error;
40
155
  }
156
+ if (result instanceof Promise) {
157
+ return result.then(
158
+ (value) => {
159
+ if (!this.leave(slot, frame)) throw this.overlapError();
160
+ return value;
161
+ },
162
+ (error) => {
163
+ this.leave(slot, frame);
164
+ throw error;
165
+ }
166
+ );
167
+ }
168
+ this.leave(slot, frame);
169
+ return result;
41
170
  }
42
171
  getStore() {
43
- return this.stack.length > 0 ? this.stack[this.stack.length - 1] : void 0;
172
+ if (this.native) return this.native.get();
173
+ const slot = active;
174
+ if (slot.tainted.has(this)) throw this.overlapError();
175
+ const stack = this.stacks.get(slot);
176
+ return stack && stack.length > 0 ? stack[stack.length - 1].store : void 0;
177
+ }
178
+ /**
179
+ * Remove `frame` from its request's stack. Returns false when it was not the innermost frame: a
180
+ * later run of this storage in the same request was still open, so the two overlapped and reads
181
+ * in between may have seen the other run's store.
182
+ */
183
+ leave(slot, frame) {
184
+ const stack = this.stacks.get(slot) ?? [];
185
+ const index = stack.lastIndexOf(frame);
186
+ const innermost = index === stack.length - 1;
187
+ if (index !== -1) stack.splice(index, 1);
188
+ if (!innermost) {
189
+ slot.tainted.add(this);
190
+ if (!overlapReported) {
191
+ overlapReported = true;
192
+ console.error(
193
+ "[frontmcp] Two concurrent runs of one async context overlapped inside a request. This runtime has no AsyncContext, so the context of each cannot be told apart; reads fail for the rest of that request instead of returning the wrong value."
194
+ );
195
+ }
196
+ }
197
+ if (stack.length === 0) slot.tainted.delete(this);
198
+ slot.leave();
199
+ return innermost;
200
+ }
201
+ overlapError() {
202
+ return new AsyncContextOverlapError(
203
+ "Concurrent runs of an async context overlapped in one request, and this runtime has no AsyncContext to tell them apart. Run the calls one after another, or use a runtime with AsyncContext."
204
+ );
44
205
  }
45
206
  };
46
207
  // Annotate the CommonJS export names for ESM import in node:
47
208
  0 && (module.exports = {
48
- AsyncLocalStorage
209
+ AsyncContextOverlapError,
210
+ AsyncLocalStorage,
211
+ awaitOutsideRequest,
212
+ getAsyncContextMode,
213
+ runRequestExclusive
49
214
  });
@@ -1 +1 @@
1
- export { AsyncLocalStorage } from '#async-context';
1
+ export { AsyncContextOverlapError, AsyncLocalStorage, awaitOutsideRequest, getAsyncContextMode, runRequestExclusive, type AsyncContextMode, } from '#async-context';
@@ -1 +1,12 @@
1
1
  export { AsyncLocalStorage } from 'node:async_hooks';
2
+ export { AsyncContextOverlapError } from './overlap-error';
3
+ /** How the runtime keeps async context apart: Node and Workers propagate it natively. */
4
+ export type AsyncContextMode = 'native' | 'serialized';
5
+ export declare function getAsyncContextMode(): AsyncContextMode;
6
+ /**
7
+ * Run one top-level request. Node propagates `AsyncLocalStorage` across awaits, so requests may
8
+ * overlap freely and this only calls `fn`.
9
+ */
10
+ export declare function runRequestExclusive<R>(fn: () => R | Promise<R>): Promise<R>;
11
+ /** Await something a request waits on from outside (a client's answer). A pass-through on Node. */
12
+ export declare function awaitOutsideRequest<T>(promise: PromiseLike<T>): Promise<T>;
@@ -20,11 +20,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // libs/utils/src/async-context/node-async-context.ts
21
21
  var node_async_context_exports = {};
22
22
  __export(node_async_context_exports, {
23
- AsyncLocalStorage: () => import_node_async_hooks.AsyncLocalStorage
23
+ AsyncContextOverlapError: () => AsyncContextOverlapError,
24
+ AsyncLocalStorage: () => import_node_async_hooks.AsyncLocalStorage,
25
+ awaitOutsideRequest: () => awaitOutsideRequest,
26
+ getAsyncContextMode: () => getAsyncContextMode,
27
+ runRequestExclusive: () => runRequestExclusive
24
28
  });
25
29
  module.exports = __toCommonJS(node_async_context_exports);
26
30
  var import_node_async_hooks = require("node:async_hooks");
31
+
32
+ // libs/utils/src/async-context/overlap-error.ts
33
+ var AsyncContextOverlapError = class extends Error {
34
+ constructor(message) {
35
+ super(message);
36
+ this.name = "AsyncContextOverlapError";
37
+ }
38
+ };
39
+
40
+ // libs/utils/src/async-context/node-async-context.ts
41
+ function getAsyncContextMode() {
42
+ return "native";
43
+ }
44
+ async function runRequestExclusive(fn) {
45
+ return fn();
46
+ }
47
+ async function awaitOutsideRequest(promise) {
48
+ return promise;
49
+ }
27
50
  // Annotate the CommonJS export names for ESM import in node:
28
51
  0 && (module.exports = {
29
- AsyncLocalStorage
52
+ AsyncContextOverlapError,
53
+ AsyncLocalStorage,
54
+ awaitOutsideRequest,
55
+ getAsyncContextMode,
56
+ runRequestExclusive
30
57
  });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Thrown by the browser `AsyncLocalStorage` when two concurrent runs of one storage in the same
3
+ * request overlapped. Without a runtime-provided async context the storage cannot tell which of them
4
+ * a later read belongs to, so it refuses to answer instead of returning the other run's value.
5
+ */
6
+ export declare class AsyncContextOverlapError extends Error {
7
+ constructor(message: string);
8
+ }
@@ -1,26 +1,185 @@
1
1
  import { createRequire as __frontmcpCreateRequire } from 'module';
2
2
  const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
3
+ // libs/utils/src/async-context/overlap-error.ts
4
+ var AsyncContextOverlapError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "AsyncContextOverlapError";
8
+ }
9
+ };
10
+
3
11
  // libs/utils/src/async-context/browser-async-context.ts
12
+ var nativeAsyncContext = (() => {
13
+ const candidate = globalThis.AsyncContext;
14
+ return typeof candidate?.Variable === "function" ? candidate : void 0;
15
+ })();
16
+ function getAsyncContextMode() {
17
+ return nativeAsyncContext ? "native" : "serialized";
18
+ }
19
+ var SLOW_TURN_WARNING_MS = 1e4;
20
+ var RequestSlot = class {
21
+ /** Frames of every storage that are still running in this request. */
22
+ live = 0;
23
+ /** Storages whose runs overlapped in this request; they refuse to answer until they unwind. */
24
+ tainted = /* @__PURE__ */ new Set();
25
+ idleWaiters = [];
26
+ enter() {
27
+ this.live++;
28
+ }
29
+ leave() {
30
+ this.live--;
31
+ if (this.live === 0) {
32
+ for (const resolve of this.idleWaiters.splice(0)) resolve();
33
+ }
34
+ }
35
+ /** Resolves once no frame of this request is running any more. */
36
+ idle() {
37
+ return this.live === 0 ? Promise.resolve() : new Promise((resolve) => this.idleWaiters.push(resolve));
38
+ }
39
+ };
40
+ var ROOT = new RequestSlot();
41
+ var active = ROOT;
42
+ var owner;
43
+ var turnQueue = [];
44
+ var slowTurnWarned = false;
45
+ var overlapReported = false;
46
+ function takeTurn(slot) {
47
+ if (!owner) {
48
+ owner = slot;
49
+ active = slot;
50
+ return Promise.resolve();
51
+ }
52
+ return new Promise((resolve) => {
53
+ const timer = setTimeout(() => {
54
+ if (slowTurnWarned) return;
55
+ slowTurnWarned = true;
56
+ console.warn(
57
+ "[frontmcp] A request has waited more than 10s for another one to finish. This runtime has no AsyncContext, so FrontMCP runs requests one at a time to keep their contexts apart. A tool that calls its own server through a client (instead of this.scope flows) waits for itself."
58
+ );
59
+ }, SLOW_TURN_WARNING_MS);
60
+ timer.unref?.();
61
+ turnQueue.push({
62
+ slot,
63
+ start: () => {
64
+ clearTimeout(timer);
65
+ resolve();
66
+ }
67
+ });
68
+ });
69
+ }
70
+ function passTurn(slot) {
71
+ if (owner !== slot) return;
72
+ const next = turnQueue.shift();
73
+ if (next) {
74
+ owner = next.slot;
75
+ active = next.slot;
76
+ next.start();
77
+ } else {
78
+ owner = void 0;
79
+ active = ROOT;
80
+ }
81
+ }
82
+ async function runRequestExclusive(fn) {
83
+ if (nativeAsyncContext) return fn();
84
+ const slot = new RequestSlot();
85
+ await takeTurn(slot);
86
+ try {
87
+ return await fn();
88
+ } finally {
89
+ await slot.idle();
90
+ passTurn(slot);
91
+ }
92
+ }
93
+ async function awaitOutsideRequest(promise) {
94
+ if (nativeAsyncContext) return promise;
95
+ const slot = owner;
96
+ if (!slot || active !== slot) return promise;
97
+ passTurn(slot);
98
+ try {
99
+ return await promise;
100
+ } finally {
101
+ await takeTurn(slot);
102
+ }
103
+ }
4
104
  var AsyncLocalStorage = class {
5
- stack = [];
105
+ native;
106
+ stacks = /* @__PURE__ */ new WeakMap();
107
+ constructor() {
108
+ if (nativeAsyncContext) this.native = new nativeAsyncContext.Variable();
109
+ }
6
110
  run(store, callback, ...args) {
7
- this.stack.push(store);
111
+ if (this.native) return this.native.run(store, callback, ...args);
112
+ const slot = active;
113
+ let stack = this.stacks.get(slot);
114
+ if (!stack) {
115
+ stack = [];
116
+ this.stacks.set(slot, stack);
117
+ }
118
+ const frame = { store };
119
+ stack.push(frame);
120
+ slot.enter();
121
+ let result;
8
122
  try {
9
- const result = callback(...args);
10
- if (result instanceof Promise) {
11
- return result.finally(() => this.stack.pop());
12
- }
13
- this.stack.pop();
14
- return result;
15
- } catch (err) {
16
- this.stack.pop();
17
- throw err;
123
+ result = callback(...args);
124
+ } catch (error) {
125
+ this.leave(slot, frame);
126
+ throw error;
18
127
  }
128
+ if (result instanceof Promise) {
129
+ return result.then(
130
+ (value) => {
131
+ if (!this.leave(slot, frame)) throw this.overlapError();
132
+ return value;
133
+ },
134
+ (error) => {
135
+ this.leave(slot, frame);
136
+ throw error;
137
+ }
138
+ );
139
+ }
140
+ this.leave(slot, frame);
141
+ return result;
19
142
  }
20
143
  getStore() {
21
- return this.stack.length > 0 ? this.stack[this.stack.length - 1] : void 0;
144
+ if (this.native) return this.native.get();
145
+ const slot = active;
146
+ if (slot.tainted.has(this)) throw this.overlapError();
147
+ const stack = this.stacks.get(slot);
148
+ return stack && stack.length > 0 ? stack[stack.length - 1].store : void 0;
149
+ }
150
+ /**
151
+ * Remove `frame` from its request's stack. Returns false when it was not the innermost frame: a
152
+ * later run of this storage in the same request was still open, so the two overlapped and reads
153
+ * in between may have seen the other run's store.
154
+ */
155
+ leave(slot, frame) {
156
+ const stack = this.stacks.get(slot) ?? [];
157
+ const index = stack.lastIndexOf(frame);
158
+ const innermost = index === stack.length - 1;
159
+ if (index !== -1) stack.splice(index, 1);
160
+ if (!innermost) {
161
+ slot.tainted.add(this);
162
+ if (!overlapReported) {
163
+ overlapReported = true;
164
+ console.error(
165
+ "[frontmcp] Two concurrent runs of one async context overlapped inside a request. This runtime has no AsyncContext, so the context of each cannot be told apart; reads fail for the rest of that request instead of returning the wrong value."
166
+ );
167
+ }
168
+ }
169
+ if (stack.length === 0) slot.tainted.delete(this);
170
+ slot.leave();
171
+ return innermost;
172
+ }
173
+ overlapError() {
174
+ return new AsyncContextOverlapError(
175
+ "Concurrent runs of an async context overlapped in one request, and this runtime has no AsyncContext to tell them apart. Run the calls one after another, or use a runtime with AsyncContext."
176
+ );
22
177
  }
23
178
  };
24
179
  export {
25
- AsyncLocalStorage
180
+ AsyncContextOverlapError,
181
+ AsyncLocalStorage,
182
+ awaitOutsideRequest,
183
+ getAsyncContextMode,
184
+ runRequestExclusive
26
185
  };
@@ -2,6 +2,29 @@ import { createRequire as __frontmcpCreateRequire } from 'module';
2
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
+
6
+ // libs/utils/src/async-context/overlap-error.ts
7
+ var AsyncContextOverlapError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "AsyncContextOverlapError";
11
+ }
12
+ };
13
+
14
+ // libs/utils/src/async-context/node-async-context.ts
15
+ function getAsyncContextMode() {
16
+ return "native";
17
+ }
18
+ async function runRequestExclusive(fn) {
19
+ return fn();
20
+ }
21
+ async function awaitOutsideRequest(promise) {
22
+ return promise;
23
+ }
5
24
  export {
6
- AsyncLocalStorage
25
+ AsyncContextOverlapError,
26
+ AsyncLocalStorage,
27
+ awaitOutsideRequest,
28
+ getAsyncContextMode,
29
+ runRequestExclusive
7
30
  };
package/esm/index.mjs CHANGED
@@ -4786,7 +4786,13 @@ function safeStringify(value, space) {
4786
4786
  init_crypto();
4787
4787
 
4788
4788
  // libs/utils/src/async-context/index.ts
4789
- import { AsyncLocalStorage } from "#async-context";
4789
+ import {
4790
+ AsyncContextOverlapError,
4791
+ AsyncLocalStorage,
4792
+ awaitOutsideRequest,
4793
+ getAsyncContextMode,
4794
+ runRequestExclusive
4795
+ } from "#async-context";
4790
4796
 
4791
4797
  // libs/utils/src/event-emitter/index.ts
4792
4798
  import { EventEmitter as EventEmitter2 } from "#event-emitter";
@@ -5866,6 +5872,7 @@ function setMachineIdOverride(id) {
5866
5872
  machineIdOverride = id;
5867
5873
  }
5868
5874
  export {
5875
+ AsyncContextOverlapError,
5869
5876
  AsyncLocalStorage,
5870
5877
  BaseStorageAdapter,
5871
5878
  DEFAULT_CODE_VERIFIER_LENGTH,
@@ -5902,6 +5909,7 @@ export {
5902
5909
  assertNode,
5903
5910
  asymmetricAlgSchema,
5904
5911
  asymmetricKeyDataSchema,
5912
+ awaitOutsideRequest,
5905
5913
  base64Decode,
5906
5914
  base64Encode,
5907
5915
  base64urlDecode,
@@ -5960,6 +5968,7 @@ export {
5960
5968
  generatePkcePair,
5961
5969
  generateRsaKeyPair2 as generateRsaKeyPair,
5962
5970
  generateSecret,
5971
+ getAsyncContextMode,
5963
5972
  getCookie,
5964
5973
  getCrypto,
5965
5974
  getCwd2 as getCwd,
@@ -6039,6 +6048,7 @@ export {
6039
6048
  rsaVerify2 as rsaVerify,
6040
6049
  rsaVerifySync2 as rsaVerifySync,
6041
6050
  runCmd,
6051
+ runRequestExclusive,
6042
6052
  safeExec,
6043
6053
  safeJsonForScript,
6044
6054
  safeMatch,
package/index.d.ts CHANGED
@@ -21,7 +21,7 @@ export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJson
21
21
  export { safeStringify } from './serialization';
22
22
  export { getCrypto, rsaVerify, rsaVerifySync, pemToPublicJwk, generateRsaKeyPair, rsaSignBase64Url, type RsaJwk, type RsaKeyPair, jwtAlgToNodeAlg, isRsaPssAlg, randomUUID, randomBytes, sha256, sha256Hex, sha256Base64url, hmacSha256, hkdfSha256, encryptAesGcm, decryptAesGcm, timingSafeEqual, bytesToHex, base64urlEncode, base64urlDecode, base64Encode, base64Decode, isNode, isBrowser, assertNode, EncryptedBlobError, encryptValue, decryptValue, tryDecryptValue, serializeBlob, deserializeBlob, tryDeserializeBlob, isValidEncryptedBlob, encryptAndSerialize, deserializeAndDecrypt, tryDeserializeAndDecrypt, generateCodeVerifier, generateCodeChallenge, verifyCodeChallenge, generatePkcePair, isValidCodeVerifier, isValidCodeChallenge, MIN_CODE_VERIFIER_LENGTH, MAX_CODE_VERIFIER_LENGTH, DEFAULT_CODE_VERIFIER_LENGTH, PkceError, type PkcePair, type SecretData, type SecretPersistenceOptions, type SecretValidationResult, secretDataSchema, validateSecretData, parseSecretData, isSecretPersistenceEnabled, resolveSecretPath, loadSecret, saveSecret, deleteSecret, generateSecret, createSecretData, getOrCreateSecret, clearCachedSecret, isSecretCached, type SignedData, type HmacSigningConfig, signData, verifyData, isSignedData, verifyOrParseData, type BaseKeyData, type SecretKeyData, type AsymmetricKeyData, type AnyKeyData, type KeyPersistenceOptions, type CreateKeyPersistenceOptions, type CreateSecretOptions, type CreateAsymmetricOptions, type KeyValidationResult, asymmetricAlgSchema, secretKeyDataSchema, asymmetricKeyDataSchema, anyKeyDataSchema, validateKeyData, parseKeyData, isSecretKeyData, isAsymmetricKeyData, KeyPersistence, createKeyPersistence, createKeyPersistenceWithStorage, } from './crypto';
23
23
  export type { CryptoProvider, EncBlob, EncryptedBlob } from './crypto';
24
- export { AsyncLocalStorage } from './async-context';
24
+ export { AsyncContextOverlapError, AsyncLocalStorage, awaitOutsideRequest, getAsyncContextMode, runRequestExclusive, type AsyncContextMode, } from './async-context';
25
25
  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';
package/index.js CHANGED
@@ -3977,6 +3977,7 @@ var init_cloudflare_kv = __esm({
3977
3977
  // libs/utils/src/index.ts
3978
3978
  var index_exports = {};
3979
3979
  __export(index_exports, {
3980
+ AsyncContextOverlapError: () => import_async_context.AsyncContextOverlapError,
3980
3981
  AsyncLocalStorage: () => import_async_context.AsyncLocalStorage,
3981
3982
  BaseStorageAdapter: () => BaseStorageAdapter,
3982
3983
  DEFAULT_CODE_VERIFIER_LENGTH: () => DEFAULT_CODE_VERIFIER_LENGTH,
@@ -4013,6 +4014,7 @@ __export(index_exports, {
4013
4014
  assertNode: () => assertNode,
4014
4015
  asymmetricAlgSchema: () => asymmetricAlgSchema,
4015
4016
  asymmetricKeyDataSchema: () => asymmetricKeyDataSchema,
4017
+ awaitOutsideRequest: () => import_async_context.awaitOutsideRequest,
4016
4018
  base64Decode: () => base64Decode,
4017
4019
  base64Encode: () => base64Encode,
4018
4020
  base64urlDecode: () => base64urlDecode,
@@ -4071,6 +4073,7 @@ __export(index_exports, {
4071
4073
  generatePkcePair: () => generatePkcePair,
4072
4074
  generateRsaKeyPair: () => generateRsaKeyPair2,
4073
4075
  generateSecret: () => generateSecret,
4076
+ getAsyncContextMode: () => import_async_context.getAsyncContextMode,
4074
4077
  getCookie: () => getCookie,
4075
4078
  getCrypto: () => getCrypto,
4076
4079
  getCwd: () => import_env3.getCwd,
@@ -4150,6 +4153,7 @@ __export(index_exports, {
4150
4153
  rsaVerify: () => rsaVerify2,
4151
4154
  rsaVerifySync: () => rsaVerifySync2,
4152
4155
  runCmd: () => runCmd,
4156
+ runRequestExclusive: () => import_async_context.runRequestExclusive,
4153
4157
  safeExec: () => safeExec,
4154
4158
  safeJsonForScript: () => safeJsonForScript,
4155
4159
  safeMatch: () => safeMatch,
@@ -6086,6 +6090,7 @@ function setMachineIdOverride(id) {
6086
6090
  }
6087
6091
  // Annotate the CommonJS export names for ESM import in node:
6088
6092
  0 && (module.exports = {
6093
+ AsyncContextOverlapError,
6089
6094
  AsyncLocalStorage,
6090
6095
  BaseStorageAdapter,
6091
6096
  DEFAULT_CODE_VERIFIER_LENGTH,
@@ -6122,6 +6127,7 @@ function setMachineIdOverride(id) {
6122
6127
  assertNode,
6123
6128
  asymmetricAlgSchema,
6124
6129
  asymmetricKeyDataSchema,
6130
+ awaitOutsideRequest,
6125
6131
  base64Decode,
6126
6132
  base64Encode,
6127
6133
  base64urlDecode,
@@ -6180,6 +6186,7 @@ function setMachineIdOverride(id) {
6180
6186
  generatePkcePair,
6181
6187
  generateRsaKeyPair,
6182
6188
  generateSecret,
6189
+ getAsyncContextMode,
6183
6190
  getCookie,
6184
6191
  getCrypto,
6185
6192
  getCwd,
@@ -6259,6 +6266,7 @@ function setMachineIdOverride(id) {
6259
6266
  rsaVerify,
6260
6267
  rsaVerifySync,
6261
6268
  runCmd,
6269
+ runRequestExclusive,
6262
6270
  safeExec,
6263
6271
  safeJsonForScript,
6264
6272
  safeMatch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
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.15.2",
30
+ "@enclave-vm/ast": "^2.15.3",
31
31
  "@noble/ciphers": "^2.1.1",
32
32
  "@noble/hashes": "^2.0.1"
33
33
  },
34
34
  "peerDependencies": {
35
- "@frontmcp/lazy-zod": "1.8.2",
35
+ "@frontmcp/lazy-zod": "1.8.4",
36
36
  "@vercel/kv": "^2.0.0 || ^3.0.0",
37
37
  "ioredis": "^5.0.0"
38
38
  },