@frontmcp/utils 1.9.5 → 1.9.7
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/README.md +50 -29
- package/esm/index.mjs +51 -1
- package/index.js +51 -1
- package/package.json +12 -11
- package/storage/adapters/base.d.ts +2 -0
- package/storage/adapters/memory.d.ts +1 -0
- package/storage/adapters/redis.d.ts +1 -0
- package/storage/adapters/upstash.d.ts +1 -0
- package/storage/adapters/vercel-kv.d.ts +1 -0
- package/storage/namespace.d.ts +1 -0
- package/storage/types.d.ts +12 -0
- package/storage/utils/compare-and-delete.d.ts +2 -0
package/README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# @frontmcp/utils
|
|
2
2
|
|
|
3
|
-
Shared
|
|
3
|
+
Shared helpers for the FrontMCP packages: names and URI templates, cross-platform crypto, key-value storage, file system access, environment detection and more. This is an internal package: `@frontmcp/sdk` and the other `@frontmcp/*` packages depend on it and install it for you. Import it directly only when your own code, like a plugin, needs one of these helpers.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@frontmcp/utils) [](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
|
|
8
6
|
|
|
9
7
|
## Install
|
|
10
8
|
|
|
@@ -12,40 +10,63 @@ Shared utility functions for the FrontMCP ecosystem.
|
|
|
12
10
|
npm install @frontmcp/utils
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**Naming** — `splitWords`, `toCase`, `shortHash`, `ensureMaxLen`, `idFromString` for string manipulation and case conversion
|
|
18
|
-
|
|
19
|
-
**URI** — `isValidMcpUri`, `extractUriScheme`, `parseUriTemplate`, `matchUriTemplate`, `expandUriTemplate` (RFC 3986 / RFC 6570)
|
|
20
|
-
|
|
21
|
-
**Path** — `trimSlashes`, `joinPath` for URL path operations
|
|
13
|
+
It needs Node.js 24 or later. The crypto, async context, event emitter, environment and path helpers also have browser builds, picked through the package's `imports` map. `@frontmcp/lazy-zod` is a required peer dependency (it needs `zod` 4). The others are optional, one per storage backend:
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
| Optional peer | Enables |
|
|
16
|
+
| ---------------- | ------------------------------------------------------------ |
|
|
17
|
+
| `ioredis` | `createStorage({ type: 'redis' })` and `RedisStorageAdapter` |
|
|
18
|
+
| `@vercel/kv` | `createStorage({ type: 'vercel-kv' })` |
|
|
19
|
+
| `@upstash/redis` | `createStorage({ type: 'upstash' })` |
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
**Crypto** — `sha256`, `sha256Hex`, `sha256Base64url`, `hkdfSha256`, `encryptAesGcm`, `decryptAesGcm`, `randomBytes`, `randomUUID`, `generateCodeVerifier`, `generateCodeChallenge`, `generatePkcePair`, `base64urlEncode`, `base64urlDecode` for cross-platform cryptography
|
|
28
|
-
|
|
29
|
-
**File system** — `readFile`, `writeFile`, `mkdir`, `stat`, `fileExists`, `readJSON`, `writeJSON`, `ensureDir`, `isDirEmpty`, `runCmd` and more — lazy-loaded for Node.js environments
|
|
30
|
-
|
|
31
|
-
## Quick Example
|
|
21
|
+
## Usage
|
|
32
22
|
|
|
33
23
|
```ts
|
|
34
|
-
import {
|
|
24
|
+
import { createStorage, expandUriTemplate, matchUriTemplate, sha256Hex, toCase } from '@frontmcp/utils';
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
// { id: '
|
|
26
|
+
matchUriTemplate('tickets://{id}/comments/{commentId}', 'tickets://T-1/comments/42');
|
|
27
|
+
// { id: 'T-1', commentId: '42' }
|
|
38
28
|
|
|
39
|
-
|
|
29
|
+
expandUriTemplate('tickets://{id}', { id: 'T-2' }); // 'tickets://T-2'
|
|
30
|
+
toCase(['create', 'ticket'], 'snake'); // 'create_ticket'
|
|
31
|
+
sha256Hex('hello world'); // 'b94d27b9...'
|
|
40
32
|
|
|
41
|
-
const
|
|
33
|
+
const storage = await createStorage({ type: 'memory' });
|
|
34
|
+
await storage.set('ticket:T-1', 'open', { ttlSeconds: 60 });
|
|
35
|
+
await storage.get('ticket:T-1'); // 'open'
|
|
42
36
|
```
|
|
43
37
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
## What's in it
|
|
39
|
+
|
|
40
|
+
| Area | Exports |
|
|
41
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| Naming | `splitWords`, `toCase`, `shortHash`, `ensureMaxLen`, `idFromString` |
|
|
43
|
+
| URIs | `isValidMcpUri`, `extractUriScheme`, `parseUriTemplate`, `matchUriTemplate`, `expandUriTemplate`, `extractTemplateParams` (RFC 3986 URIs, RFC 6570 templates) |
|
|
44
|
+
| Paths | `trimSlashes`, `joinPath`, and cross-platform `pathJoin`, `pathResolve`, `basename`, `dirname`, `extname` |
|
|
45
|
+
| Content | `sanitizeToJson`, `inferMimeType`, `safeStringify` |
|
|
46
|
+
| HTTP | `validateBaseUrl`, `buildSetCookie`, `parseCookies`, `getCookie`, `isLocalhost`, `isSecureRequest` |
|
|
47
|
+
| Network | `classifyIpAddress`, `parseIpv4`, `expandIpv6`, `isRedirectResponse`, for SSRF (server-side request forgery) checks |
|
|
48
|
+
| Escaping | `escapeHtml`, `escapeHtmlAttr`, `escapeJsString`, `safeJsonForScript` |
|
|
49
|
+
| Crypto | `sha256`, `sha256Hex`, `hmacSha256`, `hkdfSha256`, `encryptAesGcm`, `decryptAesGcm`, `randomBytes`, `randomUUID`, `timingSafeEqual`, `base64urlEncode`, `base64urlDecode` |
|
|
50
|
+
| PKCE | `generateCodeVerifier`, `generateCodeChallenge`, `verifyCodeChallenge`, `generatePkcePair` (RFC 7636) |
|
|
51
|
+
| Encryption, keys | `encryptValue`, `decryptValue`, `signData`, `verifyData`, `KeyPersistence`, `createKeyPersistence` |
|
|
52
|
+
| Storage | `createStorage`, `createMemoryStorage`, `TypedStorage`, `EncryptedTypedStorage`, and adapters for memory, Redis, Vercel KV, Upstash and the file system |
|
|
53
|
+
| File system | `readFile`, `writeFile`, `mkdir`, `stat`, `fileExists`, `readJSON`, `writeJSON`, `ensureDir`, `isDirEmpty`, `runCmd` and more. Node.js only; `fs` loads on first use. |
|
|
54
|
+
| Environment | `getEnv`, `isProduction`, `isDevelopment`, `isEdgeRuntime`, `isServerless`, `getRuntimeContext`, `isNode`, `isBrowser` |
|
|
55
|
+
| Context, events | `AsyncLocalStorage`, `EventEmitter`, both with browser builds |
|
|
56
|
+
| Safe regex | `isPatternSafe`, `createSafeRegExp`, `safeTest`, `safeMatch`, `safeReplace`, against ReDoS (regular expression denial of service) |
|
|
57
|
+
| Model tool calls | `processPlatformToolCalls`, which passes OpenAI, Claude or Vercel AI tool calls to your `callTool` function and returns the results in that platform's format |
|
|
58
|
+
| Machine ID | `getMachineId`, `setMachineIdOverride`, `getHostname` |
|
|
59
|
+
|
|
60
|
+
## Related packages
|
|
61
|
+
|
|
62
|
+
- [`@frontmcp/sdk`](https://www.npmjs.com/package/@frontmcp/sdk): the framework, built on these helpers.
|
|
63
|
+
- [`@frontmcp/auth`](https://www.npmjs.com/package/@frontmcp/auth): uses the crypto, PKCE and storage helpers.
|
|
64
|
+
- [`@frontmcp/storage-sqlite`](https://www.npmjs.com/package/@frontmcp/storage-sqlite): a SQLite storage adapter that implements this package's `StorageAdapter`.
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
Source: [`libs/utils`](https://github.com/agentfront/frontmcp/tree/main/libs/utils). Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/agentfront/frontmcp/blob/main/CONTRIBUTING.md).
|
|
48
69
|
|
|
49
70
|
## License
|
|
50
71
|
|
|
51
|
-
Apache-2.0
|
|
72
|
+
Apache-2.0. See [LICENSE](https://github.com/agentfront/frontmcp/blob/main/LICENSE).
|
package/esm/index.mjs
CHANGED
|
@@ -1283,6 +1283,14 @@ var init_base = __esm({
|
|
|
1283
1283
|
"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete."
|
|
1284
1284
|
);
|
|
1285
1285
|
}
|
|
1286
|
+
/** Default: not supported, for the same reason as {@link deleteIfEquals}. */
|
|
1287
|
+
async expireIfEquals(_key, _expectedValue, _ttlSeconds) {
|
|
1288
|
+
throw new StorageNotSupportedError(
|
|
1289
|
+
"expireIfEquals",
|
|
1290
|
+
this.backendName,
|
|
1291
|
+
"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-expire."
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1286
1294
|
// ============================================
|
|
1287
1295
|
// Pub/Sub (default: not supported)
|
|
1288
1296
|
// ============================================
|
|
@@ -1438,6 +1446,12 @@ var init_memory = __esm({
|
|
|
1438
1446
|
if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
|
|
1439
1447
|
return this.deleteEntry(key);
|
|
1440
1448
|
}
|
|
1449
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
1450
|
+
this.ensureConnected();
|
|
1451
|
+
const entry = this.store.get(key);
|
|
1452
|
+
if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
|
|
1453
|
+
return this.expire(key, ttlSeconds);
|
|
1454
|
+
}
|
|
1441
1455
|
async exists(key) {
|
|
1442
1456
|
this.ensureConnected();
|
|
1443
1457
|
const entry = this.store.get(key);
|
|
@@ -2951,11 +2965,12 @@ var init_utils = __esm({
|
|
|
2951
2965
|
});
|
|
2952
2966
|
|
|
2953
2967
|
// libs/utils/src/storage/utils/compare-and-delete.ts
|
|
2954
|
-
var COMPARE_AND_DELETE_SCRIPT;
|
|
2968
|
+
var COMPARE_AND_DELETE_SCRIPT, COMPARE_AND_EXPIRE_SCRIPT;
|
|
2955
2969
|
var init_compare_and_delete = __esm({
|
|
2956
2970
|
"libs/utils/src/storage/utils/compare-and-delete.ts"() {
|
|
2957
2971
|
"use strict";
|
|
2958
2972
|
COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
|
|
2973
|
+
COMPARE_AND_EXPIRE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0`;
|
|
2959
2974
|
}
|
|
2960
2975
|
});
|
|
2961
2976
|
|
|
@@ -3200,6 +3215,17 @@ var init_redis = __esm({
|
|
|
3200
3215
|
);
|
|
3201
3216
|
return deleted === 1;
|
|
3202
3217
|
}
|
|
3218
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
3219
|
+
validateTTL(ttlSeconds);
|
|
3220
|
+
const extended = await this.getConnectedClient().eval(
|
|
3221
|
+
COMPARE_AND_EXPIRE_SCRIPT,
|
|
3222
|
+
1,
|
|
3223
|
+
this.prefixKey(key),
|
|
3224
|
+
expectedValue,
|
|
3225
|
+
String(ttlSeconds)
|
|
3226
|
+
);
|
|
3227
|
+
return extended === 1;
|
|
3228
|
+
}
|
|
3203
3229
|
async exists(key) {
|
|
3204
3230
|
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3205
3231
|
return result > 0;
|
|
@@ -3533,6 +3559,15 @@ var init_vercel_kv = __esm({
|
|
|
3533
3559
|
);
|
|
3534
3560
|
return deleted === 1;
|
|
3535
3561
|
}
|
|
3562
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
3563
|
+
validateTTL(ttlSeconds);
|
|
3564
|
+
const extended = await this.getConnectedClient().eval(
|
|
3565
|
+
COMPARE_AND_EXPIRE_SCRIPT,
|
|
3566
|
+
[this.prefixKey(key)],
|
|
3567
|
+
[expectedValue, String(ttlSeconds)]
|
|
3568
|
+
);
|
|
3569
|
+
return extended === 1;
|
|
3570
|
+
}
|
|
3536
3571
|
async exists(key) {
|
|
3537
3572
|
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3538
3573
|
return result > 0;
|
|
@@ -3768,6 +3803,15 @@ var init_upstash = __esm({
|
|
|
3768
3803
|
);
|
|
3769
3804
|
return deleted === 1;
|
|
3770
3805
|
}
|
|
3806
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
3807
|
+
validateTTL(ttlSeconds);
|
|
3808
|
+
const extended = await this.getConnectedClient().eval(
|
|
3809
|
+
COMPARE_AND_EXPIRE_SCRIPT,
|
|
3810
|
+
[this.prefixKey(key)],
|
|
3811
|
+
[expectedValue, String(ttlSeconds)]
|
|
3812
|
+
);
|
|
3813
|
+
return extended === 1;
|
|
3814
|
+
}
|
|
3771
3815
|
async exists(key) {
|
|
3772
3816
|
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3773
3817
|
return result > 0;
|
|
@@ -5113,6 +5157,12 @@ var NamespacedStorageImpl = class _NamespacedStorageImpl {
|
|
|
5113
5157
|
}
|
|
5114
5158
|
return this.adapter.deleteIfEquals(this.prefixKey(key), expectedValue);
|
|
5115
5159
|
}
|
|
5160
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
5161
|
+
if (!this.adapter.expireIfEquals) {
|
|
5162
|
+
throw new StorageNotSupportedError("expireIfEquals", "this storage adapter");
|
|
5163
|
+
}
|
|
5164
|
+
return this.adapter.expireIfEquals(this.prefixKey(key), expectedValue, ttlSeconds);
|
|
5165
|
+
}
|
|
5116
5166
|
// ============================================
|
|
5117
5167
|
// Pub/Sub (with channel prefixing)
|
|
5118
5168
|
// ============================================
|
package/index.js
CHANGED
|
@@ -1285,6 +1285,14 @@ var init_base = __esm({
|
|
|
1285
1285
|
"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete."
|
|
1286
1286
|
);
|
|
1287
1287
|
}
|
|
1288
|
+
/** Default: not supported, for the same reason as {@link deleteIfEquals}. */
|
|
1289
|
+
async expireIfEquals(_key, _expectedValue, _ttlSeconds) {
|
|
1290
|
+
throw new StorageNotSupportedError(
|
|
1291
|
+
"expireIfEquals",
|
|
1292
|
+
this.backendName,
|
|
1293
|
+
"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-expire."
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1288
1296
|
// ============================================
|
|
1289
1297
|
// Pub/Sub (default: not supported)
|
|
1290
1298
|
// ============================================
|
|
@@ -1440,6 +1448,12 @@ var init_memory = __esm({
|
|
|
1440
1448
|
if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
|
|
1441
1449
|
return this.deleteEntry(key);
|
|
1442
1450
|
}
|
|
1451
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
1452
|
+
this.ensureConnected();
|
|
1453
|
+
const entry = this.store.get(key);
|
|
1454
|
+
if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
|
|
1455
|
+
return this.expire(key, ttlSeconds);
|
|
1456
|
+
}
|
|
1443
1457
|
async exists(key) {
|
|
1444
1458
|
this.ensureConnected();
|
|
1445
1459
|
const entry = this.store.get(key);
|
|
@@ -2953,11 +2967,12 @@ var init_utils = __esm({
|
|
|
2953
2967
|
});
|
|
2954
2968
|
|
|
2955
2969
|
// libs/utils/src/storage/utils/compare-and-delete.ts
|
|
2956
|
-
var COMPARE_AND_DELETE_SCRIPT;
|
|
2970
|
+
var COMPARE_AND_DELETE_SCRIPT, COMPARE_AND_EXPIRE_SCRIPT;
|
|
2957
2971
|
var init_compare_and_delete = __esm({
|
|
2958
2972
|
"libs/utils/src/storage/utils/compare-and-delete.ts"() {
|
|
2959
2973
|
"use strict";
|
|
2960
2974
|
COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
|
|
2975
|
+
COMPARE_AND_EXPIRE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0`;
|
|
2961
2976
|
}
|
|
2962
2977
|
});
|
|
2963
2978
|
|
|
@@ -3202,6 +3217,17 @@ var init_redis = __esm({
|
|
|
3202
3217
|
);
|
|
3203
3218
|
return deleted === 1;
|
|
3204
3219
|
}
|
|
3220
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
3221
|
+
validateTTL(ttlSeconds);
|
|
3222
|
+
const extended = await this.getConnectedClient().eval(
|
|
3223
|
+
COMPARE_AND_EXPIRE_SCRIPT,
|
|
3224
|
+
1,
|
|
3225
|
+
this.prefixKey(key),
|
|
3226
|
+
expectedValue,
|
|
3227
|
+
String(ttlSeconds)
|
|
3228
|
+
);
|
|
3229
|
+
return extended === 1;
|
|
3230
|
+
}
|
|
3205
3231
|
async exists(key) {
|
|
3206
3232
|
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3207
3233
|
return result > 0;
|
|
@@ -3535,6 +3561,15 @@ var init_vercel_kv = __esm({
|
|
|
3535
3561
|
);
|
|
3536
3562
|
return deleted === 1;
|
|
3537
3563
|
}
|
|
3564
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
3565
|
+
validateTTL(ttlSeconds);
|
|
3566
|
+
const extended = await this.getConnectedClient().eval(
|
|
3567
|
+
COMPARE_AND_EXPIRE_SCRIPT,
|
|
3568
|
+
[this.prefixKey(key)],
|
|
3569
|
+
[expectedValue, String(ttlSeconds)]
|
|
3570
|
+
);
|
|
3571
|
+
return extended === 1;
|
|
3572
|
+
}
|
|
3538
3573
|
async exists(key) {
|
|
3539
3574
|
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3540
3575
|
return result > 0;
|
|
@@ -3770,6 +3805,15 @@ var init_upstash = __esm({
|
|
|
3770
3805
|
);
|
|
3771
3806
|
return deleted === 1;
|
|
3772
3807
|
}
|
|
3808
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
3809
|
+
validateTTL(ttlSeconds);
|
|
3810
|
+
const extended = await this.getConnectedClient().eval(
|
|
3811
|
+
COMPARE_AND_EXPIRE_SCRIPT,
|
|
3812
|
+
[this.prefixKey(key)],
|
|
3813
|
+
[expectedValue, String(ttlSeconds)]
|
|
3814
|
+
);
|
|
3815
|
+
return extended === 1;
|
|
3816
|
+
}
|
|
3773
3817
|
async exists(key) {
|
|
3774
3818
|
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3775
3819
|
return result > 0;
|
|
@@ -5323,6 +5367,12 @@ var NamespacedStorageImpl = class _NamespacedStorageImpl {
|
|
|
5323
5367
|
}
|
|
5324
5368
|
return this.adapter.deleteIfEquals(this.prefixKey(key), expectedValue);
|
|
5325
5369
|
}
|
|
5370
|
+
async expireIfEquals(key, expectedValue, ttlSeconds) {
|
|
5371
|
+
if (!this.adapter.expireIfEquals) {
|
|
5372
|
+
throw new StorageNotSupportedError("expireIfEquals", "this storage adapter");
|
|
5373
|
+
}
|
|
5374
|
+
return this.adapter.expireIfEquals(this.prefixKey(key), expectedValue, ttlSeconds);
|
|
5375
|
+
}
|
|
5326
5376
|
// ============================================
|
|
5327
5377
|
// Pub/Sub (with channel prefixing)
|
|
5328
5378
|
// ============================================
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/utils",
|
|
3
|
-
"version": "1.9.
|
|
4
|
-
"description": "Shared
|
|
3
|
+
"version": "1.9.7",
|
|
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",
|
|
7
7
|
"keywords": [
|
|
8
|
+
"frontmcp",
|
|
9
|
+
"mcp",
|
|
8
10
|
"utils",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"typescript"
|
|
15
|
-
"mcp"
|
|
11
|
+
"uri-template",
|
|
12
|
+
"crypto",
|
|
13
|
+
"pkce",
|
|
14
|
+
"storage",
|
|
15
|
+
"redis",
|
|
16
|
+
"typescript"
|
|
16
17
|
],
|
|
17
18
|
"repository": {
|
|
18
19
|
"type": "git",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"bugs": {
|
|
23
24
|
"url": "https://github.com/agentfront/frontmcp/issues"
|
|
24
25
|
},
|
|
25
|
-
"homepage": "https://
|
|
26
|
+
"homepage": "https://frontmcp.dev",
|
|
26
27
|
"engines": {
|
|
27
28
|
"node": ">=24.0.0"
|
|
28
29
|
},
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"@noble/hashes": "^2.0.1"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"@frontmcp/lazy-zod": "1.9.
|
|
36
|
+
"@frontmcp/lazy-zod": "1.9.7",
|
|
36
37
|
"@upstash/redis": "^1.34.0",
|
|
37
38
|
"@vercel/kv": "^2.0.0 || ^3.0.0",
|
|
38
39
|
"ioredis": "^5.0.0"
|
|
@@ -68,6 +68,8 @@ export declare abstract class BaseStorageAdapter implements StorageAdapter {
|
|
|
68
68
|
* Override in adapters whose backend can compare and delete in one step.
|
|
69
69
|
*/
|
|
70
70
|
deleteIfEquals(_key: string, _expectedValue: string): Promise<boolean>;
|
|
71
|
+
/** Default: not supported, for the same reason as {@link deleteIfEquals}. */
|
|
72
|
+
expireIfEquals(_key: string, _expectedValue: string, _ttlSeconds: number): Promise<boolean>;
|
|
71
73
|
/**
|
|
72
74
|
* Default: pub/sub not supported.
|
|
73
75
|
* Override in adapters that support it.
|
|
@@ -43,6 +43,7 @@ export declare class MemoryStorageAdapter extends BaseStorageAdapter {
|
|
|
43
43
|
protected doSet(key: string, value: string, options?: SetOptions): Promise<void>;
|
|
44
44
|
delete(key: string): Promise<boolean>;
|
|
45
45
|
deleteIfEquals(key: string, expectedValue: string): Promise<boolean>;
|
|
46
|
+
expireIfEquals(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
46
47
|
exists(key: string): Promise<boolean>;
|
|
47
48
|
expire(key: string, ttlSeconds: number): Promise<boolean>;
|
|
48
49
|
ttl(key: string): Promise<number | null>;
|
|
@@ -59,6 +59,7 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
|
|
|
59
59
|
protected doSet(key: string, value: string, options?: SetOptions): Promise<void>;
|
|
60
60
|
delete(key: string): Promise<boolean>;
|
|
61
61
|
deleteIfEquals(key: string, expectedValue: string): Promise<boolean>;
|
|
62
|
+
expireIfEquals(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
62
63
|
exists(key: string): Promise<boolean>;
|
|
63
64
|
mget(keys: string[]): Promise<(string | null)[]>;
|
|
64
65
|
mset(entries: import('../types').SetEntry[]): Promise<void>;
|
|
@@ -53,6 +53,7 @@ export declare class UpstashStorageAdapter extends BaseStorageAdapter {
|
|
|
53
53
|
protected doSet(key: string, value: string, options?: SetOptions): Promise<void>;
|
|
54
54
|
delete(key: string): Promise<boolean>;
|
|
55
55
|
deleteIfEquals(key: string, expectedValue: string): Promise<boolean>;
|
|
56
|
+
expireIfEquals(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
56
57
|
exists(key: string): Promise<boolean>;
|
|
57
58
|
mget(keys: string[]): Promise<(string | null)[]>;
|
|
58
59
|
mdelete(keys: string[]): Promise<number>;
|
|
@@ -88,6 +88,7 @@ export declare class VercelKvStorageAdapter extends BaseStorageAdapter {
|
|
|
88
88
|
protected doSet(key: string, value: string, options?: SetOptions): Promise<void>;
|
|
89
89
|
delete(key: string): Promise<boolean>;
|
|
90
90
|
deleteIfEquals(key: string, expectedValue: string): Promise<boolean>;
|
|
91
|
+
expireIfEquals(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
91
92
|
exists(key: string): Promise<boolean>;
|
|
92
93
|
mget(keys: string[]): Promise<(string | null)[]>;
|
|
93
94
|
mdelete(keys: string[]): Promise<number>;
|
package/storage/namespace.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ export declare class NamespacedStorageImpl implements NamespacedStorage {
|
|
|
67
67
|
decr(key: string): Promise<number>;
|
|
68
68
|
incrBy(key: string, amount: number): Promise<number>;
|
|
69
69
|
deleteIfEquals(key: string, expectedValue: string): Promise<boolean>;
|
|
70
|
+
expireIfEquals(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
70
71
|
publish(channel: string, message: string): Promise<number>;
|
|
71
72
|
subscribe(channel: string, handler: MessageHandler): Promise<Unsubscribe>;
|
|
72
73
|
supportsPubSub(): boolean;
|
package/storage/types.d.ts
CHANGED
|
@@ -214,6 +214,16 @@ export interface StorageAdapter {
|
|
|
214
214
|
* @throws StorageNotSupportedError if the backend has no atomic compare-and-delete
|
|
215
215
|
*/
|
|
216
216
|
deleteIfEquals?(key: string, expectedValue: string): Promise<boolean>;
|
|
217
|
+
/**
|
|
218
|
+
* Atomically set a key's TTL only while it still holds `expectedValue` (compare-and-expire).
|
|
219
|
+
*
|
|
220
|
+
* @param key - Storage key
|
|
221
|
+
* @param expectedValue - The exact value the key must hold
|
|
222
|
+
* @param ttlSeconds - The new time to live, in seconds
|
|
223
|
+
* @returns true if this call set the TTL, false if the key was missing or held another value
|
|
224
|
+
* @throws StorageNotSupportedError if the backend has no atomic compare-and-expire
|
|
225
|
+
*/
|
|
226
|
+
expireIfEquals?(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
217
227
|
/**
|
|
218
228
|
* Publish a message to a channel.
|
|
219
229
|
* Not all adapters support pub/sub (e.g., Vercel KV doesn't).
|
|
@@ -278,6 +288,8 @@ export interface NamespacedStorage extends StorageAdapter {
|
|
|
278
288
|
namespace(name: string, id?: string): NamespacedStorage;
|
|
279
289
|
/** {@link StorageAdapter.deleteIfEquals}; throws StorageNotSupportedError when the adapter has none. */
|
|
280
290
|
deleteIfEquals(key: string, expectedValue: string): Promise<boolean>;
|
|
291
|
+
/** {@link StorageAdapter.expireIfEquals}; throws StorageNotSupportedError when the adapter has none. */
|
|
292
|
+
expireIfEquals(key: string, expectedValue: string, ttlSeconds: number): Promise<boolean>;
|
|
281
293
|
/**
|
|
282
294
|
* Get the underlying root adapter (for advanced use).
|
|
283
295
|
* Use with caution - operations bypass namespace prefixing.
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
/** Redis Lua script for `deleteIfEquals`: deletes KEYS[1] only while it holds ARGV[1], returning 1 or 0. */
|
|
2
2
|
export declare const COMPARE_AND_DELETE_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0";
|
|
3
|
+
/** Redis Lua script for `expireIfEquals`: sets KEYS[1]'s TTL to ARGV[2] seconds only while it holds ARGV[1]. */
|
|
4
|
+
export declare const COMPARE_AND_EXPIRE_SCRIPT = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0";
|