@frontmcp/plugins 0.6.0 → 0.6.2
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/{src/cache → cache}/cache.types.d.ts +26 -3
- package/cache/index.js +412 -0
- package/{src/cache → cache}/providers/cache-memory.provider.d.ts +3 -3
- package/{src/cache → cache}/providers/cache-redis.provider.d.ts +3 -3
- package/cache/providers/cache-vercel-kv.provider.d.ts +24 -0
- package/codecall/index.js +2988 -0
- package/{src/codecall → codecall}/tools/invoke.schema.d.ts +6 -1
- package/esm/cache/index.mjs +395 -0
- package/esm/codecall/index.mjs +2959 -0
- package/esm/index.mjs +2951 -0
- package/esm/package.json +85 -0
- package/index.js +2973 -0
- package/package.json +49 -10
- package/src/cache/README.md +0 -186
- package/src/cache/cache.plugin.js +0 -135
- package/src/cache/cache.plugin.js.map +0 -1
- package/src/cache/cache.symbol.js +0 -5
- package/src/cache/cache.symbol.js.map +0 -1
- package/src/cache/cache.types.js +0 -3
- package/src/cache/cache.types.js.map +0 -1
- package/src/cache/index.js +0 -8
- package/src/cache/index.js.map +0 -1
- package/src/cache/providers/cache-memory.provider.js +0 -110
- package/src/cache/providers/cache-memory.provider.js.map +0 -1
- package/src/cache/providers/cache-redis.provider.js +0 -69
- package/src/cache/providers/cache-redis.provider.js.map +0 -1
- package/src/codecall/README.md +0 -999
- package/src/codecall/codecall.plugin.js +0 -152
- package/src/codecall/codecall.plugin.js.map +0 -1
- package/src/codecall/codecall.symbol.js +0 -4
- package/src/codecall/codecall.symbol.js.map +0 -1
- package/src/codecall/codecall.types.js +0 -262
- package/src/codecall/codecall.types.js.map +0 -1
- package/src/codecall/errors/index.js +0 -6
- package/src/codecall/errors/index.js.map +0 -1
- package/src/codecall/errors/tool-call.errors.js +0 -119
- package/src/codecall/errors/tool-call.errors.js.map +0 -1
- package/src/codecall/index.js +0 -8
- package/src/codecall/index.js.map +0 -1
- package/src/codecall/providers/code-call.config.js +0 -120
- package/src/codecall/providers/code-call.config.js.map +0 -1
- package/src/codecall/security/index.js +0 -7
- package/src/codecall/security/index.js.map +0 -1
- package/src/codecall/security/self-reference-guard.js +0 -70
- package/src/codecall/security/self-reference-guard.js.map +0 -1
- package/src/codecall/security/tool-access-control.service.js +0 -170
- package/src/codecall/security/tool-access-control.service.js.map +0 -1
- package/src/codecall/services/audit-logger.service.js +0 -322
- package/src/codecall/services/audit-logger.service.js.map +0 -1
- package/src/codecall/services/enclave.service.js +0 -214
- package/src/codecall/services/enclave.service.js.map +0 -1
- package/src/codecall/services/error-enrichment.service.js +0 -387
- package/src/codecall/services/error-enrichment.service.js.map +0 -1
- package/src/codecall/services/index.js +0 -13
- package/src/codecall/services/index.js.map +0 -1
- package/src/codecall/services/output-sanitizer.js +0 -260
- package/src/codecall/services/output-sanitizer.js.map +0 -1
- package/src/codecall/services/synonym-expansion.service.js +0 -374
- package/src/codecall/services/synonym-expansion.service.js.map +0 -1
- package/src/codecall/services/tool-search.service.js +0 -587
- package/src/codecall/services/tool-search.service.js.map +0 -1
- package/src/codecall/tools/describe.schema.js +0 -67
- package/src/codecall/tools/describe.schema.js.map +0 -1
- package/src/codecall/tools/describe.tool.js +0 -207
- package/src/codecall/tools/describe.tool.js.map +0 -1
- package/src/codecall/tools/execute.schema.js +0 -116
- package/src/codecall/tools/execute.schema.js.map +0 -1
- package/src/codecall/tools/execute.tool.js +0 -238
- package/src/codecall/tools/execute.tool.js.map +0 -1
- package/src/codecall/tools/index.js +0 -13
- package/src/codecall/tools/index.js.map +0 -1
- package/src/codecall/tools/invoke.schema.js +0 -27
- package/src/codecall/tools/invoke.schema.js.map +0 -1
- package/src/codecall/tools/invoke.tool.js +0 -70
- package/src/codecall/tools/invoke.tool.js.map +0 -1
- package/src/codecall/tools/search.schema.js +0 -60
- package/src/codecall/tools/search.schema.js.map +0 -1
- package/src/codecall/tools/search.tool.js +0 -108
- package/src/codecall/tools/search.tool.js.map +0 -1
- package/src/codecall/utils/describe.utils.js +0 -531
- package/src/codecall/utils/describe.utils.js.map +0 -1
- package/src/codecall/utils/index.js +0 -7
- package/src/codecall/utils/index.js.map +0 -1
- package/src/codecall/utils/mcp-result.js +0 -36
- package/src/codecall/utils/mcp-result.js.map +0 -1
- package/src/index.js +0 -9
- package/src/index.js.map +0 -1
- /package/{src/cache → cache}/cache.plugin.d.ts +0 -0
- /package/{src/cache → cache}/cache.symbol.d.ts +0 -0
- /package/{src/cache → cache}/index.d.ts +0 -0
- /package/{src/codecall → codecall}/codecall.plugin.d.ts +0 -0
- /package/{src/codecall → codecall}/codecall.symbol.d.ts +0 -0
- /package/{src/codecall → codecall}/codecall.types.d.ts +0 -0
- /package/{src/codecall → codecall}/errors/index.d.ts +0 -0
- /package/{src/codecall → codecall}/errors/tool-call.errors.d.ts +0 -0
- /package/{src/codecall → codecall}/index.d.ts +0 -0
- /package/{src/codecall → codecall}/providers/code-call.config.d.ts +0 -0
- /package/{src/codecall → codecall}/security/index.d.ts +0 -0
- /package/{src/codecall → codecall}/security/self-reference-guard.d.ts +0 -0
- /package/{src/codecall → codecall}/security/tool-access-control.service.d.ts +0 -0
- /package/{src/codecall → codecall}/services/audit-logger.service.d.ts +0 -0
- /package/{src/codecall → codecall}/services/enclave.service.d.ts +0 -0
- /package/{src/codecall → codecall}/services/error-enrichment.service.d.ts +0 -0
- /package/{src/codecall → codecall}/services/index.d.ts +0 -0
- /package/{src/codecall → codecall}/services/output-sanitizer.d.ts +0 -0
- /package/{src/codecall → codecall}/services/synonym-expansion.service.d.ts +0 -0
- /package/{src/codecall → codecall}/services/tool-search.service.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/describe.schema.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/describe.tool.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/execute.schema.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/execute.tool.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/index.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/invoke.tool.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/search.schema.d.ts +0 -0
- /package/{src/codecall → codecall}/tools/search.tool.d.ts +0 -0
- /package/{src/codecall → codecall}/utils/describe.utils.d.ts +0 -0
- /package/{src/codecall → codecall}/utils/index.d.ts +0 -0
- /package/{src/codecall → codecall}/utils/mcp-result.d.ts +0 -0
- /package/{src/index.d.ts → index.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugins",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "FrontMCP plugins to extend the SDK",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -21,23 +21,62 @@
|
|
|
21
21
|
"bugs": {
|
|
22
22
|
"url": "https://github.com/agentfront/frontmcp/issues"
|
|
23
23
|
},
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"main": "./index.js",
|
|
26
|
+
"module": "./esm/index.mjs",
|
|
27
|
+
"types": "./index.d.ts",
|
|
28
|
+
"sideEffects": false,
|
|
26
29
|
"exports": {
|
|
27
30
|
"./package.json": "./package.json",
|
|
28
31
|
".": {
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./index.d.ts",
|
|
34
|
+
"default": "./index.js"
|
|
35
|
+
},
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./index.d.ts",
|
|
38
|
+
"default": "./esm/index.mjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"./cache": {
|
|
42
|
+
"require": {
|
|
43
|
+
"types": "./cache/index.d.ts",
|
|
44
|
+
"default": "./cache/index.js"
|
|
45
|
+
},
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./cache/index.d.ts",
|
|
48
|
+
"default": "./esm/cache/index.mjs"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"./codecall": {
|
|
52
|
+
"require": {
|
|
53
|
+
"types": "./codecall/index.d.ts",
|
|
54
|
+
"default": "./codecall/index.js"
|
|
55
|
+
},
|
|
56
|
+
"import": {
|
|
57
|
+
"types": "./codecall/index.d.ts",
|
|
58
|
+
"default": "./esm/codecall/index.mjs"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"./esm": null
|
|
33
62
|
},
|
|
34
63
|
"dependencies": {
|
|
35
64
|
"ioredis": "^5.8.0",
|
|
36
|
-
"@frontmcp/sdk": "0.6.
|
|
65
|
+
"@frontmcp/sdk": "0.6.2",
|
|
37
66
|
"enclave-vm": "^1.0.3",
|
|
38
67
|
"vectoriadb": "^2.0.1",
|
|
39
68
|
"zod": "^4.0.0",
|
|
40
|
-
"@modelcontextprotocol/sdk": "1.
|
|
69
|
+
"@modelcontextprotocol/sdk": "1.25.1"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"reflect-metadata": "^0.2.2"
|
|
41
73
|
},
|
|
42
|
-
"
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@vercel/kv": "^2.0.0 || ^3.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@vercel/kv": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
43
82
|
}
|
package/src/cache/README.md
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# Cache Plugin
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
### Purpose
|
|
6
|
-
|
|
7
|
-
Provides **transparent response caching** for tools based on their input payloads — reducing redundant computation and
|
|
8
|
-
improving response time.
|
|
9
|
-
|
|
10
|
-
### Storage
|
|
11
|
-
|
|
12
|
-
Supports **in-memory** caching out of the box and optional **Redis** backends. Choose `type: 'memory'`, `type: 'redis'`,
|
|
13
|
-
or `type: 'redis-client'` when registering the plugin.
|
|
14
|
-
|
|
15
|
-
### Keying
|
|
16
|
-
|
|
17
|
-
Cache entries are keyed using a **deterministic hash** of each tool’s validated input (`ctx.input`).
|
|
18
|
-
|
|
19
|
-
### Lifecycle Hooks
|
|
20
|
-
|
|
21
|
-
- **Before execution:** `ToolHook.Will('execute')` checks the cache store.
|
|
22
|
-
- **After execution:** `ToolHook.Did('execute')` writes new responses with the resolved TTL.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 🔄 How It Works
|
|
27
|
-
|
|
28
|
-
1. Before a tool executes, the plugin hashes `ctx.input` and checks the configured store for a cached entry.
|
|
29
|
-
2. On a **cache hit**, the cached output is returned immediately, and a flag
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{ "___cached__": true }
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
is added to the response (useful for debugging).
|
|
36
|
-
|
|
37
|
-
3. If no cached entry exists, the tool runs normally, and the output is stored with the resolved TTL.
|
|
38
|
-
4. When `slideWindow` is enabled, each read refreshes the TTL to keep hot entries alive.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## ⚙️ Requirements
|
|
43
|
-
|
|
44
|
-
- Memory mode (`type: 'memory'` or omitted) needs no external services; data resets when the process restarts.
|
|
45
|
-
- Redis mode (`type: 'redis'`) provisions a `CacheRedisProvider` using your host/port (optionally password/db).
|
|
46
|
-
- Redis client mode (`type: 'redis-client'`) reuses an existing `ioredis` client that you supply.
|
|
47
|
-
- Only enable caching for **deterministic** tools whose outputs depend solely on inputs.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 🧩 Registering the Plugin
|
|
52
|
-
|
|
53
|
-
Assume your app class is decorated with `@App` and exposes a `plugins` array.
|
|
54
|
-
|
|
55
|
-
### 1. Default configuration (in-memory, 1-day TTL)
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
plugins: [CachePlugin];
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 2. Custom default TTL (memory)
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
plugins: [
|
|
65
|
-
CachePlugin.init({
|
|
66
|
-
type: 'memory',
|
|
67
|
-
defaultTTL: 300, // 5 minutes
|
|
68
|
-
}),
|
|
69
|
-
];
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 3. Redis connection via config
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
plugins: [
|
|
76
|
-
CachePlugin.init({
|
|
77
|
-
type: 'redis',
|
|
78
|
-
defaultTTL: 600,
|
|
79
|
-
config: {
|
|
80
|
-
host: '127.0.0.1',
|
|
81
|
-
port: 6379,
|
|
82
|
-
// password, db optional
|
|
83
|
-
},
|
|
84
|
-
}),
|
|
85
|
-
];
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 4. Reuse an existing Redis client
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
plugins: [
|
|
92
|
-
CachePlugin.init({
|
|
93
|
-
type: 'redis-client',
|
|
94
|
-
defaultTTL: 900,
|
|
95
|
-
client: existingRedis,
|
|
96
|
-
}),
|
|
97
|
-
];
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 🧠 Tool-Level Configuration
|
|
103
|
-
|
|
104
|
-
Caching is **opt-in** per tool. Add the `cache` field in your tool’s metadata.
|
|
105
|
-
|
|
106
|
-
### Minimal example
|
|
107
|
-
|
|
108
|
-
Uses plugin defaults.
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
@Tool({
|
|
112
|
-
name: 'create-expense',
|
|
113
|
-
cache: true,
|
|
114
|
-
})
|
|
115
|
-
export default class CreateExpenseTool extends ToolContext {
|
|
116
|
-
// ...
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Custom TTL and sliding window
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
@Tool({
|
|
124
|
-
name: 'get-expense-by-id',
|
|
125
|
-
cache: {
|
|
126
|
-
ttl: 60, // 1 minute
|
|
127
|
-
slideWindow: true, // refresh TTL on read
|
|
128
|
-
},
|
|
129
|
-
})
|
|
130
|
-
export default class GetExpenseTool extends ToolContext {
|
|
131
|
-
// ...
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## ⚖️ Behavior Details
|
|
138
|
-
|
|
139
|
-
| Behavior | Description |
|
|
140
|
-
| ------------------ | ---------------------------------------------------------------------- |
|
|
141
|
-
| **Key Derivation** | Deterministic hash from `ctx.input`. Changing input changes cache key. |
|
|
142
|
-
| **Cache Hits** | Adds `___cached__: true` to the output (for observability only). |
|
|
143
|
-
| **Default TTL** | Plugin `defaultTTL` → falls back to `86400` seconds (1 day). |
|
|
144
|
-
| **Sliding Window** | Extends TTL on reads when `slideWindow` is true. |
|
|
145
|
-
| **Store Choice** | Memory is node-local; Redis enables multi-instance sharing. |
|
|
146
|
-
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
## 🧹 Invalidation Strategies
|
|
150
|
-
|
|
151
|
-
| Strategy | Use When | Notes |
|
|
152
|
-
| ----------------------- | -------------------------- | ---------------------------------------- |
|
|
153
|
-
| **Time-based** | Data changes often | Use short TTLs |
|
|
154
|
-
| **Input Shaping** | Input determines freshness | Include relevant identifiers in input |
|
|
155
|
-
| **Manual Invalidation** | You need explicit control | Extend or wrap the plugin to delete keys |
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## 🧩 Troubleshooting
|
|
160
|
-
|
|
161
|
-
| Symptom | Possible Cause | Fix |
|
|
162
|
-
| --------------------------- | -------------------------------------------- | ------------------------------------------------------------ |
|
|
163
|
-
| No cache hits | Tool missing `cache` config or store offline | Add `cache: {}` to tool metadata and verify store connection |
|
|
164
|
-
| Output unexpectedly cached | Previous result reused | Lower TTL or modify input for unique cache key |
|
|
165
|
-
| Need tenant/session scoping | Same input shared across tenants | Include tenant/session IDs in the input payload |
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## 🧾 Reference
|
|
170
|
-
|
|
171
|
-
### Plugin options (registration)
|
|
172
|
-
|
|
173
|
-
| Option | Type | Default | Description |
|
|
174
|
-
| ------------ | ------------------------------------------------ | ---------- | -------------------------------------------------- |
|
|
175
|
-
| `type` | `'memory' \| 'redis' \| 'redis-client'` | `'memory'` | Selects the backing store. |
|
|
176
|
-
| `defaultTTL` | `number` | `86400` | Default time-to-live (seconds). |
|
|
177
|
-
| `config` | `{ host: string; port: number; password?; db? }` | — | Redis connection details when `type: 'redis'`. |
|
|
178
|
-
| `client` | `Redis` (from `ioredis`) | — | Existing Redis client when `type: 'redis-client'`. |
|
|
179
|
-
|
|
180
|
-
### Tool metadata (`@Tool` / `tool`)
|
|
181
|
-
|
|
182
|
-
| Option | Type | Description |
|
|
183
|
-
| ------------------- | --------- | ------------------------------------- |
|
|
184
|
-
| `cache` | `true` | Enable caching with plugin defaults. |
|
|
185
|
-
| `cache.ttl` | `number` | Override TTL (seconds) for this tool. |
|
|
186
|
-
| `cache.slideWindow` | `boolean` | Refresh TTL on reads for this tool. |
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var CachePlugin_1;
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const sdk_1 = require("@frontmcp/sdk");
|
|
6
|
-
const cache_redis_provider_1 = tslib_1.__importDefault(require("./providers/cache-redis.provider"));
|
|
7
|
-
const cache_memory_provider_1 = tslib_1.__importDefault(require("./providers/cache-memory.provider"));
|
|
8
|
-
const cache_symbol_1 = require("./cache.symbol");
|
|
9
|
-
let CachePlugin = class CachePlugin extends sdk_1.DynamicPlugin {
|
|
10
|
-
static { CachePlugin_1 = this; }
|
|
11
|
-
static dynamicProviders = (options) => {
|
|
12
|
-
const providers = [];
|
|
13
|
-
switch (options.type) {
|
|
14
|
-
case 'redis':
|
|
15
|
-
case 'redis-client':
|
|
16
|
-
providers.push({
|
|
17
|
-
name: 'cache:redis',
|
|
18
|
-
provide: cache_symbol_1.CacheStoreToken,
|
|
19
|
-
useValue: new cache_redis_provider_1.default(options),
|
|
20
|
-
});
|
|
21
|
-
break;
|
|
22
|
-
case 'memory':
|
|
23
|
-
providers.push({
|
|
24
|
-
name: 'cache:memory',
|
|
25
|
-
provide: cache_symbol_1.CacheStoreToken,
|
|
26
|
-
useValue: new cache_memory_provider_1.default(options.defaultTTL),
|
|
27
|
-
});
|
|
28
|
-
break;
|
|
29
|
-
}
|
|
30
|
-
return providers;
|
|
31
|
-
};
|
|
32
|
-
static defaultOptions = {
|
|
33
|
-
type: 'memory',
|
|
34
|
-
};
|
|
35
|
-
options;
|
|
36
|
-
constructor(options = CachePlugin_1.defaultOptions) {
|
|
37
|
-
super();
|
|
38
|
-
this.options = {
|
|
39
|
-
defaultTTL: 60 * 60 * 24,
|
|
40
|
-
...options,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
async willReadCache(flowCtx) {
|
|
44
|
-
const { tool, toolContext } = flowCtx.state;
|
|
45
|
-
if (!tool || !toolContext)
|
|
46
|
-
return;
|
|
47
|
-
const { cache } = toolContext.metadata;
|
|
48
|
-
if (!cache || typeof toolContext.input === 'undefined') {
|
|
49
|
-
// no cache or no input, skip
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const cacheStore = this.get(cache_symbol_1.CacheStoreToken);
|
|
53
|
-
const hash = hashObject({ tool: tool.fullName, input: toolContext.input });
|
|
54
|
-
const cached = await cacheStore.getValue(hash);
|
|
55
|
-
if (cached !== undefined && cached !== null) {
|
|
56
|
-
if (cache === true || (cache.ttl && cache.slideWindow)) {
|
|
57
|
-
const ttl = cache === true ? this.options.defaultTTL : cache.ttl ?? this.options.defaultTTL;
|
|
58
|
-
await cacheStore.setValue(hash, cached, ttl);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* double check if cache still valid based on tool output schema
|
|
62
|
-
*/
|
|
63
|
-
if (!tool.safeParseOutput(cached).success) {
|
|
64
|
-
await cacheStore.delete(hash);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* cache hit, set output to the main flow context
|
|
69
|
-
*/
|
|
70
|
-
flowCtx.state.rawOutput = cached;
|
|
71
|
-
/**
|
|
72
|
-
* call respond to bypass tool execution
|
|
73
|
-
*/
|
|
74
|
-
toolContext.respond(cached);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async willWriteCache(flowCtx) {
|
|
78
|
-
const { tool, toolContext } = flowCtx.state;
|
|
79
|
-
if (!tool || !toolContext)
|
|
80
|
-
return;
|
|
81
|
-
const { cache } = toolContext.metadata;
|
|
82
|
-
if (!cache || typeof toolContext.input === 'undefined') {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const cacheStore = this.get(cache_symbol_1.CacheStoreToken);
|
|
86
|
-
const ttl = cache === true ? this.options.defaultTTL : cache.ttl ?? this.options.defaultTTL;
|
|
87
|
-
const hash = hashObject({ tool: tool.fullName, input: toolContext.input });
|
|
88
|
-
await cacheStore.setValue(hash, toolContext.output, ttl);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
tslib_1.__decorate([
|
|
92
|
-
sdk_1.ToolHook.Will('execute', { priority: 1000 }),
|
|
93
|
-
tslib_1.__metadata("design:type", Function),
|
|
94
|
-
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
95
|
-
tslib_1.__metadata("design:returntype", Promise)
|
|
96
|
-
], CachePlugin.prototype, "willReadCache", null);
|
|
97
|
-
tslib_1.__decorate([
|
|
98
|
-
sdk_1.ToolHook.Did('execute', { priority: 1000 }),
|
|
99
|
-
tslib_1.__metadata("design:type", Function),
|
|
100
|
-
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
101
|
-
tslib_1.__metadata("design:returntype", Promise)
|
|
102
|
-
], CachePlugin.prototype, "willWriteCache", null);
|
|
103
|
-
CachePlugin = CachePlugin_1 = tslib_1.__decorate([
|
|
104
|
-
(0, sdk_1.Plugin)({
|
|
105
|
-
name: 'cache',
|
|
106
|
-
description: 'Cache plugin for caching tool results',
|
|
107
|
-
providers: [
|
|
108
|
-
/* add providers that always loaded with the plugin or default providers */
|
|
109
|
-
{
|
|
110
|
-
// this is a default provider for cache, will be overridden if dynamicProviders based on config
|
|
111
|
-
name: 'cache:memory',
|
|
112
|
-
provide: cache_symbol_1.CacheStoreToken,
|
|
113
|
-
useValue: new cache_memory_provider_1.default(60 * 60 * 24),
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
}),
|
|
117
|
-
tslib_1.__metadata("design:paramtypes", [Object])
|
|
118
|
-
], CachePlugin);
|
|
119
|
-
exports.default = CachePlugin;
|
|
120
|
-
function hashObject(obj) {
|
|
121
|
-
const keys = Object.keys(obj).sort();
|
|
122
|
-
return keys.reduce((acc, key) => {
|
|
123
|
-
acc += key + ':';
|
|
124
|
-
const val = obj[key];
|
|
125
|
-
if (typeof val === 'object' && val !== null) {
|
|
126
|
-
acc += hashObject(val);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
acc += val;
|
|
130
|
-
}
|
|
131
|
-
acc += ';';
|
|
132
|
-
return acc;
|
|
133
|
-
}, '');
|
|
134
|
-
}
|
|
135
|
-
//# sourceMappingURL=cache.plugin.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.plugin.js","sourceRoot":"","sources":["../../../src/cache/cache.plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { DynamicPlugin, FlowCtxOf, Plugin, ProviderType, ToolHook } from '@frontmcp/sdk';\nimport CacheRedisProvider from './providers/cache-redis.provider';\nimport CacheMemoryProvider from './providers/cache-memory.provider';\nimport { CachePluginOptions } from './cache.types';\nimport { CacheStoreToken } from './cache.symbol';\n\n@Plugin({\n name: 'cache',\n description: 'Cache plugin for caching tool results',\n providers: [\n /* add providers that always loaded with the plugin or default providers */\n {\n // this is a default provider for cache, will be overridden if dynamicProviders based on config\n name: 'cache:memory',\n provide: CacheStoreToken,\n useValue: new CacheMemoryProvider(60 * 60 * 24),\n },\n ],\n})\nexport default class CachePlugin extends DynamicPlugin<CachePluginOptions> {\n static override dynamicProviders = (options: CachePluginOptions) => {\n const providers: ProviderType[] = [];\n switch (options.type) {\n case 'redis':\n case 'redis-client':\n providers.push({\n name: 'cache:redis',\n provide: CacheStoreToken,\n useValue: new CacheRedisProvider(options),\n });\n break;\n case 'memory':\n providers.push({\n name: 'cache:memory',\n provide: CacheStoreToken,\n useValue: new CacheMemoryProvider(options.defaultTTL),\n });\n break;\n }\n return providers;\n };\n\n static defaultOptions: CachePluginOptions = {\n type: 'memory',\n };\n options: CachePluginOptions;\n\n constructor(options: CachePluginOptions = CachePlugin.defaultOptions) {\n super();\n this.options = {\n defaultTTL: 60 * 60 * 24,\n ...options,\n };\n }\n\n @ToolHook.Will('execute', { priority: 1000 })\n async willReadCache(flowCtx: FlowCtxOf<'tools:call-tool'>) {\n const { tool, toolContext } = flowCtx.state;\n if (!tool || !toolContext) return;\n\n const { cache } = toolContext.metadata;\n if (!cache || typeof toolContext.input === 'undefined') {\n // no cache or no input, skip\n return;\n }\n const cacheStore = this.get(CacheStoreToken);\n const hash = hashObject({ tool: tool.fullName, input: toolContext.input });\n const cached = await cacheStore.getValue(hash);\n\n if (cached !== undefined && cached !== null) {\n if (cache === true || (cache.ttl && cache.slideWindow)) {\n const ttl = cache === true ? this.options.defaultTTL : cache.ttl ?? this.options.defaultTTL;\n await cacheStore.setValue(hash, cached, ttl);\n }\n\n /**\n * double check if cache still valid based on tool output schema\n */\n if (!tool.safeParseOutput(cached).success) {\n await cacheStore.delete(hash);\n return;\n }\n /**\n * cache hit, set output to the main flow context\n */\n flowCtx.state.rawOutput = cached;\n\n /**\n * call respond to bypass tool execution\n */\n toolContext.respond(cached);\n }\n }\n\n @ToolHook.Did('execute', { priority: 1000 })\n async willWriteCache(flowCtx: FlowCtxOf<'tools:call-tool'>) {\n const { tool, toolContext } = flowCtx.state;\n if (!tool || !toolContext) return;\n const { cache } = toolContext.metadata;\n if (!cache || typeof toolContext.input === 'undefined') {\n return;\n }\n const cacheStore = this.get(CacheStoreToken);\n const ttl = cache === true ? this.options.defaultTTL : cache.ttl ?? this.options.defaultTTL;\n\n const hash = hashObject({ tool: tool.fullName, input: toolContext.input });\n await cacheStore.setValue(hash, toolContext.output, ttl);\n }\n}\n\nfunction hashObject(obj: any) {\n const keys = Object.keys(obj).sort();\n return keys.reduce((acc, key) => {\n acc += key + ':';\n const val = obj[key];\n if (typeof val === 'object' && val !== null) {\n acc += hashObject(val);\n } else {\n acc += val;\n }\n acc += ';';\n return acc;\n }, '');\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.symbol.js","sourceRoot":"","sources":["../../../src/cache/cache.symbol.ts"],"names":[],"mappings":";;;AAGa,QAAA,eAAe,GAA+B,MAAM,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["import { CacheStoreInterface } from './cache.types';\nimport { Token } from '@frontmcp/sdk';\n\nexport const CacheStoreToken: Token<CacheStoreInterface> = Symbol('plugin:cache:store');\n"]}
|
package/src/cache/cache.types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.types.js","sourceRoot":"","sources":["../../../src/cache/cache.types.ts"],"names":[],"mappings":"","sourcesContent":["import { Redis as RedisClient } from 'ioredis';\n\ndeclare global {\n interface ExtendFrontMcpToolMetadata {\n cache?: CachePluginToolOptions | true;\n }\n}\n\nexport interface CachePluginToolOptions {\n /**\n * Time to live in seconds. Default is 1 day.\n */\n ttl?: number; // default 1 day\n\n /**\n * If true, the cache value will be updated with the new value after the TTL.\n * Default is false.\n */\n slideWindow?: boolean;\n}\n\nexport interface BaseCachePluginOptions {\n defaultTTL?: number; // default 1 day\n}\n\nexport interface RedisClientCachePluginOptions extends BaseCachePluginOptions {\n type: 'redis-client';\n client: RedisClient;\n}\n\nexport interface RedisCachePluginOptions extends BaseCachePluginOptions {\n type: 'redis';\n config: {\n host: string;\n port: number;\n password?: string;\n db?: number;\n };\n}\n\nexport type MemoryCachePluginOptions = BaseCachePluginOptions & {\n type: 'memory';\n};\n\nexport type RedisCacheOptions = RedisClientCachePluginOptions | RedisCachePluginOptions;\n\nexport type CachePluginOptions = MemoryCachePluginOptions | RedisCacheOptions;\n\nexport interface CacheStoreInterface {\n setValue(key: string, value: any, ttlSeconds?: number): Promise<void>;\n\n getValue<T = any>(key: string, defaultValue?: T): Promise<T | undefined>;\n\n delete(key: string): Promise<void>;\n\n exists(key: string): Promise<boolean>;\n\n close(): Promise<void>;\n}\n"]}
|
package/src/cache/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
var cache_plugin_1 = require("./cache.plugin");
|
|
6
|
-
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return tslib_1.__importDefault(cache_plugin_1).default; } });
|
|
7
|
-
tslib_1.__exportStar(require("./cache.types"), exports);
|
|
8
|
-
//# sourceMappingURL=index.js.map
|
package/src/cache/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":";;;;AAAA,+CAAyC;AAAhC,gIAAA,OAAO,OAAA;AAChB,wDAA8B","sourcesContent":["export { default } from './cache.plugin';\nexport * from './cache.types';\n"]}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const sdk_1 = require("@frontmcp/sdk");
|
|
5
|
-
const MAX_TIMEOUT_MS = 2 ** 31 - 1; // ~24.8 days (Node setTimeout limit)
|
|
6
|
-
let CacheMemoryProvider = class CacheMemoryProvider {
|
|
7
|
-
memory = new Map();
|
|
8
|
-
sweeper;
|
|
9
|
-
constructor(sweepIntervalTTL = 60) {
|
|
10
|
-
this.sweeper = setInterval(() => this.sweep(), sweepIntervalTTL * 1000);
|
|
11
|
-
// don’t keep the process alive just for the sweeper (Node >=14)
|
|
12
|
-
this.sweeper.unref?.();
|
|
13
|
-
}
|
|
14
|
-
/** Set any value (auto-stringifies objects) */
|
|
15
|
-
async setValue(key, value, ttlSeconds) {
|
|
16
|
-
const strValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
17
|
-
// clear any previous timeout on this key
|
|
18
|
-
const existing = this.memory.get(key);
|
|
19
|
-
if (existing?.timeout)
|
|
20
|
-
clearTimeout(existing.timeout);
|
|
21
|
-
const entry = { value: strValue };
|
|
22
|
-
if (ttlSeconds && ttlSeconds > 0) {
|
|
23
|
-
const ttlMs = ttlSeconds * 1000;
|
|
24
|
-
entry.expiresAt = Date.now() + ttlMs;
|
|
25
|
-
// Only schedule a timer if within Node's setTimeout limit; otherwise rely on sweeper/lazy purge
|
|
26
|
-
if (ttlMs <= MAX_TIMEOUT_MS) {
|
|
27
|
-
entry.timeout = setTimeout(() => {
|
|
28
|
-
// final check guards against clock drift or updates
|
|
29
|
-
const e = this.memory.get(key);
|
|
30
|
-
if (e && e.expiresAt && e.expiresAt <= Date.now()) {
|
|
31
|
-
this.memory.delete(key);
|
|
32
|
-
}
|
|
33
|
-
}, ttlMs);
|
|
34
|
-
entry.timeout.unref?.();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
this.memory.set(key, entry);
|
|
38
|
-
}
|
|
39
|
-
/** Get a value and automatically parse JSON if possible */
|
|
40
|
-
async getValue(key, defaultValue) {
|
|
41
|
-
const entry = this.memory.get(key);
|
|
42
|
-
if (!entry)
|
|
43
|
-
return defaultValue;
|
|
44
|
-
if (this.isExpired(entry)) {
|
|
45
|
-
await this.delete(key);
|
|
46
|
-
return defaultValue;
|
|
47
|
-
}
|
|
48
|
-
const raw = entry.value;
|
|
49
|
-
try {
|
|
50
|
-
return JSON.parse(raw);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// fallback for plain string values
|
|
54
|
-
return raw;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/** Delete a key */
|
|
58
|
-
async delete(key) {
|
|
59
|
-
const entry = this.memory.get(key);
|
|
60
|
-
if (entry?.timeout)
|
|
61
|
-
clearTimeout(entry.timeout);
|
|
62
|
-
this.memory.delete(key);
|
|
63
|
-
}
|
|
64
|
-
/** Check if a key exists (and not expired) */
|
|
65
|
-
async exists(key) {
|
|
66
|
-
const entry = this.memory.get(key);
|
|
67
|
-
if (!entry)
|
|
68
|
-
return false;
|
|
69
|
-
if (this.isExpired(entry)) {
|
|
70
|
-
await this.delete(key);
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
/** Gracefully close the provider */
|
|
76
|
-
async close() {
|
|
77
|
-
if (this.sweeper)
|
|
78
|
-
clearInterval(this.sweeper);
|
|
79
|
-
for (const [, entry] of this.memory) {
|
|
80
|
-
if (entry.timeout)
|
|
81
|
-
clearTimeout(entry.timeout);
|
|
82
|
-
}
|
|
83
|
-
this.memory.clear();
|
|
84
|
-
}
|
|
85
|
-
// ---- internals ----
|
|
86
|
-
isExpired(entry) {
|
|
87
|
-
return entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
|
|
88
|
-
}
|
|
89
|
-
/** Periodically remove expired keys to keep memory tidy */
|
|
90
|
-
sweep() {
|
|
91
|
-
const now = Date.now();
|
|
92
|
-
for (const [key, entry] of this.memory) {
|
|
93
|
-
if (entry.expiresAt !== undefined && entry.expiresAt <= now) {
|
|
94
|
-
if (entry.timeout)
|
|
95
|
-
clearTimeout(entry.timeout);
|
|
96
|
-
this.memory.delete(key);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
CacheMemoryProvider = tslib_1.__decorate([
|
|
102
|
-
(0, sdk_1.Provider)({
|
|
103
|
-
name: 'provider:cache:memory',
|
|
104
|
-
description: 'Memory-based cache provider',
|
|
105
|
-
scope: sdk_1.ProviderScope.GLOBAL,
|
|
106
|
-
}),
|
|
107
|
-
tslib_1.__metadata("design:paramtypes", [Object])
|
|
108
|
-
], CacheMemoryProvider);
|
|
109
|
-
exports.default = CacheMemoryProvider;
|
|
110
|
-
//# sourceMappingURL=cache-memory.provider.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache-memory.provider.js","sourceRoot":"","sources":["../../../../src/cache/providers/cache-memory.provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { Provider, ProviderScope } from '@frontmcp/sdk';\nimport { CacheStoreInterface } from '../cache.types';\n\ntype Entry = {\n value: string;\n /** epoch millis when this entry expires (undefined = no TTL) */\n expiresAt?: number;\n /** per-key timeout when TTL is short enough to schedule */\n timeout?: NodeJS.Timeout;\n};\n\nconst MAX_TIMEOUT_MS = 2 ** 31 - 1; // ~24.8 days (Node setTimeout limit)\n\n@Provider({\n name: 'provider:cache:memory',\n description: 'Memory-based cache provider',\n scope: ProviderScope.GLOBAL,\n})\nexport default class CacheMemoryProvider implements CacheStoreInterface{\n private readonly memory = new Map<string, Entry>();\n private sweeper?: NodeJS.Timeout;\n\n constructor(sweepIntervalTTL = 60) {\n this.sweeper = setInterval(() => this.sweep(), sweepIntervalTTL * 1000);\n // don’t keep the process alive just for the sweeper (Node >=14)\n (this.sweeper as any).unref?.();\n }\n\n /** Set any value (auto-stringifies objects) */\n async setValue(key: string, value: any, ttlSeconds?: number): Promise<void> {\n const strValue = typeof value === 'string' ? value : JSON.stringify(value);\n\n // clear any previous timeout on this key\n const existing = this.memory.get(key);\n if (existing?.timeout) clearTimeout(existing.timeout);\n\n const entry: Entry = { value: strValue };\n\n if (ttlSeconds && ttlSeconds > 0) {\n const ttlMs = ttlSeconds * 1000;\n entry.expiresAt = Date.now() + ttlMs;\n\n // Only schedule a timer if within Node's setTimeout limit; otherwise rely on sweeper/lazy purge\n if (ttlMs <= MAX_TIMEOUT_MS) {\n entry.timeout = setTimeout(() => {\n // final check guards against clock drift or updates\n const e = this.memory.get(key);\n if (e && e.expiresAt && e.expiresAt <= Date.now()) {\n this.memory.delete(key);\n }\n }, ttlMs);\n (entry.timeout as any).unref?.();\n }\n }\n\n this.memory.set(key, entry);\n }\n\n /** Get a value and automatically parse JSON if possible */\n async getValue<T = any>(key: string, defaultValue?: T): Promise<T | undefined> {\n const entry = this.memory.get(key);\n if (!entry) return defaultValue;\n\n if (this.isExpired(entry)) {\n await this.delete(key);\n return defaultValue;\n }\n\n const raw = entry.value;\n try {\n return JSON.parse(raw) as T;\n } catch {\n // fallback for plain string values\n return raw as unknown as T;\n }\n }\n\n /** Delete a key */\n async delete(key: string): Promise<void> {\n const entry = this.memory.get(key);\n if (entry?.timeout) clearTimeout(entry.timeout);\n this.memory.delete(key);\n }\n\n /** Check if a key exists (and not expired) */\n async exists(key: string): Promise<boolean> {\n const entry = this.memory.get(key);\n if (!entry) return false;\n if (this.isExpired(entry)) {\n await this.delete(key);\n return false;\n }\n return true;\n }\n\n /** Gracefully close the provider */\n async close(): Promise<void> {\n if (this.sweeper) clearInterval(this.sweeper);\n for (const [, entry] of this.memory) {\n if (entry.timeout) clearTimeout(entry.timeout);\n }\n this.memory.clear();\n }\n\n // ---- internals ----\n\n private isExpired(entry: Entry): boolean {\n return entry.expiresAt !== undefined && entry.expiresAt <= Date.now();\n }\n\n /** Periodically remove expired keys to keep memory tidy */\n private sweep(): void {\n const now = Date.now();\n for (const [key, entry] of this.memory) {\n if (entry.expiresAt !== undefined && entry.expiresAt <= now) {\n if (entry.timeout) clearTimeout(entry.timeout);\n this.memory.delete(key);\n }\n }\n }\n}\n"]}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
|
|
5
|
-
const sdk_1 = require("@frontmcp/sdk");
|
|
6
|
-
let CacheRedisProvider = class CacheRedisProvider {
|
|
7
|
-
client;
|
|
8
|
-
constructor(options) {
|
|
9
|
-
if (options.type !== 'redis' && options.type !== 'redis-client') {
|
|
10
|
-
throw new Error('Invalid cache provider type');
|
|
11
|
-
}
|
|
12
|
-
if (options.type === 'redis-client') {
|
|
13
|
-
this.client = options.client;
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
this.client = new ioredis_1.default({
|
|
17
|
-
lazyConnect: false,
|
|
18
|
-
maxRetriesPerRequest: 3,
|
|
19
|
-
...options.config,
|
|
20
|
-
});
|
|
21
|
-
this.client.on('connect', () => console.log('[Redis] Connected'));
|
|
22
|
-
this.client.on('error', (err) => console.error('[Redis] Error:', err));
|
|
23
|
-
}
|
|
24
|
-
/** Set any value (auto-stringifies objects) */
|
|
25
|
-
async setValue(key, value, ttlSeconds) {
|
|
26
|
-
const strValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
27
|
-
if (ttlSeconds && ttlSeconds > 0) {
|
|
28
|
-
await this.client.set(key, strValue, 'EX', ttlSeconds);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
await this.client.set(key, strValue);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/** Get a value and automatically parse JSON if possible */
|
|
35
|
-
async getValue(key, defaultValue) {
|
|
36
|
-
const raw = await this.client.get(key);
|
|
37
|
-
if (raw === null)
|
|
38
|
-
return defaultValue;
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(raw);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// fallback for plain string values
|
|
44
|
-
return raw;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/** Delete a key */
|
|
48
|
-
async delete(key) {
|
|
49
|
-
await this.client.del(key);
|
|
50
|
-
}
|
|
51
|
-
/** Check if a key exists */
|
|
52
|
-
async exists(key) {
|
|
53
|
-
return (await this.client.exists(key)) === 1;
|
|
54
|
-
}
|
|
55
|
-
/** Gracefully close the Redis connection */
|
|
56
|
-
async close() {
|
|
57
|
-
await this.client.quit();
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
CacheRedisProvider = tslib_1.__decorate([
|
|
61
|
-
(0, sdk_1.Provider)({
|
|
62
|
-
name: 'provider:cache:redis',
|
|
63
|
-
description: 'Redis-based cache provider',
|
|
64
|
-
scope: sdk_1.ProviderScope.GLOBAL,
|
|
65
|
-
}),
|
|
66
|
-
tslib_1.__metadata("design:paramtypes", [Object])
|
|
67
|
-
], CacheRedisProvider);
|
|
68
|
-
exports.default = CacheRedisProvider;
|
|
69
|
-
//# sourceMappingURL=cache-redis.provider.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache-redis.provider.js","sourceRoot":"","sources":["../../../../src/cache/providers/cache-redis.provider.ts"],"names":[],"mappings":";;;AAAA,8DAAsD;AACtD,uCAAwD;AAQzC,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IACpB,MAAM,CAAc;IAErC,YAAY,OAA0B;QACpC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACpC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,iBAAK,CAAC;YACtB,WAAW,EAAE,KAAK;YAClB,oBAAoB,EAAE,CAAC;YACvB,GAAG,OAAO,CAAC,MAAM;SAClB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,+CAA+C;IAC/C,KAAK,CAAC,QAAQ,CAAC,GAAW,EAAE,KAAU,EAAE,UAAmB;QACzD,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3E,IAAI,UAAU,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QACzD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,QAAQ,CAAU,GAAW,EAAE,YAAgB;QACnD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,YAAY,CAAC;QAEtC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;YACnC,OAAO,GAAmB,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,4BAA4B;IAC5B,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED,4CAA4C;IAC5C,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AA3DoB,kBAAkB;IALtC,IAAA,cAAQ,EAAC;QACR,IAAI,EAAE,sBAAsB;QAC5B,WAAW,EAAE,4BAA4B;QACzC,KAAK,EAAE,mBAAa,CAAC,MAAM;KAC5B,CAAC;;GACmB,kBAAkB,CA2DtC;kBA3DoB,kBAAkB","sourcesContent":["import Redis, { Redis as RedisClient } from 'ioredis';\nimport { Provider, ProviderScope } from '@frontmcp/sdk';\nimport { CacheStoreInterface, RedisCacheOptions } from '../cache.types';\n\n@Provider({\n name: 'provider:cache:redis',\n description: 'Redis-based cache provider',\n scope: ProviderScope.GLOBAL,\n})\nexport default class CacheRedisProvider implements CacheStoreInterface {\n private readonly client: RedisClient;\n\n constructor(options: RedisCacheOptions) {\n if (options.type !== 'redis' && options.type !== 'redis-client') {\n throw new Error('Invalid cache provider type');\n }\n\n if (options.type === 'redis-client') {\n this.client = options.client;\n return;\n }\n this.client = new Redis({\n lazyConnect: false,\n maxRetriesPerRequest: 3,\n ...options.config,\n });\n\n this.client.on('connect', () => console.log('[Redis] Connected'));\n this.client.on('error', (err) => console.error('[Redis] Error:', err));\n }\n\n /** Set any value (auto-stringifies objects) */\n async setValue(key: string, value: any, ttlSeconds?: number): Promise<void> {\n const strValue = typeof value === 'string' ? value : JSON.stringify(value);\n if (ttlSeconds && ttlSeconds > 0) {\n await this.client.set(key, strValue, 'EX', ttlSeconds);\n } else {\n await this.client.set(key, strValue);\n }\n }\n\n /** Get a value and automatically parse JSON if possible */\n async getValue<T = any>(key: string, defaultValue?: T): Promise<T | undefined> {\n const raw = await this.client.get(key);\n if (raw === null) return defaultValue;\n\n try {\n return JSON.parse(raw) as T;\n } catch {\n // fallback for plain string values\n return raw as unknown as T;\n }\n }\n\n /** Delete a key */\n async delete(key: string): Promise<void> {\n await this.client.del(key);\n }\n\n /** Check if a key exists */\n async exists(key: string): Promise<boolean> {\n return (await this.client.exists(key)) === 1;\n }\n\n /** Gracefully close the Redis connection */\n async close(): Promise<void> {\n await this.client.quit();\n }\n}\n"]}
|