@frontmcp/utils 1.12.0-rc.1 → 1.12.1

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 @@
1
+ export { registerOptionalModule, requireOptionalModule } from './optional-module';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Hand FrontMCP an optional package (`@frontmcp/observability`, `@frontmcp/storage-sqlite`) that the
3
+ * bundle already contains. On Cloudflare Workers `require()` cannot reach bundled
4
+ * modules, so the worker entry registers them here (#768). Kept on
5
+ * `globalThis` so a second copy of a FrontMCP package in the same bundle sees it too.
6
+ */
7
+ export declare function registerOptionalModule(moduleName: string, moduleExports: unknown): void;
8
+ /**
9
+ * Return a module registered with {@link registerOptionalModule}, or run
10
+ * `load` — pass a literal `() => require('pkg')` so bundlers still see it.
11
+ */
12
+ export declare function requireOptionalModule<T>(moduleName: string, load: () => T): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/utils",
3
- "version": "1.12.0-rc.1",
3
+ "version": "1.12.1",
4
4
  "description": "Shared helpers for FrontMCP: URI templates, naming, cross-platform crypto and PKCE, key-value storage, file system and environment detection",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -28,12 +28,12 @@
28
28
  "node": ">=24.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@enclave-vm/ast": "^2.18.0",
31
+ "@enclave-vm/ast": "^2.19.2",
32
32
  "@noble/ciphers": "^2.1.1",
33
33
  "@noble/hashes": "^2.0.1"
34
34
  },
