@frontmcp/utils 1.9.3 → 1.9.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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Class identity shared by every copy of a package loaded in one process.
3
+ *
4
+ * A process can hold two copies of a FrontMCP package: in an ES-module project `@frontmcp/sdk`'s ESM
5
+ * build loads `@frontmcp/observability` with `require()`, which resolves observability's CommonJS
6
+ * build, which in turn loads the CommonJS builds of the SDK and of its dependencies. Each copy has its
7
+ * own classes, so a plain `instanceof` in one copy does not recognise an instance made by the other:
8
+ * an error the client was answered with as a `PublicMcpError` was recorded as an internal error with
9
+ * a new id (#802).
10
+ *
11
+ * A branded class answers `instanceof` for its own instances as usual, and also for an instance of
12
+ * the class with the same brand from another copy. The brand is an own property of the class, so a
13
+ * subclass is recognised only through a brand of its own: an unbranded subclass keeps the plain
14
+ * check, and never matches a different subclass that happens to share a branded parent.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * export class GuardError extends Error {
19
+ * static override [Symbol.hasInstance](value: unknown): boolean {
20
+ * return isBrandedInstance(this, value);
21
+ * }
22
+ * }
23
+ * brandClass(GuardError, '@frontmcp/guard:GuardError');
24
+ * ```
25
+ */
26
+ /**
27
+ * Give `klass` a process-wide brand. The same class in another copy of its package carries the same
28
+ * brand, so each recognises the other's instances through {@link isBrandedInstance}. Namespace the
29
+ * brand with the package name (`'@frontmcp/sdk:McpError'`).
30
+ */
31
+ export declare function brandClass(klass: object, brand: string): void;
32
+ /**
33
+ * `value instanceof klass`, also true when `value` is an instance of the class that carries `klass`'s
34
+ * own brand in another copy of the package. Meant as the `static [Symbol.hasInstance]` of a branded
35
+ * class (subclasses inherit it, and match through their own brand or the plain check).
36
+ */
37
+ export declare function isBrandedInstance(klass: object, value: unknown): boolean;
@@ -0,0 +1 @@
1
+ export { brandClass, isBrandedInstance } from './class-brand';
package/esm/index.mjs CHANGED
@@ -117,6 +117,9 @@ async function fileExists(p) {
117
117
  return false;
118
118
  }
119
119
  }
120
+ function fileExistsSync(p) {
121
+ return getFs().existsSync(p);
122
+ }
120
123
  async function readJSON(jsonPath) {
121
124
  try {
122
125
  const fsp = getFsp();
@@ -4883,6 +4886,30 @@ function safeJsonForScript(value) {
4883
4886
  }
4884
4887
  }
4885
4888
 
4889
+ // libs/utils/src/class-brand/class-brand.ts
4890
+ var CLASS_BRAND = /* @__PURE__ */ Symbol.for("@frontmcp/class-brand");
4891
+ var ordinaryHasInstance = Function.prototype[Symbol.hasInstance];
4892
+ function ownBrandOf(klass) {
4893
+ if (typeof klass !== "function" || !Object.prototype.hasOwnProperty.call(klass, CLASS_BRAND)) return void 0;
4894
+ return klass[CLASS_BRAND];
4895
+ }
4896
+ function classOfPrototype(proto) {
4897
+ return Object.getOwnPropertyDescriptor(proto, "constructor")?.value;
4898
+ }
4899
+ function brandClass(klass, brand) {
4900
+ Object.defineProperty(klass, CLASS_BRAND, { value: brand, enumerable: false, writable: false, configurable: false });
4901
+ }
4902
+ function isBrandedInstance(klass, value) {
4903
+ if (ordinaryHasInstance.call(klass, value)) return true;
4904
+ if (typeof value !== "object" || value === null) return false;
4905
+ const brand = ownBrandOf(klass);
4906
+ if (brand === void 0) return false;
4907
+ for (let proto = Object.getPrototypeOf(value); proto !== null; proto = Object.getPrototypeOf(proto)) {
4908
+ if (ownBrandOf(classOfPrototype(proto)) === brand) return true;
4909
+ }
4910
+ return false;
4911
+ }
4912
+
4886
4913
  // libs/utils/src/serialization/serialization.ts
4887
4914
  function safeStringify(value, space) {
4888
4915
  const seen = /* @__PURE__ */ new WeakSet();
@@ -5907,6 +5934,7 @@ export {
5907
5934
  base64urlDecode,
5908
5935
  base64urlEncode,
5909
5936
  basename,
5937
+ brandClass,
5910
5938
  buildPrefix,
5911
5939
  buildSetCookie,
5912
5940
  bytesToHex,
@@ -5957,6 +5985,7 @@ export {
5957
5985
  extractTemplateParams,
5958
5986
  extractUriScheme,
5959
5987
  fileExists,
5988
+ fileExistsSync,
5960
5989
  findNonFiniteNumber,
5961
5990
  generateCodeChallenge,
5962
5991
  generateCodeVerifier,
@@ -5983,6 +6012,7 @@ export {
5983
6012
  inferMimeType,
5984
6013
  isAbsolute,
5985
6014
  isAsymmetricKeyData,
6015
+ isBrandedInstance,
5986
6016
  isBrowser,
5987
6017
  isDebug,
5988
6018
  isDevelopment,
package/fs/fs.d.ts CHANGED
@@ -137,6 +137,21 @@ export declare function unlink(p: string): Promise<void>;
137
137
  * await fileExists('/path/to/file.txt') // true or false
138
138
  */
139
139
  export declare function fileExists(p: string): Promise<boolean>;
140
+ /**
141
+ * Check whether a file or directory exists, synchronously.
142
+ *
143
+ * **Node.js only** - throws an error if called in browser.
144
+ *
145
+ * Use this only where async is not possible (e.g. resolving a command line
146
+ * before a synchronous `spawn`). Prefer {@link fileExists} otherwise.
147
+ *
148
+ * @param p - Path to check
149
+ * @returns true if the path exists, false otherwise
150
+ *
151
+ * @example
152
+ * fileExistsSync('/path/to/file.txt') // true or false
153
+ */
154
+ export declare function fileExistsSync(p: string): boolean;
140
155
  /**
141
156
  * Read and parse a JSON file.
142
157
  *
@@ -343,6 +358,11 @@ export declare function access(p: string, mode?: number): Promise<void>;
343
358
  export declare function runCmd(cmd: string, args: string[], opts?: {
344
359
  cwd?: string;
345
360
  env?: NodeJS.ProcessEnv;
361
+ /**
362
+ * Pass `args` to the child unquoted (Windows only). Set it when the arguments
363
+ * are already quoted for `cmd.exe /d /s /c`; ignored on other platforms.
364
+ */
365
+ windowsVerbatimArguments?: boolean;
346
366
  }): Promise<void>;
347
367
  /**
348
368
  * Handle returned by {@link watchFile}. `close()` stops the watch and is safe
package/fs/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
1
+ export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, fileExistsSync, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
2
2
  export type { FileWatcherHandle, SymlinkType } from './fs';
package/index.d.ts CHANGED
@@ -15,9 +15,10 @@ export { validateBaseUrl, buildSetCookie, parseCookies, getCookie, isLocalhost,
15
15
  export type { CookieOptions, SecureDetectionRequest } from './http';
16
16
  export { classifyIpAddress, expandIpv6, extractEmbeddedIpv4, parseIpv4, isRedirectResponse } from './net';
17
17
  export type { IpAddressClassification, IpAddressRange, SpecialPurposeIpRange, RedirectCandidate } from './net';
18
- export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
18
+ export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, fileExistsSync, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
19
19
  export type { FileWatcherHandle } from './fs';
20
20
  export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJsonForScript } from './escape';
21
+ export { brandClass, isBrandedInstance } from './class-brand';
21
22
  export { safeStringify } from './serialization';
22
23
  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
24
  export type { CryptoProvider, EncBlob, EncryptedBlob } from './crypto';
package/index.js CHANGED
@@ -119,6 +119,9 @@ async function fileExists(p) {
119
119
  return false;
120
120
  }
121
121
  }
122
+ function fileExistsSync(p) {
123
+ return getFs().existsSync(p);
124
+ }
122
125
  async function readJSON(jsonPath) {
123
126
  try {
124
127
  const fsp = getFsp();
@@ -4134,6 +4137,7 @@ __export(index_exports, {
4134
4137
  base64urlDecode: () => base64urlDecode,
4135
4138
  base64urlEncode: () => base64urlEncode,
4136
4139
  basename: () => import_path2.basename,
4140
+ brandClass: () => brandClass,
4137
4141
  buildPrefix: () => buildPrefix,
4138
4142
  buildSetCookie: () => buildSetCookie,
4139
4143
  bytesToHex: () => bytesToHex,
@@ -4184,6 +4188,7 @@ __export(index_exports, {
4184
4188
  extractTemplateParams: () => extractTemplateParams,
4185
4189
  extractUriScheme: () => extractUriScheme,
4186
4190
  fileExists: () => fileExists,
4191
+ fileExistsSync: () => fileExistsSync,
4187
4192
  findNonFiniteNumber: () => findNonFiniteNumber,
4188
4193
  generateCodeChallenge: () => generateCodeChallenge,
4189
4194
  generateCodeVerifier: () => generateCodeVerifier,
@@ -4210,6 +4215,7 @@ __export(index_exports, {
4210
4215
  inferMimeType: () => inferMimeType,
4211
4216
  isAbsolute: () => import_path2.isAbsolute,
4212
4217
  isAsymmetricKeyData: () => isAsymmetricKeyData,
4218
+ isBrandedInstance: () => isBrandedInstance,
4213
4219
  isBrowser: () => isBrowser,
4214
4220
  isDebug: () => import_env2.isDebug,
4215
4221
  isDevelopment: () => import_env2.isDevelopment,
@@ -5114,6 +5120,30 @@ function safeJsonForScript(value) {
5114
5120
  }
5115
5121
  }
5116
5122
 
5123
+ // libs/utils/src/class-brand/class-brand.ts
5124
+ var CLASS_BRAND = /* @__PURE__ */ Symbol.for("@frontmcp/class-brand");
5125
+ var ordinaryHasInstance = Function.prototype[Symbol.hasInstance];
5126
+ function ownBrandOf(klass) {
5127
+ if (typeof klass !== "function" || !Object.prototype.hasOwnProperty.call(klass, CLASS_BRAND)) return void 0;
5128
+ return klass[CLASS_BRAND];
5129
+ }
5130
+ function classOfPrototype(proto) {
5131
+ return Object.getOwnPropertyDescriptor(proto, "constructor")?.value;
5132
+ }
5133
+ function brandClass(klass, brand) {
5134
+ Object.defineProperty(klass, CLASS_BRAND, { value: brand, enumerable: false, writable: false, configurable: false });
5135
+ }
5136
+ function isBrandedInstance(klass, value) {
5137
+ if (ordinaryHasInstance.call(klass, value)) return true;
5138
+ if (typeof value !== "object" || value === null) return false;
5139
+ const brand = ownBrandOf(klass);
5140
+ if (brand === void 0) return false;
5141
+ for (let proto = Object.getPrototypeOf(value); proto !== null; proto = Object.getPrototypeOf(proto)) {
5142
+ if (ownBrandOf(classOfPrototype(proto)) === brand) return true;
5143
+ }
5144
+ return false;
5145
+ }
5146
+
5117
5147
  // libs/utils/src/serialization/serialization.ts
5118
5148
  function safeStringify(value, space) {
5119
5149
  const seen = /* @__PURE__ */ new WeakSet();
@@ -6114,6 +6144,7 @@ function setMachineIdOverride(id) {
6114
6144
  base64urlDecode,
6115
6145
  base64urlEncode,
6116
6146
  basename,
6147
+ brandClass,
6117
6148
  buildPrefix,
6118
6149
  buildSetCookie,
6119
6150
  bytesToHex,
@@ -6164,6 +6195,7 @@ function setMachineIdOverride(id) {
6164
6195
  extractTemplateParams,
6165
6196
  extractUriScheme,
6166
6197
  fileExists,
6198
+ fileExistsSync,
6167
6199
  findNonFiniteNumber,
6168
6200
  generateCodeChallenge,
6169
6201
  generateCodeVerifier,
@@ -6190,6 +6222,7 @@ function setMachineIdOverride(id) {
6190
6222
  inferMimeType,
6191
6223
  isAbsolute,
6192
6224
  isAsymmetricKeyData,
6225
+ isBrandedInstance,
6193
6226
  isBrowser,
6194
6227
  isDebug,
6195
6228
  isDevelopment,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.9.3",
3
+ "version": "1.9.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",
@@ -32,7 +32,7 @@
32
32
  "@noble/hashes": "^2.0.1"
33
33
  },
34
34
  "peerDependencies": {
35
- "@frontmcp/lazy-zod": "1.9.3",
35
+ "@frontmcp/lazy-zod": "1.9.4",
36
36
  "@upstash/redis": "^1.34.0",
37
37
  "@vercel/kv": "^2.0.0 || ^3.0.0",
38
38
  "ioredis": "^5.0.0"