35
35
  "peerDependencies": {
36
- "@frontmcp/lazy-zod": "1.12.0-rc.1",
36
+ "@frontmcp/lazy-zod": "1.12.1",
37
37
  "@upstash/redis": "^1.34.0",
38
38
  "@vercel/kv": "^2.0.0 || ^3.0.0",
39
39
  "ioredis": "^5.0.0"
@@ -12,7 +12,7 @@ export type RedactedFields = readonly string[] | ((key: string) => boolean);
12
12
  *
13
13
  * @param obj - Object to redact fields from
14
14
  * @param fields - Field names to redact (case-insensitive), or a test on each key
15
- * @param maxDepth - Maximum recursion depth (default: 5)
15
+ * @param maxDepth - Maximum recursion depth (default: 5); an object or list below it becomes '[TRUNCATED]'
16
16
  * @returns New object with sensitive fields replaced by '[REDACTED]'
17
17
  */
18
18
  export declare function redactFields(obj: Record<string, unknown>, fields: RedactedFields, maxDepth?: number): Record<string, unknown>;
@@ -88,6 +88,8 @@ export declare class FileSystemStorageAdapter extends BaseStorageAdapter {
88
88
  private readonly extension;
89
89
  private readonly dirMode;
90
90
  private readonly fileMode;
91
+ /** The last update queued for each key, so a counter's read and write never interleave with another update. */
92
+ private readonly keyUpdates;
91
93
  constructor(options: FileSystemAdapterOptions);
92
94
  connect(): Promise<void>;
93
95
  disconnect(): Promise<void>;
@@ -97,11 +99,20 @@ export declare class FileSystemStorageAdapter extends BaseStorageAdapter {
97
99
  delete(key: string): Promise<boolean>;
98
100
  exists(key: string): Promise<boolean>;
99
101
  expire(key: string, ttlSeconds: number): Promise<boolean>;
102
+ /** Set the expiry of `key` from its current entry; run only through {@link updateInTurn}. */
103
+ private expireNow;
100
104
  ttl(key: string): Promise<number | null>;
101
105
  keys(pattern?: string): Promise<string[]>;
102
106
  incr(key: string): Promise<number>;
103
107
  decr(key: string): Promise<number>;
104
108
  incrBy(key: string, amount: number): Promise<number>;
109
+ /** Add `amount` to the counter at `key`; run only through {@link updateInTurn}. */
110
+ private incrByNow;
111
+ /**
112
+ * Run `update` once the updates of `key` queued before it in this process have finished, so two updates of
113
+ * one key never read the same entry. Processes that share a directory are not coordinated.
114
+ */
115
+ private updateInTurn;
105
116
  /**
106
117
  * Convert a storage key to a file path.
107
118
  * Sanitizes the key to be filesystem-safe.
@@ -34,7 +34,7 @@ export declare class MemoryStorageAdapter extends BaseStorageAdapter {
34
34
  private readonly emitter;
35
35
  private sweepInterval?;
36
36
  private readonly options;
37
- private accessOrder;
37
+ private readonly accessOrder;
38
38
  constructor(options?: MemoryAdapterOptions);
39
39
  connect(): Promise<void>;
40
40
  disconnect(): Promise<void>;
@@ -76,7 +76,11 @@ export declare class MemoryStorageAdapter extends BaseStorageAdapter {
76
76
  */
77
77
  private removeLRU;
78
78
  /**
79
- * Evict oldest entry (LRU).
79
+ * Evict least recently used entries until a new key fits within maxEntries.
80
+ */
81
+ private makeRoomFor;
82
+ /**
83
+ * Evict the least recently used entry. Returns false when no entry is left to evict.
80
84
  */
81
85
  private evictOldest;
82
86
  /**
@@ -20,5 +20,5 @@ export { EncryptedTypedStorage } from './encrypted-typed-storage';
20
20
  export type { EncryptedTypedStorageOptions, EncryptedSetOptions, EncryptedSetEntry, EncryptionKey, StoredEncryptedBlob, ClientKeyBinding, } from './encrypted-typed-storage.types';
21
21
  export { BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, createRedisClient, VercelKvStorageAdapter, createVercelKvClient, UpstashStorageAdapter, FileSystemStorageAdapter, LocalStorageAdapter, IndexedDBStorageAdapter, } from './adapters';
22
22
  export type { CreateRedisClientOptions, VercelKvConnection, FileSystemAdapterOptions, LocalStorageAdapterOptions, IndexedDBAdapterOptions, } from './adapters';
23
- export { globToRegex, matchesPattern, validatePattern, escapeGlob } from './utils/pattern';
23
+ export { createGlobMatcher, globToRegex, matchesPattern, validatePattern, escapeGlob } from './utils/pattern';
24
24
  export { MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './utils/ttl';
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Storage Utilities
3
3
  */
4
- export { globToRegex, matchesPattern, validatePattern, escapeGlob } from './pattern';
4
+ export { createGlobMatcher, globToRegex, matchesPattern, validatePattern, escapeGlob } from './pattern';
5
5
  export { MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './ttl';
@@ -1,15 +1,41 @@
1
1
  /**
2
2
  * Pattern Matching Utilities
3
3
  *
4
- * Convert glob patterns to regex for key matching.
5
- * Includes ReDoS protection to prevent denial-of-service attacks.
4
+ * Glob patterns for key matching: `*` matches any sequence, `?` matches one character, and a backslash makes the
5
+ * next character literal, as in Redis `SCAN MATCH`, so `escapeGlob()` output means the same thing on every backend.
6
+ * Matching runs without regular expressions, in time bounded by the key length times the pattern length.
6
7
  */
7
8
  /**
8
- * Convert a glob pattern to a RegExp.
9
+ * Compile a glob pattern into a function that tests whole keys.
9
10
  *
10
11
  * Supports:
11
12
  * - `*` matches any sequence of characters (including empty)
12
13
  * - `?` matches exactly one character
14
+ * - a backslash makes the next character literal, so `escapeGlob(value)` matches exactly `value`
15
+ *
16
+ * Unlike {@link globToRegex}, the matcher never backtracks: a pattern with many wildcards cannot stall the process.
17
+ *
18
+ * @param pattern - Glob pattern (e.g., "user:*:profile")
19
+ * @returns A function that reports whether a key matches the whole pattern
20
+ * @throws StoragePatternError if the pattern is too long or has too many wildcards
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const isProfile = createGlobMatcher('user:*:profile');
25
+ * isProfile('user:123:profile'); // true
26
+ * isProfile('user:profile'); // false
27
+ *
28
+ * const exact = createGlobMatcher(`id:${escapeGlob('a*b')}`);
29
+ * exact('id:a*b'); // true
30
+ * exact('id:axxb'); // false
31
+ * ```
32
+ */
33
+ export declare function createGlobMatcher(pattern: string): (key: string) => boolean;
34
+ /**
35
+ * Convert a glob pattern to a RegExp.
36
+ *
37
+ * Supports `*`, `?` and backslash escapes, as {@link createGlobMatcher} does. The result is a backtracking regular
38
+ * expression; to test keys, use {@link createGlobMatcher} or {@link matchesPattern}, which do not backtrack.
13
39
  *
14
40
  * @param pattern - Glob pattern (e.g., "user:*:profile")
15
41
  * @returns RegExp for matching keys
@@ -56,7 +82,8 @@ export declare function validatePattern(pattern: string): {
56
82
  };
57
83
  /**
58
84
  * Escape a string for use as a literal in a glob pattern.
59
- * Escapes * and ? characters.
85
+ * Escapes `*`, `?`, `[`, `]` and backslash (Redis `SCAN MATCH` reads `[` as the start of a character class). Every
86
+ * matcher in this module, and Redis `SCAN MATCH`, reads the result as the literal string.
60
87
  *
61
88
  * @param literal - String to escape
62
89
  * @returns Escaped string safe for use in patterns