@frontmcp/edge 0.0.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.
- package/LICENSE +201 -0
- package/README.md +83 -0
- package/esm/index.mjs +4030 -0
- package/esm/package.json +63 -0
- package/index.d.ts +122 -0
- package/index.js +4011 -0
- package/kv-cache.d.ts +82 -0
- package/managed.d.ts +69 -0
- package/package.json +63 -0
- package/session-host.d.ts +41 -0
- package/skill-index-cache.d.ts +51 -0
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,4030 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __commonJS = (cb, mod) => function __require3() {
|
|
14
|
+
try {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw mod = 0, e;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
|
|
37
|
+
// libs/utils/dist/event-emitter/node-event-emitter.js
|
|
38
|
+
var require_node_event_emitter = __commonJS({
|
|
39
|
+
"libs/utils/dist/event-emitter/node-event-emitter.js"(exports, module) {
|
|
40
|
+
"use strict";
|
|
41
|
+
var __defProp3 = Object.defineProperty;
|
|
42
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
43
|
+
var __getOwnPropNames3 = Object.getOwnPropertyNames;
|
|
44
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
45
|
+
var __export2 = (target, all) => {
|
|
46
|
+
for (var name in all)
|
|
47
|
+
__defProp3(target, name, { get: all[name], enumerable: true });
|
|
48
|
+
};
|
|
49
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
50
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
51
|
+
for (let key of __getOwnPropNames3(from))
|
|
52
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
53
|
+
__defProp3(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
54
|
+
}
|
|
55
|
+
return to;
|
|
56
|
+
};
|
|
57
|
+
var __toCommonJS = (mod) => __copyProps2(__defProp3({}, "__esModule", { value: true }), mod);
|
|
58
|
+
var node_event_emitter_exports = {};
|
|
59
|
+
__export2(node_event_emitter_exports, {
|
|
60
|
+
EventEmitter: () => import_events.EventEmitter
|
|
61
|
+
});
|
|
62
|
+
module.exports = __toCommonJS(node_event_emitter_exports);
|
|
63
|
+
var import_events = __require("events");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// libs/utils/dist/crypto/node.js
|
|
68
|
+
var require_node = __commonJS({
|
|
69
|
+
"libs/utils/dist/crypto/node.js"(exports, module) {
|
|
70
|
+
"use strict";
|
|
71
|
+
var __create2 = Object.create;
|
|
72
|
+
var __defProp3 = Object.defineProperty;
|
|
73
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
74
|
+
var __getOwnPropNames3 = Object.getOwnPropertyNames;
|
|
75
|
+
var __getProtoOf2 = Object.getPrototypeOf;
|
|
76
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
77
|
+
var __export2 = (target, all) => {
|
|
78
|
+
for (var name in all)
|
|
79
|
+
__defProp3(target, name, { get: all[name], enumerable: true });
|
|
80
|
+
};
|
|
81
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
82
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
83
|
+
for (let key of __getOwnPropNames3(from))
|
|
84
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
85
|
+
__defProp3(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
86
|
+
}
|
|
87
|
+
return to;
|
|
88
|
+
};
|
|
89
|
+
var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
|
|
90
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
91
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
92
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
93
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
94
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp3(target, "default", { value: mod, enumerable: true }) : target,
|
|
95
|
+
mod
|
|
96
|
+
));
|
|
97
|
+
var __toCommonJS = (mod) => __copyProps2(__defProp3({}, "__esModule", { value: true }), mod);
|
|
98
|
+
var node_exports2 = {};
|
|
99
|
+
__export2(node_exports2, {
|
|
100
|
+
createSignedJwt: () => createSignedJwt2,
|
|
101
|
+
cryptoProvider: () => nodeCrypto2,
|
|
102
|
+
generateRsaKeyPair: () => generateRsaKeyPair2,
|
|
103
|
+
isRsaPssAlg: () => isRsaPssAlg2,
|
|
104
|
+
jwtAlgToNodeAlg: () => jwtAlgToNodeAlg2,
|
|
105
|
+
nodeCrypto: () => nodeCrypto2,
|
|
106
|
+
pemToPublicJwk: () => pemToPublicJwk2,
|
|
107
|
+
rsaSign: () => rsaSign2,
|
|
108
|
+
rsaSignBase64Url: () => rsaSignBase64Url2,
|
|
109
|
+
rsaVerify: () => rsaVerify2,
|
|
110
|
+
rsaVerifySync: () => rsaVerifySync2
|
|
111
|
+
});
|
|
112
|
+
module.exports = __toCommonJS(node_exports2);
|
|
113
|
+
var import_node_crypto2 = __toESM2(__require("node:crypto"));
|
|
114
|
+
var JWT_ALG_TO_NODE_DIGEST2 = {
|
|
115
|
+
RS256: "RSA-SHA256",
|
|
116
|
+
RS384: "RSA-SHA384",
|
|
117
|
+
RS512: "RSA-SHA512",
|
|
118
|
+
// For RSA-PSS, Node's crypto.sign/verify uses the digest algorithm + explicit PSS padding options.
|
|
119
|
+
PS256: "RSA-SHA256",
|
|
120
|
+
PS384: "RSA-SHA384",
|
|
121
|
+
PS512: "RSA-SHA512"
|
|
122
|
+
};
|
|
123
|
+
function jwtAlgToNodeAlg2(jwtAlg) {
|
|
124
|
+
const nodeAlg = JWT_ALG_TO_NODE_DIGEST2[jwtAlg];
|
|
125
|
+
if (!nodeAlg) {
|
|
126
|
+
throw new Error(`Unsupported JWT algorithm: ${jwtAlg}`);
|
|
127
|
+
}
|
|
128
|
+
return nodeAlg;
|
|
129
|
+
}
|
|
130
|
+
function isRsaPssAlg2(jwtAlg) {
|
|
131
|
+
return jwtAlg.startsWith("PS");
|
|
132
|
+
}
|
|
133
|
+
function toUint8Array2(buf) {
|
|
134
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
135
|
+
}
|
|
136
|
+
function toBuffer2(data) {
|
|
137
|
+
if (typeof data === "string") {
|
|
138
|
+
return Buffer.from(data, "utf8");
|
|
139
|
+
}
|
|
140
|
+
return Buffer.from(data);
|
|
141
|
+
}
|
|
142
|
+
var nodeCrypto2 = {
|
|
143
|
+
randomUUID() {
|
|
144
|
+
return import_node_crypto2.default.randomUUID();
|
|
145
|
+
},
|
|
146
|
+
randomBytes(length) {
|
|
147
|
+
return toUint8Array2(import_node_crypto2.default.randomBytes(length));
|
|
148
|
+
},
|
|
149
|
+
sha256(data) {
|
|
150
|
+
const hash = import_node_crypto2.default.createHash("sha256").update(toBuffer2(data)).digest();
|
|
151
|
+
return toUint8Array2(hash);
|
|
152
|
+
},
|
|
153
|
+
sha256Hex(data) {
|
|
154
|
+
return import_node_crypto2.default.createHash("sha256").update(toBuffer2(data)).digest("hex");
|
|
155
|
+
},
|
|
156
|
+
hmacSha256(key, data) {
|
|
157
|
+
const hmac2 = import_node_crypto2.default.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
|
|
158
|
+
return toUint8Array2(hmac2);
|
|
159
|
+
},
|
|
160
|
+
hkdfSha256(ikm, salt, info, length) {
|
|
161
|
+
const ikmBuf = Buffer.from(ikm);
|
|
162
|
+
const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
|
|
163
|
+
const prk = import_node_crypto2.default.createHmac("sha256", saltBuf).update(ikmBuf).digest();
|
|
164
|
+
const hashLen = 32;
|
|
165
|
+
const n = Math.ceil(length / hashLen);
|
|
166
|
+
const chunks = [];
|
|
167
|
+
let prev = Buffer.alloc(0);
|
|
168
|
+
for (let i = 1; i <= n; i++) {
|
|
169
|
+
prev = import_node_crypto2.default.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
|
|
170
|
+
chunks.push(prev);
|
|
171
|
+
}
|
|
172
|
+
return toUint8Array2(Buffer.concat(chunks).subarray(0, length));
|
|
173
|
+
},
|
|
174
|
+
encryptAesGcm(key, plaintext, iv) {
|
|
175
|
+
const cipher = import_node_crypto2.default.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
|
|
176
|
+
const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
|
|
177
|
+
const tag = cipher.getAuthTag();
|
|
178
|
+
return {
|
|
179
|
+
ciphertext: toUint8Array2(encrypted),
|
|
180
|
+
tag: toUint8Array2(tag)
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
decryptAesGcm(key, ciphertext, iv, tag) {
|
|
184
|
+
const decipher = import_node_crypto2.default.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
|
|
185
|
+
decipher.setAuthTag(Buffer.from(tag));
|
|
186
|
+
const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
|
|
187
|
+
return toUint8Array2(decrypted);
|
|
188
|
+
},
|
|
189
|
+
timingSafeEqual(a, b) {
|
|
190
|
+
if (a.length !== b.length) return false;
|
|
191
|
+
return import_node_crypto2.default.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function generateRsaKeyPair2(modulusLength = 2048, alg = "RS256") {
|
|
195
|
+
const kid = `rsa-key-${Date.now()}-${import_node_crypto2.default.randomBytes(8).toString("hex")}`;
|
|
196
|
+
const { privateKey, publicKey } = import_node_crypto2.default.generateKeyPairSync("rsa", {
|
|
197
|
+
modulusLength
|
|
198
|
+
});
|
|
199
|
+
const exported = publicKey.export({ format: "jwk" });
|
|
200
|
+
const publicJwk = {
|
|
201
|
+
...exported,
|
|
202
|
+
kid,
|
|
203
|
+
alg,
|
|
204
|
+
use: "sig",
|
|
205
|
+
kty: "RSA"
|
|
206
|
+
};
|
|
207
|
+
return { privateKey, publicKey, publicJwk };
|
|
208
|
+
}
|
|
209
|
+
function rsaSign2(algorithm, data, privateKey, options) {
|
|
210
|
+
const signingKey = options ? { key: privateKey, ...options } : privateKey;
|
|
211
|
+
return import_node_crypto2.default.sign(algorithm, data, signingKey);
|
|
212
|
+
}
|
|
213
|
+
function rsaVerify2(jwtAlg, data, publicJwk, signature) {
|
|
214
|
+
const publicKey = import_node_crypto2.default.createPublicKey({ key: publicJwk, format: "jwk" });
|
|
215
|
+
const nodeAlgorithm = jwtAlgToNodeAlg2(jwtAlg);
|
|
216
|
+
const verifyKey = isRsaPssAlg2(jwtAlg) ? {
|
|
217
|
+
key: publicKey,
|
|
218
|
+
padding: import_node_crypto2.default.constants.RSA_PKCS1_PSS_PADDING,
|
|
219
|
+
saltLength: import_node_crypto2.default.constants.RSA_PSS_SALTLEN_DIGEST
|
|
220
|
+
} : publicKey;
|
|
221
|
+
return import_node_crypto2.default.verify(nodeAlgorithm, data, verifyKey, signature);
|
|
222
|
+
}
|
|
223
|
+
function rsaSignBase64Url2(jwtAlg, data, privateJwk) {
|
|
224
|
+
const privateKey = import_node_crypto2.default.createPrivateKey({ key: privateJwk, format: "jwk" });
|
|
225
|
+
const nodeAlgorithm = jwtAlgToNodeAlg2(jwtAlg);
|
|
226
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
227
|
+
const sig = isRsaPssAlg2(jwtAlg) ? rsaSign2(nodeAlgorithm, buf, privateKey, {
|
|
228
|
+
padding: import_node_crypto2.default.constants.RSA_PKCS1_PSS_PADDING,
|
|
229
|
+
saltLength: import_node_crypto2.default.constants.RSA_PSS_SALTLEN_DIGEST
|
|
230
|
+
}) : rsaSign2(nodeAlgorithm, buf, privateKey);
|
|
231
|
+
return sig.toString("base64url");
|
|
232
|
+
}
|
|
233
|
+
function rsaVerifySync2(jwtAlg, data, publicJwk, signature) {
|
|
234
|
+
try {
|
|
235
|
+
const publicKey = import_node_crypto2.default.createPublicKey({ key: publicJwk, format: "jwk" });
|
|
236
|
+
const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
237
|
+
const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
|
|
238
|
+
if (jwtAlg === "EdDSA") {
|
|
239
|
+
return import_node_crypto2.default.verify(null, dataBuf, publicKey, sigBuf);
|
|
240
|
+
}
|
|
241
|
+
const nodeAlgorithm = jwtAlgToNodeAlg2(jwtAlg);
|
|
242
|
+
const verifyKey = isRsaPssAlg2(jwtAlg) ? {
|
|
243
|
+
key: publicKey,
|
|
244
|
+
padding: import_node_crypto2.default.constants.RSA_PKCS1_PSS_PADDING,
|
|
245
|
+
saltLength: import_node_crypto2.default.constants.RSA_PSS_SALTLEN_DIGEST
|
|
246
|
+
} : publicKey;
|
|
247
|
+
return import_node_crypto2.default.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function pemToPublicJwk2(pem) {
|
|
253
|
+
const key = import_node_crypto2.default.createPublicKey({ key: pem, format: "pem" });
|
|
254
|
+
return key.export({ format: "jwk" });
|
|
255
|
+
}
|
|
256
|
+
function createSignedJwt2(payload, privateKey, kid, alg = "RS256") {
|
|
257
|
+
const header = { alg, typ: "JWT", kid };
|
|
258
|
+
const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
259
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
260
|
+
const signatureInput = `${headerB64}.${payloadB64}`;
|
|
261
|
+
const nodeAlgorithm = jwtAlgToNodeAlg2(alg);
|
|
262
|
+
const signature = rsaSign2(
|
|
263
|
+
nodeAlgorithm,
|
|
264
|
+
Buffer.from(signatureInput),
|
|
265
|
+
privateKey,
|
|
266
|
+
isRsaPssAlg2(alg) ? {
|
|
267
|
+
padding: import_node_crypto2.default.constants.RSA_PKCS1_PSS_PADDING,
|
|
268
|
+
saltLength: import_node_crypto2.default.constants.RSA_PSS_SALTLEN_DIGEST
|
|
269
|
+
} : void 0
|
|
270
|
+
);
|
|
271
|
+
const signatureB64 = signature.toString("base64url");
|
|
272
|
+
return `${headerB64}.${payloadB64}.${signatureB64}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// libs/edge/src/index.ts
|
|
278
|
+
import { FrontMcpInstance, createWebFetchHandler } from "@frontmcp/sdk";
|
|
279
|
+
|
|
280
|
+
// libs/edge/src/managed.ts
|
|
281
|
+
function buildManagedOpenApiPluginOptions(managed) {
|
|
282
|
+
const source = {
|
|
283
|
+
type: "saas",
|
|
284
|
+
endpoint: managed.endpoint,
|
|
285
|
+
authToken: managed.authToken,
|
|
286
|
+
expectedAudience: managed.expectedAudience,
|
|
287
|
+
jwksUrl: managed.jwksUrl,
|
|
288
|
+
expectedIssuer: managed.expectedIssuer
|
|
289
|
+
};
|
|
290
|
+
if (managed.pollIntervalMs !== void 0) source["pollIntervalMs"] = managed.pollIntervalMs;
|
|
291
|
+
if (managed.enableWebhook !== void 0) source["enableWebhook"] = managed.enableWebhook;
|
|
292
|
+
const options = { source };
|
|
293
|
+
if (managed.requireSignature !== void 0) options["requireSignature"] = managed.requireSignature;
|
|
294
|
+
if (managed.trustedKeys !== void 0) options["trustedKeys"] = managed.trustedKeys;
|
|
295
|
+
if (managed.dev !== void 0) options["dev"] = managed.dev;
|
|
296
|
+
if (managed.credentials !== void 0) options["credentials"] = managed.credentials;
|
|
297
|
+
if (managed.outbound !== void 0) options["outbound"] = managed.outbound;
|
|
298
|
+
if (managed.bundleCacheDir !== void 0) options["bundleCacheDir"] = managed.bundleCacheDir;
|
|
299
|
+
return options;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// libs/edge/src/session-host.ts
|
|
303
|
+
import {
|
|
304
|
+
buildPersistentWebStandardMcp,
|
|
305
|
+
runHttpRequestFlowWeb
|
|
306
|
+
} from "@frontmcp/sdk";
|
|
307
|
+
|
|
308
|
+
// libs/utils/dist/esm/index.mjs
|
|
309
|
+
import { createRequire as __frontmcpCreateRequire } from "module";
|
|
310
|
+
|
|
311
|
+
// libs/lazy-zod/dist/esm/index.mjs
|
|
312
|
+
import { z as realZ } from "zod";
|
|
313
|
+
import { z as z2 } from "zod";
|
|
314
|
+
import { toJSONSchema } from "zod";
|
|
315
|
+
import {
|
|
316
|
+
NEVER,
|
|
317
|
+
ZodAny,
|
|
318
|
+
ZodArray,
|
|
319
|
+
ZodBigInt,
|
|
320
|
+
ZodBoolean,
|
|
321
|
+
ZodDate,
|
|
322
|
+
ZodDefault,
|
|
323
|
+
ZodDiscriminatedUnion,
|
|
324
|
+
ZodEnum,
|
|
325
|
+
ZodError,
|
|
326
|
+
ZodIntersection,
|
|
327
|
+
ZodLiteral,
|
|
328
|
+
ZodNever,
|
|
329
|
+
ZodNull,
|
|
330
|
+
ZodNullable,
|
|
331
|
+
ZodNumber,
|
|
332
|
+
ZodObject,
|
|
333
|
+
ZodOptional,
|
|
334
|
+
ZodRecord,
|
|
335
|
+
ZodString,
|
|
336
|
+
ZodSymbol,
|
|
337
|
+
ZodTuple,
|
|
338
|
+
ZodType,
|
|
339
|
+
ZodUndefined,
|
|
340
|
+
ZodUnion,
|
|
341
|
+
ZodUnknown,
|
|
342
|
+
ZodVoid
|
|
343
|
+
} from "zod";
|
|
344
|
+
var HEAVY_FACTORIES = /* @__PURE__ */ new Set([
|
|
345
|
+
"object",
|
|
346
|
+
"strictObject",
|
|
347
|
+
"looseObject",
|
|
348
|
+
"union",
|
|
349
|
+
"discriminatedUnion",
|
|
350
|
+
"intersection",
|
|
351
|
+
"record",
|
|
352
|
+
"tuple"
|
|
353
|
+
]);
|
|
354
|
+
var CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
355
|
+
// ZodType generic
|
|
356
|
+
"optional",
|
|
357
|
+
"nullable",
|
|
358
|
+
"nullish",
|
|
359
|
+
"default",
|
|
360
|
+
"prefault",
|
|
361
|
+
"describe",
|
|
362
|
+
"refine",
|
|
363
|
+
"superRefine",
|
|
364
|
+
"transform",
|
|
365
|
+
"pipe",
|
|
366
|
+
"or",
|
|
367
|
+
"and",
|
|
368
|
+
"readonly",
|
|
369
|
+
"brand",
|
|
370
|
+
"catch",
|
|
371
|
+
"array",
|
|
372
|
+
"promise",
|
|
373
|
+
// ZodString
|
|
374
|
+
"email",
|
|
375
|
+
"url",
|
|
376
|
+
"uuid",
|
|
377
|
+
"cuid",
|
|
378
|
+
"cuid2",
|
|
379
|
+
"ulid",
|
|
380
|
+
"regex",
|
|
381
|
+
"startsWith",
|
|
382
|
+
"endsWith",
|
|
383
|
+
"trim",
|
|
384
|
+
"toLowerCase",
|
|
385
|
+
"toUpperCase",
|
|
386
|
+
"datetime",
|
|
387
|
+
"date",
|
|
388
|
+
"time",
|
|
389
|
+
"ip",
|
|
390
|
+
"cidr",
|
|
391
|
+
"base64",
|
|
392
|
+
"base64url",
|
|
393
|
+
"jwt",
|
|
394
|
+
"nanoid",
|
|
395
|
+
"emoji",
|
|
396
|
+
"includes",
|
|
397
|
+
"normalize",
|
|
398
|
+
// ZodString / ZodNumber / ZodArray — shared (`Set` dedupes)
|
|
399
|
+
"min",
|
|
400
|
+
"max",
|
|
401
|
+
"length",
|
|
402
|
+
"gt",
|
|
403
|
+
"gte",
|
|
404
|
+
"lt",
|
|
405
|
+
"lte",
|
|
406
|
+
// ZodNumber
|
|
407
|
+
"int",
|
|
408
|
+
"positive",
|
|
409
|
+
"negative",
|
|
410
|
+
"nonnegative",
|
|
411
|
+
"nonpositive",
|
|
412
|
+
"finite",
|
|
413
|
+
"safe",
|
|
414
|
+
"multipleOf",
|
|
415
|
+
"step",
|
|
416
|
+
// ZodObject
|
|
417
|
+
"extend",
|
|
418
|
+
"merge",
|
|
419
|
+
"pick",
|
|
420
|
+
"omit",
|
|
421
|
+
"partial",
|
|
422
|
+
"deepPartial",
|
|
423
|
+
"required",
|
|
424
|
+
"passthrough",
|
|
425
|
+
"strict",
|
|
426
|
+
"strip",
|
|
427
|
+
"catchall",
|
|
428
|
+
"keyof",
|
|
429
|
+
// ZodArray
|
|
430
|
+
"nonempty",
|
|
431
|
+
"element",
|
|
432
|
+
// ZodEnum
|
|
433
|
+
"extract",
|
|
434
|
+
"exclude"
|
|
435
|
+
]);
|
|
436
|
+
var LAZY_BRAND = /* @__PURE__ */ Symbol.for("@frontmcp/lazy-zod.LazyZodSchema");
|
|
437
|
+
var LAZY_TARGET = /* @__PURE__ */ Symbol.for("@frontmcp/lazy-zod.LazyZodSchema.target");
|
|
438
|
+
var HOT_PATH_METHODS = /* @__PURE__ */ new Set(["parse", "safeParse", "parseAsync", "safeParseAsync"]);
|
|
439
|
+
var LazyZodSchema = class {
|
|
440
|
+
constructor(_materialize) {
|
|
441
|
+
this._materialize = _materialize;
|
|
442
|
+
Object.defineProperty(this, LAZY_BRAND, {
|
|
443
|
+
value: true,
|
|
444
|
+
enumerable: false,
|
|
445
|
+
writable: false,
|
|
446
|
+
configurable: false
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
_materialize;
|
|
450
|
+
_resolved;
|
|
451
|
+
/** Materialize (and memoize) the underlying real zod schema. */
|
|
452
|
+
materialize() {
|
|
453
|
+
return this._resolved ?? (this._resolved = this._materialize());
|
|
454
|
+
}
|
|
455
|
+
/** True once the underlying schema has been constructed. */
|
|
456
|
+
get isMaterialized() {
|
|
457
|
+
return this._resolved !== void 0;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
function wrapLazy(schema) {
|
|
461
|
+
const proxy = new Proxy(schema, {
|
|
462
|
+
get(target, key, _receiver) {
|
|
463
|
+
if (key === LAZY_BRAND) return true;
|
|
464
|
+
if (key === LAZY_TARGET) return target;
|
|
465
|
+
if (typeof key !== "symbol" && Object.prototype.hasOwnProperty.call(target, key)) {
|
|
466
|
+
return target[key];
|
|
467
|
+
}
|
|
468
|
+
if (typeof key === "string" && HOT_PATH_METHODS.has(key)) {
|
|
469
|
+
return (...args) => {
|
|
470
|
+
const real2 = target.materialize();
|
|
471
|
+
const bound = real2[key].bind(real2);
|
|
472
|
+
Object.defineProperty(target, key, {
|
|
473
|
+
value: bound,
|
|
474
|
+
writable: true,
|
|
475
|
+
configurable: true,
|
|
476
|
+
enumerable: false
|
|
477
|
+
});
|
|
478
|
+
return bound(...args);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
if (typeof key === "string" && CHAIN_METHODS.has(key)) {
|
|
482
|
+
return (...args) => wrapLazy(
|
|
483
|
+
new LazyZodSchema(() => {
|
|
484
|
+
const real2 = target.materialize();
|
|
485
|
+
return real2[key](...args);
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const real = target.materialize();
|
|
490
|
+
const val = real[key];
|
|
491
|
+
if (typeof val === "function") {
|
|
492
|
+
if (key === "constructor") return val;
|
|
493
|
+
const maybeCtor = val;
|
|
494
|
+
if (typeof maybeCtor.prototype === "object" && maybeCtor.prototype !== null && Object.getOwnPropertyNames(maybeCtor.prototype).length > 1) {
|
|
495
|
+
return val;
|
|
496
|
+
}
|
|
497
|
+
return val.bind(real);
|
|
498
|
+
}
|
|
499
|
+
return val;
|
|
500
|
+
},
|
|
501
|
+
has(target, key) {
|
|
502
|
+
if (key === LAZY_BRAND) return true;
|
|
503
|
+
if (typeof key === "string" && (HOT_PATH_METHODS.has(key) || CHAIN_METHODS.has(key))) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
return Reflect.has(target.materialize(), key);
|
|
507
|
+
},
|
|
508
|
+
getPrototypeOf(target) {
|
|
509
|
+
return Object.getPrototypeOf(target.materialize());
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
return proxy;
|
|
513
|
+
}
|
|
514
|
+
var lazyProxy = new Proxy(realZ, {
|
|
515
|
+
get(target, key, receiver) {
|
|
516
|
+
if (typeof key === "string" && HEAVY_FACTORIES.has(key)) {
|
|
517
|
+
const realFactory = target[key];
|
|
518
|
+
return (...args) => wrapLazy(new LazyZodSchema(() => realFactory.call(target, ...args)));
|
|
519
|
+
}
|
|
520
|
+
return Reflect.get(target, key, receiver);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
var z = lazyProxy;
|
|
524
|
+
|
|
525
|
+
// libs/utils/dist/esm/index.mjs
|
|
526
|
+
var import_event_emitter = __toESM(require_node_event_emitter(), 1);
|
|
527
|
+
var import_crypto_provider = __toESM(require_node(), 1);
|
|
528
|
+
import crypto2 from "node:crypto";
|
|
529
|
+
import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
|
|
530
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
531
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
532
|
+
import { randomBytes as nobleRandomBytes } from "@noble/hashes/utils.js";
|
|
533
|
+
import { gcm } from "@noble/ciphers/aes.js";
|
|
534
|
+
import { analyzeForReDoS, REDOS_THRESHOLDS } from "@enclave-vm/ast";
|
|
535
|
+
var require2 = __frontmcpCreateRequire(import.meta.url || "file:///");
|
|
536
|
+
var __defProp2 = Object.defineProperty;
|
|
537
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
538
|
+
var __require2 = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? require2 : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
539
|
+
get: (a, b) => (typeof require2 !== "undefined" ? require2 : a)[b]
|
|
540
|
+
}) : x)(function(x) {
|
|
541
|
+
if (typeof require2 !== "undefined") return require2.apply(this, arguments);
|
|
542
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
543
|
+
});
|
|
544
|
+
var __esm = (fn, res, err) => function __init() {
|
|
545
|
+
if (err) throw err[0];
|
|
546
|
+
try {
|
|
547
|
+
return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
|
|
548
|
+
} catch (e) {
|
|
549
|
+
throw err = [e], e;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
var __export = (target, all) => {
|
|
553
|
+
for (var name in all)
|
|
554
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
555
|
+
};
|
|
556
|
+
function isNode() {
|
|
557
|
+
return typeof process !== "undefined" && !!process.versions?.node;
|
|
558
|
+
}
|
|
559
|
+
function assertNode(feature) {
|
|
560
|
+
if (!isNode()) {
|
|
561
|
+
throw new Error(`${feature} is not supported in the browser. Requires Node.js.`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
var init_runtime = __esm({
|
|
565
|
+
"libs/utils/src/crypto/runtime.ts"() {
|
|
566
|
+
"use strict";
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
function getFsp() {
|
|
570
|
+
if (!_fsp) {
|
|
571
|
+
assertNode("File system operations");
|
|
572
|
+
_fsp = __require2("fs").promises;
|
|
573
|
+
}
|
|
574
|
+
return _fsp;
|
|
575
|
+
}
|
|
576
|
+
async function writeFile(p, content, options) {
|
|
577
|
+
const fsp = getFsp();
|
|
578
|
+
await fsp.writeFile(p, content, { encoding: "utf8", mode: options?.mode });
|
|
579
|
+
}
|
|
580
|
+
async function mkdir(p, options) {
|
|
581
|
+
const fsp = getFsp();
|
|
582
|
+
await fsp.mkdir(p, options);
|
|
583
|
+
}
|
|
584
|
+
async function rename(oldPath, newPath) {
|
|
585
|
+
const fsp = getFsp();
|
|
586
|
+
await fsp.rename(oldPath, newPath);
|
|
587
|
+
}
|
|
588
|
+
async function unlink(p) {
|
|
589
|
+
const fsp = getFsp();
|
|
590
|
+
await fsp.unlink(p);
|
|
591
|
+
}
|
|
592
|
+
async function fileExists(p) {
|
|
593
|
+
try {
|
|
594
|
+
const fsp = getFsp();
|
|
595
|
+
await fsp.access(p, F_OK);
|
|
596
|
+
return true;
|
|
597
|
+
} catch {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function readJSON(jsonPath) {
|
|
602
|
+
try {
|
|
603
|
+
const fsp = getFsp();
|
|
604
|
+
const buf = await fsp.readFile(jsonPath, "utf8");
|
|
605
|
+
return JSON.parse(buf);
|
|
606
|
+
} catch {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function ensureDir(p) {
|
|
611
|
+
const fsp = getFsp();
|
|
612
|
+
await fsp.mkdir(p, { recursive: true });
|
|
613
|
+
}
|
|
614
|
+
async function readdir(p) {
|
|
615
|
+
const fsp = getFsp();
|
|
616
|
+
return fsp.readdir(p);
|
|
617
|
+
}
|
|
618
|
+
var _fsp;
|
|
619
|
+
var _fs;
|
|
620
|
+
var _spawn;
|
|
621
|
+
var F_OK;
|
|
622
|
+
var init_fs = __esm({
|
|
623
|
+
"libs/utils/src/fs/fs.ts"() {
|
|
624
|
+
"use strict";
|
|
625
|
+
init_runtime();
|
|
626
|
+
_fsp = null;
|
|
627
|
+
_fs = null;
|
|
628
|
+
_spawn = null;
|
|
629
|
+
F_OK = 0;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
var init_fs2 = __esm({
|
|
633
|
+
"libs/utils/src/fs/index.ts"() {
|
|
634
|
+
"use strict";
|
|
635
|
+
init_fs();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
function jwtAlgToNodeAlg(jwtAlg) {
|
|
639
|
+
const nodeAlg = JWT_ALG_TO_NODE_DIGEST[jwtAlg];
|
|
640
|
+
if (!nodeAlg) {
|
|
641
|
+
throw new Error(`Unsupported JWT algorithm: ${jwtAlg}`);
|
|
642
|
+
}
|
|
643
|
+
return nodeAlg;
|
|
644
|
+
}
|
|
645
|
+
function isRsaPssAlg(jwtAlg) {
|
|
646
|
+
return jwtAlg.startsWith("PS");
|
|
647
|
+
}
|
|
648
|
+
function jwtAlgToWebCryptoAlg(jwtAlg) {
|
|
649
|
+
const webAlg = JWT_ALG_TO_WEB_CRYPTO[jwtAlg];
|
|
650
|
+
if (!webAlg) {
|
|
651
|
+
throw new Error(`Unsupported JWT algorithm for WebCrypto: ${jwtAlg}`);
|
|
652
|
+
}
|
|
653
|
+
return webAlg;
|
|
654
|
+
}
|
|
655
|
+
var JWT_ALG_TO_NODE_DIGEST;
|
|
656
|
+
var JWT_ALG_TO_WEB_CRYPTO;
|
|
657
|
+
var init_jwt_alg = __esm({
|
|
658
|
+
"libs/utils/src/crypto/jwt-alg.ts"() {
|
|
659
|
+
"use strict";
|
|
660
|
+
JWT_ALG_TO_NODE_DIGEST = {
|
|
661
|
+
RS256: "RSA-SHA256",
|
|
662
|
+
RS384: "RSA-SHA384",
|
|
663
|
+
RS512: "RSA-SHA512",
|
|
664
|
+
// For RSA-PSS, Node's crypto.sign/verify uses the digest algorithm + explicit PSS padding options.
|
|
665
|
+
PS256: "RSA-SHA256",
|
|
666
|
+
PS384: "RSA-SHA384",
|
|
667
|
+
PS512: "RSA-SHA512"
|
|
668
|
+
};
|
|
669
|
+
JWT_ALG_TO_WEB_CRYPTO = {
|
|
670
|
+
RS256: "SHA-256",
|
|
671
|
+
RS384: "SHA-384",
|
|
672
|
+
RS512: "SHA-512",
|
|
673
|
+
PS256: "SHA-256",
|
|
674
|
+
PS384: "SHA-384",
|
|
675
|
+
PS512: "SHA-512"
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
var textEncoder;
|
|
680
|
+
var textDecoder;
|
|
681
|
+
var EncryptedBlobError;
|
|
682
|
+
var init_encrypted_blob = __esm({
|
|
683
|
+
"libs/utils/src/crypto/encrypted-blob.ts"() {
|
|
684
|
+
"use strict";
|
|
685
|
+
init_crypto();
|
|
686
|
+
textEncoder = new TextEncoder();
|
|
687
|
+
textDecoder = new TextDecoder();
|
|
688
|
+
EncryptedBlobError = class extends Error {
|
|
689
|
+
constructor(message) {
|
|
690
|
+
super(message);
|
|
691
|
+
this.name = "EncryptedBlobError";
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
var MIN_CODE_VERIFIER_LENGTH;
|
|
697
|
+
var MAX_CODE_VERIFIER_LENGTH;
|
|
698
|
+
var DEFAULT_CODE_VERIFIER_LENGTH;
|
|
699
|
+
var PkceError;
|
|
700
|
+
var init_pkce = __esm({
|
|
701
|
+
"libs/utils/src/crypto/pkce/pkce.ts"() {
|
|
702
|
+
"use strict";
|
|
703
|
+
init_crypto();
|
|
704
|
+
MIN_CODE_VERIFIER_LENGTH = 43;
|
|
705
|
+
MAX_CODE_VERIFIER_LENGTH = 128;
|
|
706
|
+
DEFAULT_CODE_VERIFIER_LENGTH = 64;
|
|
707
|
+
PkceError = class extends Error {
|
|
708
|
+
constructor(message) {
|
|
709
|
+
super(message);
|
|
710
|
+
this.name = "PkceError";
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
var init_pkce2 = __esm({
|
|
716
|
+
"libs/utils/src/crypto/pkce/index.ts"() {
|
|
717
|
+
"use strict";
|
|
718
|
+
init_pkce();
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
var secretDataSchema;
|
|
722
|
+
var MAX_CLOCK_DRIFT_MS;
|
|
723
|
+
var MAX_SECRET_AGE_MS;
|
|
724
|
+
var init_schema = __esm({
|
|
725
|
+
"libs/utils/src/crypto/secret-persistence/schema.ts"() {
|
|
726
|
+
"use strict";
|
|
727
|
+
secretDataSchema = z.object({
|
|
728
|
+
secret: z.string().base64url().length(43),
|
|
729
|
+
// base64url of 32 bytes is exactly 43 chars
|
|
730
|
+
createdAt: z.number().positive().int(),
|
|
731
|
+
version: z.number().positive().int()
|
|
732
|
+
}).strict();
|
|
733
|
+
MAX_CLOCK_DRIFT_MS = 6e4;
|
|
734
|
+
MAX_SECRET_AGE_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
var DEFAULT_SECRET_BYTES;
|
|
738
|
+
var DEFAULT_SECRET_DIR;
|
|
739
|
+
var noop;
|
|
740
|
+
var secretCache;
|
|
741
|
+
var generationPromises;
|
|
742
|
+
var init_persistence = __esm({
|
|
743
|
+
"libs/utils/src/crypto/secret-persistence/persistence.ts"() {
|
|
744
|
+
"use strict";
|
|
745
|
+
init_fs2();
|
|
746
|
+
init_crypto();
|
|
747
|
+
init_schema();
|
|
748
|
+
DEFAULT_SECRET_BYTES = 32;
|
|
749
|
+
DEFAULT_SECRET_DIR = ".frontmcp";
|
|
750
|
+
noop = () => {
|
|
751
|
+
};
|
|
752
|
+
secretCache = /* @__PURE__ */ new Map();
|
|
753
|
+
generationPromises = /* @__PURE__ */ new Map();
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
var init_secret_persistence = __esm({
|
|
757
|
+
"libs/utils/src/crypto/secret-persistence/index.ts"() {
|
|
758
|
+
"use strict";
|
|
759
|
+
init_schema();
|
|
760
|
+
init_persistence();
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
var init_hmac_signing = __esm({
|
|
764
|
+
"libs/utils/src/crypto/hmac-signing.ts"() {
|
|
765
|
+
"use strict";
|
|
766
|
+
init_crypto();
|
|
767
|
+
init_crypto();
|
|
768
|
+
init_crypto();
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
function validateKeyData(data) {
|
|
772
|
+
const result = anyKeyDataSchema.safeParse(data);
|
|
773
|
+
if (!result.success) {
|
|
774
|
+
return {
|
|
775
|
+
valid: false,
|
|
776
|
+
error: result.error.issues[0]?.message ?? "Invalid key structure"
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const parsed = result.data;
|
|
780
|
+
const now = Date.now();
|
|
781
|
+
if (parsed.createdAt > now + MAX_CLOCK_DRIFT_MS2) {
|
|
782
|
+
return { valid: false, error: "createdAt is in the future" };
|
|
783
|
+
}
|
|
784
|
+
if (parsed.createdAt < now - MAX_KEY_AGE_MS) {
|
|
785
|
+
return { valid: false, error: "createdAt is too old" };
|
|
786
|
+
}
|
|
787
|
+
return { valid: true, data: parsed };
|
|
788
|
+
}
|
|
789
|
+
function isSecretKeyData(data) {
|
|
790
|
+
return data.type === "secret";
|
|
791
|
+
}
|
|
792
|
+
function isAsymmetricKeyData(data) {
|
|
793
|
+
return data.type === "asymmetric";
|
|
794
|
+
}
|
|
795
|
+
var MAX_CLOCK_DRIFT_MS2;
|
|
796
|
+
var MAX_KEY_AGE_MS;
|
|
797
|
+
var asymmetricAlgSchema;
|
|
798
|
+
var baseKeyDataSchema;
|
|
799
|
+
var jsonWebKeySchema;
|
|
800
|
+
var secretKeyDataSchema;
|
|
801
|
+
var asymmetricKeyDataSchema;
|
|
802
|
+
var anyKeyDataSchema;
|
|
803
|
+
var init_schemas = __esm({
|
|
804
|
+
"libs/utils/src/crypto/key-persistence/schemas.ts"() {
|
|
805
|
+
"use strict";
|
|
806
|
+
MAX_CLOCK_DRIFT_MS2 = 6e4;
|
|
807
|
+
MAX_KEY_AGE_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
808
|
+
asymmetricAlgSchema = z.enum(["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]);
|
|
809
|
+
baseKeyDataSchema = z.object({
|
|
810
|
+
kid: z.string().min(1),
|
|
811
|
+
createdAt: z.number().positive().int(),
|
|
812
|
+
version: z.number().positive().int()
|
|
813
|
+
});
|
|
814
|
+
jsonWebKeySchema = z.object({
|
|
815
|
+
kty: z.string().optional(),
|
|
816
|
+
use: z.string().optional(),
|
|
817
|
+
alg: z.string().optional(),
|
|
818
|
+
kid: z.string().optional(),
|
|
819
|
+
n: z.string().optional(),
|
|
820
|
+
e: z.string().optional(),
|
|
821
|
+
d: z.string().optional(),
|
|
822
|
+
p: z.string().optional(),
|
|
823
|
+
q: z.string().optional(),
|
|
824
|
+
dp: z.string().optional(),
|
|
825
|
+
dq: z.string().optional(),
|
|
826
|
+
qi: z.string().optional(),
|
|
827
|
+
x: z.string().optional(),
|
|
828
|
+
y: z.string().optional(),
|
|
829
|
+
crv: z.string().optional()
|
|
830
|
+
}).passthrough();
|
|
831
|
+
secretKeyDataSchema = baseKeyDataSchema.extend({
|
|
832
|
+
type: z.literal("secret"),
|
|
833
|
+
secret: z.string().min(1),
|
|
834
|
+
bytes: z.number().positive().int()
|
|
835
|
+
});
|
|
836
|
+
asymmetricKeyDataSchema = baseKeyDataSchema.extend({
|
|
837
|
+
type: z.literal("asymmetric"),
|
|
838
|
+
alg: asymmetricAlgSchema,
|
|
839
|
+
privateKey: jsonWebKeySchema,
|
|
840
|
+
publicJwk: z.object({
|
|
841
|
+
keys: z.array(jsonWebKeySchema).min(1)
|
|
842
|
+
})
|
|
843
|
+
});
|
|
844
|
+
anyKeyDataSchema = z.discriminatedUnion("type", [secretKeyDataSchema, asymmetricKeyDataSchema]);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
var KeyPersistence;
|
|
848
|
+
var init_key_persistence = __esm({
|
|
849
|
+
"libs/utils/src/crypto/key-persistence/key-persistence.ts"() {
|
|
850
|
+
"use strict";
|
|
851
|
+
init_crypto();
|
|
852
|
+
init_schemas();
|
|
853
|
+
KeyPersistence = class {
|
|
854
|
+
storage;
|
|
855
|
+
cache = /* @__PURE__ */ new Map();
|
|
856
|
+
enableCache;
|
|
857
|
+
throwOnInvalid;
|
|
858
|
+
constructor(options) {
|
|
859
|
+
this.storage = options.storage;
|
|
860
|
+
this.enableCache = options.enableCache ?? true;
|
|
861
|
+
this.throwOnInvalid = options.throwOnInvalid ?? false;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get a key by ID.
|
|
865
|
+
*
|
|
866
|
+
* @param kid - Key identifier
|
|
867
|
+
* @returns Key data or null if not found
|
|
868
|
+
*/
|
|
869
|
+
async get(kid) {
|
|
870
|
+
if (this.enableCache) {
|
|
871
|
+
const cached4 = this.cache.get(kid);
|
|
872
|
+
if (cached4) return cached4;
|
|
873
|
+
}
|
|
874
|
+
const raw = await this.storage.get(kid);
|
|
875
|
+
if (!raw) return null;
|
|
876
|
+
let parsed;
|
|
877
|
+
try {
|
|
878
|
+
parsed = JSON.parse(raw);
|
|
879
|
+
} catch {
|
|
880
|
+
if (this.throwOnInvalid) {
|
|
881
|
+
throw new Error(`Invalid JSON for key "${kid}"`);
|
|
882
|
+
}
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
const validation = validateKeyData(parsed);
|
|
886
|
+
if (!validation.valid) {
|
|
887
|
+
if (this.throwOnInvalid) {
|
|
888
|
+
throw new Error(`Invalid key data for "${kid}": ${validation.error}`);
|
|
889
|
+
}
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const key = validation.data;
|
|
893
|
+
if (this.enableCache) {
|
|
894
|
+
this.cache.set(kid, key);
|
|
895
|
+
}
|
|
896
|
+
return key;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Get a secret key by ID.
|
|
900
|
+
*
|
|
901
|
+
* @param kid - Key identifier
|
|
902
|
+
* @returns Secret key data or null if not found or wrong type
|
|
903
|
+
*/
|
|
904
|
+
async getSecret(kid) {
|
|
905
|
+
const key = await this.get(kid);
|
|
906
|
+
if (!key || !isSecretKeyData(key)) return null;
|
|
907
|
+
return key;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Get an asymmetric key by ID.
|
|
911
|
+
*
|
|
912
|
+
* @param kid - Key identifier
|
|
913
|
+
* @returns Asymmetric key data or null if not found or wrong type
|
|
914
|
+
*/
|
|
915
|
+
async getAsymmetric(kid) {
|
|
916
|
+
const key = await this.get(kid);
|
|
917
|
+
if (!key || !isAsymmetricKeyData(key)) return null;
|
|
918
|
+
return key;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Store a key.
|
|
922
|
+
*
|
|
923
|
+
* @param key - Key data to store
|
|
924
|
+
*/
|
|
925
|
+
async set(key) {
|
|
926
|
+
const validation = validateKeyData(key);
|
|
927
|
+
if (!validation.valid) {
|
|
928
|
+
throw new Error(`Invalid key data: ${validation.error}`);
|
|
929
|
+
}
|
|
930
|
+
await this.storage.set(key.kid, JSON.stringify(key, null, 2));
|
|
931
|
+
if (this.enableCache) {
|
|
932
|
+
this.cache.set(key.kid, key);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Delete a key.
|
|
937
|
+
*
|
|
938
|
+
* @param kid - Key identifier
|
|
939
|
+
* @returns true if key existed and was deleted
|
|
940
|
+
*/
|
|
941
|
+
async delete(kid) {
|
|
942
|
+
this.cache.delete(kid);
|
|
943
|
+
return this.storage.delete(kid);
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Check if a key exists.
|
|
947
|
+
*
|
|
948
|
+
* @param kid - Key identifier
|
|
949
|
+
* @returns true if key exists
|
|
950
|
+
*/
|
|
951
|
+
async has(kid) {
|
|
952
|
+
if (this.enableCache && this.cache.has(kid)) {
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
return this.storage.exists(kid);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* List all key IDs.
|
|
959
|
+
*
|
|
960
|
+
* @returns Array of key identifiers
|
|
961
|
+
*/
|
|
962
|
+
async list() {
|
|
963
|
+
return this.storage.keys();
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Get or create a secret key.
|
|
967
|
+
*
|
|
968
|
+
* If a key with the given ID exists and is a valid secret key,
|
|
969
|
+
* it is returned. Otherwise, a new secret key is generated.
|
|
970
|
+
*
|
|
971
|
+
* @param kid - Key identifier
|
|
972
|
+
* @param options - Options for key creation
|
|
973
|
+
* @returns Secret key data
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* ```typescript
|
|
977
|
+
* const secret = await keys.getOrCreateSecret('encryption-key');
|
|
978
|
+
* const bytes = base64urlDecode(secret.secret);
|
|
979
|
+
* ```
|
|
980
|
+
*/
|
|
981
|
+
async getOrCreateSecret(kid, options) {
|
|
982
|
+
const existing = await this.getSecret(kid);
|
|
983
|
+
if (existing) {
|
|
984
|
+
return existing;
|
|
985
|
+
}
|
|
986
|
+
const bytes = options?.bytes ?? 32;
|
|
987
|
+
const secret = {
|
|
988
|
+
type: "secret",
|
|
989
|
+
kid,
|
|
990
|
+
secret: base64urlEncode(randomBytes(bytes)),
|
|
991
|
+
bytes,
|
|
992
|
+
createdAt: Date.now(),
|
|
993
|
+
version: 1
|
|
994
|
+
};
|
|
995
|
+
await this.set(secret);
|
|
996
|
+
return secret;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Clear the in-memory cache.
|
|
1000
|
+
*
|
|
1001
|
+
* Useful when you want to force a reload from storage.
|
|
1002
|
+
*/
|
|
1003
|
+
clearCache() {
|
|
1004
|
+
this.cache.clear();
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Clear cache for a specific key.
|
|
1008
|
+
*
|
|
1009
|
+
* @param kid - Key identifier
|
|
1010
|
+
*/
|
|
1011
|
+
clearCacheFor(kid) {
|
|
1012
|
+
this.cache.delete(kid);
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Check if a key is in the cache.
|
|
1016
|
+
*
|
|
1017
|
+
* @param kid - Key identifier
|
|
1018
|
+
* @returns true if key is cached
|
|
1019
|
+
*/
|
|
1020
|
+
isCached(kid) {
|
|
1021
|
+
return this.cache.has(kid);
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Get the underlying storage adapter.
|
|
1025
|
+
*
|
|
1026
|
+
* Use with caution - direct storage access bypasses validation.
|
|
1027
|
+
*/
|
|
1028
|
+
getAdapter() {
|
|
1029
|
+
return this.storage;
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
var StorageError;
|
|
1035
|
+
var StorageConnectionError;
|
|
1036
|
+
var StorageOperationError;
|
|
1037
|
+
var StorageNotSupportedError;
|
|
1038
|
+
var StorageConfigError;
|
|
1039
|
+
var StorageTTLError;
|
|
1040
|
+
var StoragePatternError;
|
|
1041
|
+
var StorageNotConnectedError;
|
|
1042
|
+
var EncryptedStorageError;
|
|
1043
|
+
var init_errors = __esm({
|
|
1044
|
+
"libs/utils/src/storage/errors.ts"() {
|
|
1045
|
+
"use strict";
|
|
1046
|
+
StorageError = class extends Error {
|
|
1047
|
+
cause;
|
|
1048
|
+
constructor(message, cause) {
|
|
1049
|
+
super(message);
|
|
1050
|
+
this.cause = cause;
|
|
1051
|
+
this.name = "StorageError";
|
|
1052
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1053
|
+
if (Error.captureStackTrace) {
|
|
1054
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
StorageConnectionError = class extends StorageError {
|
|
1059
|
+
constructor(message, cause, backend) {
|
|
1060
|
+
const fullMessage = cause ? `${message}: ${cause.message}` : message;
|
|
1061
|
+
super(fullMessage, cause);
|
|
1062
|
+
this.backend = backend;
|
|
1063
|
+
this.name = "StorageConnectionError";
|
|
1064
|
+
}
|
|
1065
|
+
backend;
|
|
1066
|
+
};
|
|
1067
|
+
StorageOperationError = class extends StorageError {
|
|
1068
|
+
constructor(operation, key, message, cause) {
|
|
1069
|
+
super(`${operation} failed for key "${key}": ${message}`, cause);
|
|
1070
|
+
this.operation = operation;
|
|
1071
|
+
this.key = key;
|
|
1072
|
+
this.name = "StorageOperationError";
|
|
1073
|
+
}
|
|
1074
|
+
operation;
|
|
1075
|
+
key;
|
|
1076
|
+
};
|
|
1077
|
+
StorageNotSupportedError = class extends StorageError {
|
|
1078
|
+
constructor(operation, backend, suggestion) {
|
|
1079
|
+
const msg = suggestion ? `${operation} is not supported by ${backend}. ${suggestion}` : `${operation} is not supported by ${backend}`;
|
|
1080
|
+
super(msg);
|
|
1081
|
+
this.operation = operation;
|
|
1082
|
+
this.backend = backend;
|
|
1083
|
+
this.name = "StorageNotSupportedError";
|
|
1084
|
+
}
|
|
1085
|
+
operation;
|
|
1086
|
+
backend;
|
|
1087
|
+
};
|
|
1088
|
+
StorageConfigError = class extends StorageError {
|
|
1089
|
+
constructor(backend, message) {
|
|
1090
|
+
super(`Invalid ${backend} configuration: ${message}`);
|
|
1091
|
+
this.backend = backend;
|
|
1092
|
+
this.name = "StorageConfigError";
|
|
1093
|
+
}
|
|
1094
|
+
backend;
|
|
1095
|
+
};
|
|
1096
|
+
StorageTTLError = class extends StorageError {
|
|
1097
|
+
constructor(ttl, message) {
|
|
1098
|
+
super(message ?? `Invalid TTL value: ${ttl}. TTL must be a positive integer.`);
|
|
1099
|
+
this.ttl = ttl;
|
|
1100
|
+
this.name = "StorageTTLError";
|
|
1101
|
+
}
|
|
1102
|
+
ttl;
|
|
1103
|
+
};
|
|
1104
|
+
StoragePatternError = class extends StorageError {
|
|
1105
|
+
constructor(pattern, message) {
|
|
1106
|
+
super(`Invalid pattern "${pattern}": ${message}`);
|
|
1107
|
+
this.pattern = pattern;
|
|
1108
|
+
this.name = "StoragePatternError";
|
|
1109
|
+
}
|
|
1110
|
+
pattern;
|
|
1111
|
+
};
|
|
1112
|
+
StorageNotConnectedError = class extends StorageError {
|
|
1113
|
+
constructor(backend) {
|
|
1114
|
+
super(`Storage backend "${backend}" is not connected. Call connect() first.`);
|
|
1115
|
+
this.backend = backend;
|
|
1116
|
+
this.name = "StorageNotConnectedError";
|
|
1117
|
+
}
|
|
1118
|
+
backend;
|
|
1119
|
+
};
|
|
1120
|
+
EncryptedStorageError = class extends StorageError {
|
|
1121
|
+
constructor(message) {
|
|
1122
|
+
super(message);
|
|
1123
|
+
this.name = "EncryptedStorageError";
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
function validateTTL(ttlSeconds) {
|
|
1129
|
+
if (typeof ttlSeconds !== "number") {
|
|
1130
|
+
throw new StorageTTLError(ttlSeconds, `TTL must be a number, got ${typeof ttlSeconds}`);
|
|
1131
|
+
}
|
|
1132
|
+
if (!Number.isFinite(ttlSeconds)) {
|
|
1133
|
+
throw new StorageTTLError(ttlSeconds, "TTL must be a finite number");
|
|
1134
|
+
}
|
|
1135
|
+
if (!Number.isInteger(ttlSeconds)) {
|
|
1136
|
+
throw new StorageTTLError(ttlSeconds, `TTL must be an integer, got ${ttlSeconds}`);
|
|
1137
|
+
}
|
|
1138
|
+
if (ttlSeconds <= 0) {
|
|
1139
|
+
throw new StorageTTLError(ttlSeconds, `TTL must be positive, got ${ttlSeconds}`);
|
|
1140
|
+
}
|
|
1141
|
+
if (ttlSeconds > MAX_TTL_SECONDS) {
|
|
1142
|
+
throw new StorageTTLError(ttlSeconds, `TTL exceeds maximum of ${MAX_TTL_SECONDS} seconds`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function validateOptionalTTL(ttlSeconds) {
|
|
1146
|
+
if (ttlSeconds !== void 0) {
|
|
1147
|
+
validateTTL(ttlSeconds);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function ttlToExpiresAt(ttlSeconds) {
|
|
1151
|
+
return Date.now() + ttlSeconds * 1e3;
|
|
1152
|
+
}
|
|
1153
|
+
function expiresAtToTTL(expiresAt) {
|
|
1154
|
+
const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
1155
|
+
return Math.max(0, remaining);
|
|
1156
|
+
}
|
|
1157
|
+
function isExpired(expiresAt) {
|
|
1158
|
+
if (expiresAt === void 0) return false;
|
|
1159
|
+
return Date.now() >= expiresAt;
|
|
1160
|
+
}
|
|
1161
|
+
var MAX_TTL_SECONDS;
|
|
1162
|
+
var init_ttl = __esm({
|
|
1163
|
+
"libs/utils/src/storage/utils/ttl.ts"() {
|
|
1164
|
+
"use strict";
|
|
1165
|
+
init_errors();
|
|
1166
|
+
MAX_TTL_SECONDS = 31536e5;
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
var BaseStorageAdapter;
|
|
1170
|
+
var init_base = __esm({
|
|
1171
|
+
"libs/utils/src/storage/adapters/base.ts"() {
|
|
1172
|
+
"use strict";
|
|
1173
|
+
init_errors();
|
|
1174
|
+
init_ttl();
|
|
1175
|
+
BaseStorageAdapter = class {
|
|
1176
|
+
/**
|
|
1177
|
+
* Whether the adapter is currently connected.
|
|
1178
|
+
*/
|
|
1179
|
+
connected = false;
|
|
1180
|
+
/**
|
|
1181
|
+
* Ensure adapter is connected before operations.
|
|
1182
|
+
* @throws StorageNotConnectedError if not connected
|
|
1183
|
+
*/
|
|
1184
|
+
ensureConnected() {
|
|
1185
|
+
if (!this.connected) {
|
|
1186
|
+
throw new StorageNotConnectedError(this.backendName);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Set with validation.
|
|
1191
|
+
*/
|
|
1192
|
+
async set(key, value, options) {
|
|
1193
|
+
this.ensureConnected();
|
|
1194
|
+
this.validateSetOptions(options);
|
|
1195
|
+
return this.doSet(key, value, options);
|
|
1196
|
+
}
|
|
1197
|
+
// ============================================
|
|
1198
|
+
// Batch Operations (default implementations)
|
|
1199
|
+
// ============================================
|
|
1200
|
+
/**
|
|
1201
|
+
* Default mget: sequential gets.
|
|
1202
|
+
* Override for more efficient implementations.
|
|
1203
|
+
*/
|
|
1204
|
+
async mget(keys) {
|
|
1205
|
+
this.ensureConnected();
|
|
1206
|
+
return Promise.all(keys.map((k) => this.get(k)));
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Default mset: sequential sets.
|
|
1210
|
+
* Override for atomic/pipelined implementations.
|
|
1211
|
+
*/
|
|
1212
|
+
async mset(entries) {
|
|
1213
|
+
this.ensureConnected();
|
|
1214
|
+
for (const entry of entries) {
|
|
1215
|
+
this.validateSetOptions(entry.options);
|
|
1216
|
+
}
|
|
1217
|
+
await Promise.all(entries.map((e) => this.doSet(e.key, e.value, e.options)));
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Default mdelete: sequential deletes.
|
|
1221
|
+
* Override for more efficient implementations.
|
|
1222
|
+
*/
|
|
1223
|
+
async mdelete(keys) {
|
|
1224
|
+
this.ensureConnected();
|
|
1225
|
+
const results = await Promise.all(keys.map((k) => this.delete(k)));
|
|
1226
|
+
return results.filter(Boolean).length;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Default count: keys().length.
|
|
1230
|
+
* Override for more efficient implementations.
|
|
1231
|
+
*/
|
|
1232
|
+
async count(pattern = "*") {
|
|
1233
|
+
const matchedKeys = await this.keys(pattern);
|
|
1234
|
+
return matchedKeys.length;
|
|
1235
|
+
}
|
|
1236
|
+
// ============================================
|
|
1237
|
+
// Pub/Sub (default: not supported)
|
|
1238
|
+
// ============================================
|
|
1239
|
+
/**
|
|
1240
|
+
* Default: pub/sub not supported.
|
|
1241
|
+
* Override in adapters that support it.
|
|
1242
|
+
*/
|
|
1243
|
+
supportsPubSub() {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Default: throw not supported error.
|
|
1248
|
+
* Override in adapters that support pub/sub.
|
|
1249
|
+
*/
|
|
1250
|
+
async publish(_channel, _message) {
|
|
1251
|
+
throw new StorageNotSupportedError("publish", this.backendName, this.getPubSubSuggestion());
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Default: throw not supported error.
|
|
1255
|
+
* Override in adapters that support pub/sub.
|
|
1256
|
+
*/
|
|
1257
|
+
async subscribe(_channel, _handler) {
|
|
1258
|
+
throw new StorageNotSupportedError("subscribe", this.backendName, this.getPubSubSuggestion());
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Get suggestion message for pub/sub not supported error.
|
|
1262
|
+
* Override in specific adapters.
|
|
1263
|
+
*/
|
|
1264
|
+
getPubSubSuggestion() {
|
|
1265
|
+
return "Use Redis or Upstash adapter for pub/sub support.";
|
|
1266
|
+
}
|
|
1267
|
+
// ============================================
|
|
1268
|
+
// Validation Helpers
|
|
1269
|
+
// ============================================
|
|
1270
|
+
/**
|
|
1271
|
+
* Validate set options.
|
|
1272
|
+
*/
|
|
1273
|
+
validateSetOptions(options) {
|
|
1274
|
+
if (!options) return;
|
|
1275
|
+
validateOptionalTTL(options.ttlSeconds);
|
|
1276
|
+
if (options.ifNotExists && options.ifExists) {
|
|
1277
|
+
throw new Error("ifNotExists and ifExists are mutually exclusive");
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
function globToRegex(pattern) {
|
|
1284
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
1285
|
+
throw new StoragePatternError(
|
|
1286
|
+
pattern.substring(0, 50) + "...",
|
|
1287
|
+
`Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
const wildcardCount = (pattern.match(/[*?]/g) || []).length;
|
|
1291
|
+
if (wildcardCount > MAX_WILDCARDS) {
|
|
1292
|
+
throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
|
|
1293
|
+
}
|
|
1294
|
+
if (pattern === "" || pattern === "*") {
|
|
1295
|
+
return /^.*$/;
|
|
1296
|
+
}
|
|
1297
|
+
let regexStr = "^";
|
|
1298
|
+
let prevChar = "";
|
|
1299
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
1300
|
+
const char = pattern[i];
|
|
1301
|
+
switch (char) {
|
|
1302
|
+
case "*":
|
|
1303
|
+
if (prevChar !== "*") {
|
|
1304
|
+
regexStr += ".*";
|
|
1305
|
+
}
|
|
1306
|
+
break;
|
|
1307
|
+
case "?":
|
|
1308
|
+
regexStr += ".";
|
|
1309
|
+
break;
|
|
1310
|
+
default:
|
|
1311
|
+
regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
|
|
1312
|
+
}
|
|
1313
|
+
prevChar = char;
|
|
1314
|
+
}
|
|
1315
|
+
regexStr += "$";
|
|
1316
|
+
try {
|
|
1317
|
+
return new RegExp(regexStr);
|
|
1318
|
+
} catch {
|
|
1319
|
+
throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function matchesPattern(key, pattern) {
|
|
1323
|
+
const regex = globToRegex(pattern);
|
|
1324
|
+
return regex.test(key);
|
|
1325
|
+
}
|
|
1326
|
+
var MAX_PATTERN_LENGTH;
|
|
1327
|
+
var MAX_WILDCARDS;
|
|
1328
|
+
var REGEX_SPECIAL_CHARS;
|
|
1329
|
+
var init_pattern = __esm({
|
|
1330
|
+
"libs/utils/src/storage/utils/pattern.ts"() {
|
|
1331
|
+
"use strict";
|
|
1332
|
+
init_errors();
|
|
1333
|
+
MAX_PATTERN_LENGTH = 500;
|
|
1334
|
+
MAX_WILDCARDS = 20;
|
|
1335
|
+
REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
var DEFAULT_SWEEP_INTERVAL_SECONDS;
|
|
1339
|
+
var MAX_TIMEOUT_MS;
|
|
1340
|
+
var MemoryStorageAdapter;
|
|
1341
|
+
var init_memory = __esm({
|
|
1342
|
+
"libs/utils/src/storage/adapters/memory.ts"() {
|
|
1343
|
+
"use strict";
|
|
1344
|
+
init_base();
|
|
1345
|
+
init_errors();
|
|
1346
|
+
init_pattern();
|
|
1347
|
+
init_ttl();
|
|
1348
|
+
DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
|
|
1349
|
+
MAX_TIMEOUT_MS = 2147483647;
|
|
1350
|
+
MemoryStorageAdapter = class extends BaseStorageAdapter {
|
|
1351
|
+
backendName = "memory";
|
|
1352
|
+
store = /* @__PURE__ */ new Map();
|
|
1353
|
+
emitter = new import_event_emitter.EventEmitter();
|
|
1354
|
+
sweepInterval;
|
|
1355
|
+
options;
|
|
1356
|
+
// LRU tracking (simple linked list via insertion order in Map)
|
|
1357
|
+
accessOrder = [];
|
|
1358
|
+
constructor(options = {}) {
|
|
1359
|
+
super();
|
|
1360
|
+
this.options = {
|
|
1361
|
+
enableSweeper: options.enableSweeper ?? true,
|
|
1362
|
+
sweepIntervalSeconds: options.sweepIntervalSeconds ?? DEFAULT_SWEEP_INTERVAL_SECONDS,
|
|
1363
|
+
maxEntries: options.maxEntries ?? 0
|
|
1364
|
+
// 0 = unlimited
|
|
1365
|
+
};
|
|
1366
|
+
this.emitter.setMaxListeners(1e3);
|
|
1367
|
+
}
|
|
1368
|
+
// ============================================
|
|
1369
|
+
// Connection Lifecycle
|
|
1370
|
+
// ============================================
|
|
1371
|
+
async connect() {
|
|
1372
|
+
if (this.connected) return;
|
|
1373
|
+
this.connected = true;
|
|
1374
|
+
if (this.options.enableSweeper && this.options.sweepIntervalSeconds > 0) {
|
|
1375
|
+
this.startSweeper();
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
async disconnect() {
|
|
1379
|
+
if (!this.connected) return;
|
|
1380
|
+
this.stopSweeper();
|
|
1381
|
+
this.clearAllTimeouts();
|
|
1382
|
+
this.store.clear();
|
|
1383
|
+
this.accessOrder = [];
|
|
1384
|
+
this.emitter.removeAllListeners();
|
|
1385
|
+
this.connected = false;
|
|
1386
|
+
}
|
|
1387
|
+
async ping() {
|
|
1388
|
+
return this.connected;
|
|
1389
|
+
}
|
|
1390
|
+
// ============================================
|
|
1391
|
+
// Core Operations
|
|
1392
|
+
// ============================================
|
|
1393
|
+
async get(key) {
|
|
1394
|
+
this.ensureConnected();
|
|
1395
|
+
const entry = this.store.get(key);
|
|
1396
|
+
if (!entry) return null;
|
|
1397
|
+
if (isExpired(entry.expiresAt)) {
|
|
1398
|
+
this.deleteEntry(key);
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
this.touchLRU(key);
|
|
1402
|
+
return entry.value;
|
|
1403
|
+
}
|
|
1404
|
+
async doSet(key, value, options) {
|
|
1405
|
+
const existingEntry = this.store.get(key);
|
|
1406
|
+
const exists = existingEntry !== void 0 && !isExpired(existingEntry.expiresAt);
|
|
1407
|
+
if (options?.ifNotExists && exists) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
if (options?.ifExists && !exists) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
this.clearEntryTimeout(key);
|
|
1414
|
+
const entry = { value };
|
|
1415
|
+
if (options?.ttlSeconds) {
|
|
1416
|
+
entry.expiresAt = ttlToExpiresAt(options.ttlSeconds);
|
|
1417
|
+
const ttlMs = options.ttlSeconds * 1e3;
|
|
1418
|
+
if (ttlMs < MAX_TIMEOUT_MS) {
|
|
1419
|
+
entry.timeout = setTimeout(() => {
|
|
1420
|
+
this.deleteEntry(key);
|
|
1421
|
+
}, ttlMs);
|
|
1422
|
+
if (entry.timeout.unref) {
|
|
1423
|
+
entry.timeout.unref();
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (this.options.maxEntries > 0 && !this.store.has(key)) {
|
|
1428
|
+
while (this.store.size >= this.options.maxEntries) {
|
|
1429
|
+
this.evictOldest();
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
this.store.set(key, entry);
|
|
1433
|
+
this.touchLRU(key);
|
|
1434
|
+
}
|
|
1435
|
+
async delete(key) {
|
|
1436
|
+
this.ensureConnected();
|
|
1437
|
+
return this.deleteEntry(key);
|
|
1438
|
+
}
|
|
1439
|
+
async exists(key) {
|
|
1440
|
+
this.ensureConnected();
|
|
1441
|
+
const entry = this.store.get(key);
|
|
1442
|
+
if (!entry) return false;
|
|
1443
|
+
if (isExpired(entry.expiresAt)) {
|
|
1444
|
+
this.deleteEntry(key);
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
return true;
|
|
1448
|
+
}
|
|
1449
|
+
// ============================================
|
|
1450
|
+
// TTL Operations
|
|
1451
|
+
// ============================================
|
|
1452
|
+
async expire(key, ttlSeconds) {
|
|
1453
|
+
this.ensureConnected();
|
|
1454
|
+
validateTTL(ttlSeconds);
|
|
1455
|
+
const entry = this.store.get(key);
|
|
1456
|
+
if (!entry || isExpired(entry.expiresAt)) {
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
this.clearEntryTimeout(key);
|
|
1460
|
+
entry.expiresAt = ttlToExpiresAt(ttlSeconds);
|
|
1461
|
+
const ttlMs = ttlSeconds * 1e3;
|
|
1462
|
+
if (ttlMs < MAX_TIMEOUT_MS) {
|
|
1463
|
+
entry.timeout = setTimeout(() => {
|
|
1464
|
+
this.deleteEntry(key);
|
|
1465
|
+
}, ttlMs);
|
|
1466
|
+
if (entry.timeout.unref) {
|
|
1467
|
+
entry.timeout.unref();
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return true;
|
|
1471
|
+
}
|
|
1472
|
+
async ttl(key) {
|
|
1473
|
+
this.ensureConnected();
|
|
1474
|
+
const entry = this.store.get(key);
|
|
1475
|
+
if (!entry) return null;
|
|
1476
|
+
if (isExpired(entry.expiresAt)) {
|
|
1477
|
+
this.deleteEntry(key);
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
if (entry.expiresAt === void 0) {
|
|
1481
|
+
return -1;
|
|
1482
|
+
}
|
|
1483
|
+
return expiresAtToTTL(entry.expiresAt);
|
|
1484
|
+
}
|
|
1485
|
+
// ============================================
|
|
1486
|
+
// Key Enumeration
|
|
1487
|
+
// ============================================
|
|
1488
|
+
async keys(pattern = "*") {
|
|
1489
|
+
this.ensureConnected();
|
|
1490
|
+
const regex = globToRegex(pattern);
|
|
1491
|
+
const result = [];
|
|
1492
|
+
for (const [key, entry] of this.store) {
|
|
1493
|
+
if (isExpired(entry.expiresAt)) {
|
|
1494
|
+
this.deleteEntry(key);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (regex.test(key)) {
|
|
1498
|
+
result.push(key);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return result;
|
|
1502
|
+
}
|
|
1503
|
+
// ============================================
|
|
1504
|
+
// Atomic Operations
|
|
1505
|
+
// ============================================
|
|
1506
|
+
async incr(key) {
|
|
1507
|
+
return this.incrBy(key, 1);
|
|
1508
|
+
}
|
|
1509
|
+
async decr(key) {
|
|
1510
|
+
return this.incrBy(key, -1);
|
|
1511
|
+
}
|
|
1512
|
+
async incrBy(key, amount) {
|
|
1513
|
+
this.ensureConnected();
|
|
1514
|
+
const entry = this.store.get(key);
|
|
1515
|
+
let currentValue = 0;
|
|
1516
|
+
if (entry && !isExpired(entry.expiresAt)) {
|
|
1517
|
+
const parsed = parseInt(entry.value, 10);
|
|
1518
|
+
if (isNaN(parsed)) {
|
|
1519
|
+
throw new StorageOperationError("incrBy", key, "Value is not an integer");
|
|
1520
|
+
}
|
|
1521
|
+
currentValue = parsed;
|
|
1522
|
+
}
|
|
1523
|
+
const newValue = currentValue + amount;
|
|
1524
|
+
const newEntry = { value: String(newValue) };
|
|
1525
|
+
if (entry?.expiresAt && !isExpired(entry.expiresAt)) {
|
|
1526
|
+
newEntry.expiresAt = entry.expiresAt;
|
|
1527
|
+
const remainingMs = entry.expiresAt - Date.now();
|
|
1528
|
+
if (remainingMs > 0 && remainingMs < MAX_TIMEOUT_MS) {
|
|
1529
|
+
this.clearEntryTimeout(key);
|
|
1530
|
+
newEntry.timeout = setTimeout(() => {
|
|
1531
|
+
this.deleteEntry(key);
|
|
1532
|
+
}, remainingMs);
|
|
1533
|
+
if (newEntry.timeout.unref) {
|
|
1534
|
+
newEntry.timeout.unref();
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
this.store.set(key, newEntry);
|
|
1539
|
+
this.touchLRU(key);
|
|
1540
|
+
return newValue;
|
|
1541
|
+
}
|
|
1542
|
+
// ============================================
|
|
1543
|
+
// Pub/Sub
|
|
1544
|
+
// ============================================
|
|
1545
|
+
supportsPubSub() {
|
|
1546
|
+
return true;
|
|
1547
|
+
}
|
|
1548
|
+
async publish(channel, message) {
|
|
1549
|
+
this.ensureConnected();
|
|
1550
|
+
return this.emitter.listenerCount(channel) > 0 ? (this.emitter.emit(channel, message, channel), this.emitter.listenerCount(channel)) : 0;
|
|
1551
|
+
}
|
|
1552
|
+
async subscribe(channel, handler) {
|
|
1553
|
+
this.ensureConnected();
|
|
1554
|
+
const wrappedHandler = (message, ch) => {
|
|
1555
|
+
handler(message, ch);
|
|
1556
|
+
};
|
|
1557
|
+
this.emitter.on(channel, wrappedHandler);
|
|
1558
|
+
return async () => {
|
|
1559
|
+
this.emitter.off(channel, wrappedHandler);
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
// ============================================
|
|
1563
|
+
// Internal Helpers
|
|
1564
|
+
// ============================================
|
|
1565
|
+
/**
|
|
1566
|
+
* Delete an entry and clear its timeout.
|
|
1567
|
+
*/
|
|
1568
|
+
deleteEntry(key) {
|
|
1569
|
+
const entry = this.store.get(key);
|
|
1570
|
+
if (!entry) return false;
|
|
1571
|
+
this.clearEntryTimeout(key);
|
|
1572
|
+
this.store.delete(key);
|
|
1573
|
+
this.removeLRU(key);
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Clear timeout for an entry.
|
|
1578
|
+
*/
|
|
1579
|
+
clearEntryTimeout(key) {
|
|
1580
|
+
const entry = this.store.get(key);
|
|
1581
|
+
if (entry?.timeout) {
|
|
1582
|
+
clearTimeout(entry.timeout);
|
|
1583
|
+
entry.timeout = void 0;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Clear all timeouts.
|
|
1588
|
+
*/
|
|
1589
|
+
clearAllTimeouts() {
|
|
1590
|
+
for (const [key] of this.store) {
|
|
1591
|
+
this.clearEntryTimeout(key);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Update LRU access order.
|
|
1596
|
+
*/
|
|
1597
|
+
touchLRU(key) {
|
|
1598
|
+
if (this.options.maxEntries <= 0) return;
|
|
1599
|
+
const idx = this.accessOrder.indexOf(key);
|
|
1600
|
+
if (idx !== -1) {
|
|
1601
|
+
this.accessOrder.splice(idx, 1);
|
|
1602
|
+
}
|
|
1603
|
+
this.accessOrder.push(key);
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Remove key from LRU tracking.
|
|
1607
|
+
*/
|
|
1608
|
+
removeLRU(key) {
|
|
1609
|
+
if (this.options.maxEntries <= 0) return;
|
|
1610
|
+
const idx = this.accessOrder.indexOf(key);
|
|
1611
|
+
if (idx !== -1) {
|
|
1612
|
+
this.accessOrder.splice(idx, 1);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Evict oldest entry (LRU).
|
|
1617
|
+
*/
|
|
1618
|
+
evictOldest() {
|
|
1619
|
+
if (this.accessOrder.length === 0) return;
|
|
1620
|
+
const oldestKey = this.accessOrder.shift();
|
|
1621
|
+
if (oldestKey) {
|
|
1622
|
+
this.deleteEntry(oldestKey);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Start background sweeper.
|
|
1627
|
+
*/
|
|
1628
|
+
startSweeper() {
|
|
1629
|
+
this.sweepInterval = setInterval(() => {
|
|
1630
|
+
this.sweep();
|
|
1631
|
+
}, this.options.sweepIntervalSeconds * 1e3);
|
|
1632
|
+
if (this.sweepInterval.unref) {
|
|
1633
|
+
this.sweepInterval.unref();
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Stop background sweeper.
|
|
1638
|
+
*/
|
|
1639
|
+
stopSweeper() {
|
|
1640
|
+
if (this.sweepInterval) {
|
|
1641
|
+
clearInterval(this.sweepInterval);
|
|
1642
|
+
this.sweepInterval = void 0;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Sweep expired entries.
|
|
1647
|
+
*/
|
|
1648
|
+
sweep() {
|
|
1649
|
+
const now = Date.now();
|
|
1650
|
+
for (const [key, entry] of this.store) {
|
|
1651
|
+
if (entry.expiresAt && now >= entry.expiresAt) {
|
|
1652
|
+
this.deleteEntry(key);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
// ============================================
|
|
1657
|
+
// Stats (for debugging)
|
|
1658
|
+
// ============================================
|
|
1659
|
+
/**
|
|
1660
|
+
* Get storage statistics.
|
|
1661
|
+
*/
|
|
1662
|
+
getStats() {
|
|
1663
|
+
return {
|
|
1664
|
+
size: this.store.size,
|
|
1665
|
+
maxEntries: this.options.maxEntries,
|
|
1666
|
+
sweeperActive: this.sweepInterval !== void 0
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
var filesystem_exports = {};
|
|
1673
|
+
__export(filesystem_exports, {
|
|
1674
|
+
FileSystemStorageAdapter: () => FileSystemStorageAdapter
|
|
1675
|
+
});
|
|
1676
|
+
var FileSystemStorageAdapter;
|
|
1677
|
+
var init_filesystem = __esm({
|
|
1678
|
+
"libs/utils/src/storage/adapters/filesystem.ts"() {
|
|
1679
|
+
"use strict";
|
|
1680
|
+
init_base();
|
|
1681
|
+
init_errors();
|
|
1682
|
+
init_pattern();
|
|
1683
|
+
init_fs2();
|
|
1684
|
+
init_crypto();
|
|
1685
|
+
FileSystemStorageAdapter = class extends BaseStorageAdapter {
|
|
1686
|
+
backendName = "filesystem";
|
|
1687
|
+
baseDir;
|
|
1688
|
+
extension;
|
|
1689
|
+
dirMode;
|
|
1690
|
+
fileMode;
|
|
1691
|
+
constructor(options) {
|
|
1692
|
+
super();
|
|
1693
|
+
this.baseDir = options.baseDir;
|
|
1694
|
+
this.extension = options.extension ?? ".json";
|
|
1695
|
+
this.dirMode = options.dirMode ?? 448;
|
|
1696
|
+
this.fileMode = options.fileMode ?? 384;
|
|
1697
|
+
}
|
|
1698
|
+
// ============================================
|
|
1699
|
+
// Connection Lifecycle
|
|
1700
|
+
// ============================================
|
|
1701
|
+
async connect() {
|
|
1702
|
+
if (this.connected) return;
|
|
1703
|
+
await ensureDir(this.baseDir);
|
|
1704
|
+
try {
|
|
1705
|
+
await mkdir(this.baseDir, { recursive: true, mode: this.dirMode });
|
|
1706
|
+
} catch {
|
|
1707
|
+
}
|
|
1708
|
+
this.connected = true;
|
|
1709
|
+
}
|
|
1710
|
+
async disconnect() {
|
|
1711
|
+
if (!this.connected) return;
|
|
1712
|
+
this.connected = false;
|
|
1713
|
+
}
|
|
1714
|
+
async ping() {
|
|
1715
|
+
if (!this.connected) return false;
|
|
1716
|
+
try {
|
|
1717
|
+
return await fileExists(this.baseDir);
|
|
1718
|
+
} catch {
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
// ============================================
|
|
1723
|
+
// Core Operations
|
|
1724
|
+
// ============================================
|
|
1725
|
+
async get(key) {
|
|
1726
|
+
this.ensureConnected();
|
|
1727
|
+
const filePath = this.keyToPath(key);
|
|
1728
|
+
try {
|
|
1729
|
+
const entry = await readJSON(filePath);
|
|
1730
|
+
if (!entry) return null;
|
|
1731
|
+
if (entry.expiresAt && Date.now() >= entry.expiresAt) {
|
|
1732
|
+
await this.deleteFile(filePath);
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
return entry.value;
|
|
1736
|
+
} catch (e) {
|
|
1737
|
+
const error = e;
|
|
1738
|
+
if (error.code === "ENOENT") {
|
|
1739
|
+
return null;
|
|
1740
|
+
}
|
|
1741
|
+
throw e;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async doSet(key, value, options) {
|
|
1745
|
+
const filePath = this.keyToPath(key);
|
|
1746
|
+
const existingEntry = await this.readEntry(filePath);
|
|
1747
|
+
const exists = existingEntry !== null && !this.isExpired(existingEntry);
|
|
1748
|
+
if (options?.ifNotExists && exists) {
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (options?.ifExists && !exists) {
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
const entry = { value };
|
|
1755
|
+
if (options?.ttlSeconds) {
|
|
1756
|
+
entry.expiresAt = Date.now() + options.ttlSeconds * 1e3;
|
|
1757
|
+
}
|
|
1758
|
+
await this.atomicWrite(filePath, entry);
|
|
1759
|
+
}
|
|
1760
|
+
async delete(key) {
|
|
1761
|
+
this.ensureConnected();
|
|
1762
|
+
const filePath = this.keyToPath(key);
|
|
1763
|
+
return this.deleteFile(filePath);
|
|
1764
|
+
}
|
|
1765
|
+
async exists(key) {
|
|
1766
|
+
this.ensureConnected();
|
|
1767
|
+
const filePath = this.keyToPath(key);
|
|
1768
|
+
try {
|
|
1769
|
+
const entry = await this.readEntry(filePath);
|
|
1770
|
+
if (!entry) return false;
|
|
1771
|
+
if (this.isExpired(entry)) {
|
|
1772
|
+
await this.deleteFile(filePath);
|
|
1773
|
+
return false;
|
|
1774
|
+
}
|
|
1775
|
+
return true;
|
|
1776
|
+
} catch {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
// ============================================
|
|
1781
|
+
// TTL Operations
|
|
1782
|
+
// ============================================
|
|
1783
|
+
async expire(key, ttlSeconds) {
|
|
1784
|
+
this.ensureConnected();
|
|
1785
|
+
const filePath = this.keyToPath(key);
|
|
1786
|
+
const entry = await this.readEntry(filePath);
|
|
1787
|
+
if (!entry || this.isExpired(entry)) {
|
|
1788
|
+
return false;
|
|
1789
|
+
}
|
|
1790
|
+
entry.expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
1791
|
+
await this.atomicWrite(filePath, entry);
|
|
1792
|
+
return true;
|
|
1793
|
+
}
|
|
1794
|
+
async ttl(key) {
|
|
1795
|
+
this.ensureConnected();
|
|
1796
|
+
const filePath = this.keyToPath(key);
|
|
1797
|
+
const entry = await this.readEntry(filePath);
|
|
1798
|
+
if (!entry) return null;
|
|
1799
|
+
if (this.isExpired(entry)) {
|
|
1800
|
+
await this.deleteFile(filePath);
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
if (entry.expiresAt === void 0) {
|
|
1804
|
+
return -1;
|
|
1805
|
+
}
|
|
1806
|
+
return Math.max(0, Math.ceil((entry.expiresAt - Date.now()) / 1e3));
|
|
1807
|
+
}
|
|
1808
|
+
// ============================================
|
|
1809
|
+
// Key Enumeration
|
|
1810
|
+
// ============================================
|
|
1811
|
+
async keys(pattern = "*") {
|
|
1812
|
+
this.ensureConnected();
|
|
1813
|
+
const regex = globToRegex(pattern);
|
|
1814
|
+
const result = [];
|
|
1815
|
+
try {
|
|
1816
|
+
const files = await readdir(this.baseDir);
|
|
1817
|
+
for (const file of files) {
|
|
1818
|
+
if (!file.endsWith(this.extension)) continue;
|
|
1819
|
+
const key = this.pathToKey(file);
|
|
1820
|
+
if (!regex.test(key)) continue;
|
|
1821
|
+
const filePath = this.keyToPath(key);
|
|
1822
|
+
const entry = await this.readEntry(filePath);
|
|
1823
|
+
if (entry && !this.isExpired(entry)) {
|
|
1824
|
+
result.push(key);
|
|
1825
|
+
} else if (entry && this.isExpired(entry)) {
|
|
1826
|
+
await this.deleteFile(filePath);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
} catch (e) {
|
|
1830
|
+
const error = e;
|
|
1831
|
+
if (error.code === "ENOENT") {
|
|
1832
|
+
return [];
|
|
1833
|
+
}
|
|
1834
|
+
throw e;
|
|
1835
|
+
}
|
|
1836
|
+
return result;
|
|
1837
|
+
}
|
|
1838
|
+
// ============================================
|
|
1839
|
+
// Atomic Operations
|
|
1840
|
+
// ============================================
|
|
1841
|
+
async incr(key) {
|
|
1842
|
+
return this.incrBy(key, 1);
|
|
1843
|
+
}
|
|
1844
|
+
async decr(key) {
|
|
1845
|
+
return this.incrBy(key, -1);
|
|
1846
|
+
}
|
|
1847
|
+
async incrBy(key, amount) {
|
|
1848
|
+
this.ensureConnected();
|
|
1849
|
+
const filePath = this.keyToPath(key);
|
|
1850
|
+
const entry = await this.readEntry(filePath);
|
|
1851
|
+
let currentValue = 0;
|
|
1852
|
+
if (entry && !this.isExpired(entry)) {
|
|
1853
|
+
const parsed = parseInt(entry.value, 10);
|
|
1854
|
+
if (isNaN(parsed)) {
|
|
1855
|
+
throw new StorageOperationError("incrBy", key, "Value is not an integer");
|
|
1856
|
+
}
|
|
1857
|
+
currentValue = parsed;
|
|
1858
|
+
}
|
|
1859
|
+
const newValue = currentValue + amount;
|
|
1860
|
+
const newEntry = { value: String(newValue) };
|
|
1861
|
+
if (entry?.expiresAt && !this.isExpired(entry)) {
|
|
1862
|
+
newEntry.expiresAt = entry.expiresAt;
|
|
1863
|
+
}
|
|
1864
|
+
await this.atomicWrite(filePath, newEntry);
|
|
1865
|
+
return newValue;
|
|
1866
|
+
}
|
|
1867
|
+
// ============================================
|
|
1868
|
+
// Internal Helpers
|
|
1869
|
+
// ============================================
|
|
1870
|
+
/**
|
|
1871
|
+
* Convert a storage key to a file path.
|
|
1872
|
+
* Sanitizes the key to be filesystem-safe.
|
|
1873
|
+
*/
|
|
1874
|
+
keyToPath(key) {
|
|
1875
|
+
const safeKey = this.encodeKey(key);
|
|
1876
|
+
return `${this.baseDir}/${safeKey}${this.extension}`;
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Convert a filename back to a storage key.
|
|
1880
|
+
*/
|
|
1881
|
+
pathToKey(filename) {
|
|
1882
|
+
const safeKey = filename.slice(0, -this.extension.length);
|
|
1883
|
+
return this.decodeKey(safeKey);
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Encode a key to be filesystem-safe.
|
|
1887
|
+
* Uses URL encoding to handle special characters.
|
|
1888
|
+
*/
|
|
1889
|
+
encodeKey(key) {
|
|
1890
|
+
return encodeURIComponent(key).replace(/\./g, "%2E");
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Decode a filesystem-safe key back to original.
|
|
1894
|
+
*/
|
|
1895
|
+
decodeKey(encoded) {
|
|
1896
|
+
return decodeURIComponent(encoded);
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Read an entry from a file.
|
|
1900
|
+
*/
|
|
1901
|
+
async readEntry(filePath) {
|
|
1902
|
+
try {
|
|
1903
|
+
return await readJSON(filePath);
|
|
1904
|
+
} catch {
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Check if an entry is expired.
|
|
1910
|
+
*/
|
|
1911
|
+
isExpired(entry) {
|
|
1912
|
+
return entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Delete a file, returning true if it existed.
|
|
1916
|
+
*/
|
|
1917
|
+
async deleteFile(filePath) {
|
|
1918
|
+
try {
|
|
1919
|
+
await unlink(filePath);
|
|
1920
|
+
return true;
|
|
1921
|
+
} catch (e) {
|
|
1922
|
+
const error = e;
|
|
1923
|
+
if (error.code === "ENOENT") {
|
|
1924
|
+
return false;
|
|
1925
|
+
}
|
|
1926
|
+
throw e;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Atomic write: write to temp file then rename.
|
|
1931
|
+
* This ensures we don't corrupt the file if write is interrupted.
|
|
1932
|
+
*/
|
|
1933
|
+
async atomicWrite(filePath, entry) {
|
|
1934
|
+
const tempSuffix = bytesToHex(randomBytes(8));
|
|
1935
|
+
const tempPath = `${filePath}.${tempSuffix}.tmp`;
|
|
1936
|
+
try {
|
|
1937
|
+
const content = JSON.stringify(entry, null, 2);
|
|
1938
|
+
await writeFile(tempPath, content, { mode: this.fileMode });
|
|
1939
|
+
await rename(tempPath, filePath);
|
|
1940
|
+
} catch (e) {
|
|
1941
|
+
try {
|
|
1942
|
+
await unlink(tempPath);
|
|
1943
|
+
} catch {
|
|
1944
|
+
}
|
|
1945
|
+
throw e;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Get suggestion message for pub/sub not supported error.
|
|
1950
|
+
*/
|
|
1951
|
+
getPubSubSuggestion() {
|
|
1952
|
+
return "FileSystem adapter does not support pub/sub. Use Redis or Memory adapter for pub/sub support.";
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
var indexeddb_exports = {};
|
|
1958
|
+
__export(indexeddb_exports, {
|
|
1959
|
+
IndexedDBStorageAdapter: () => IndexedDBStorageAdapter
|
|
1960
|
+
});
|
|
1961
|
+
var IndexedDBStorageAdapter;
|
|
1962
|
+
var init_indexeddb = __esm({
|
|
1963
|
+
"libs/utils/src/storage/adapters/indexeddb.ts"() {
|
|
1964
|
+
"use strict";
|
|
1965
|
+
init_base();
|
|
1966
|
+
init_pattern();
|
|
1967
|
+
IndexedDBStorageAdapter = class extends BaseStorageAdapter {
|
|
1968
|
+
backendName = "indexeddb";
|
|
1969
|
+
dbName;
|
|
1970
|
+
storeName;
|
|
1971
|
+
prefix;
|
|
1972
|
+
db = null;
|
|
1973
|
+
constructor(options) {
|
|
1974
|
+
super();
|
|
1975
|
+
this.dbName = options?.dbName ?? "frontmcp";
|
|
1976
|
+
this.storeName = options?.storeName ?? "kv";
|
|
1977
|
+
this.prefix = options?.prefix ?? "frontmcp:";
|
|
1978
|
+
}
|
|
1979
|
+
assertAvailable() {
|
|
1980
|
+
if (typeof indexedDB === "undefined") {
|
|
1981
|
+
throw new Error("IndexedDB is not available in this environment");
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
key(k) {
|
|
1985
|
+
return `${this.prefix}${k}`;
|
|
1986
|
+
}
|
|
1987
|
+
stripPrefix(fullKey) {
|
|
1988
|
+
return fullKey.slice(this.prefix.length);
|
|
1989
|
+
}
|
|
1990
|
+
// ============================================
|
|
1991
|
+
// Connection Lifecycle
|
|
1992
|
+
// ============================================
|
|
1993
|
+
async connect() {
|
|
1994
|
+
this.assertAvailable();
|
|
1995
|
+
if (this.db) {
|
|
1996
|
+
this.connected = true;
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
this.db = await new Promise((resolve2, reject) => {
|
|
2000
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
2001
|
+
request.onupgradeneeded = () => {
|
|
2002
|
+
const db = request.result;
|
|
2003
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
2004
|
+
db.createObjectStore(this.storeName, { keyPath: "k" });
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
request.onsuccess = () => resolve2(request.result);
|
|
2008
|
+
request.onerror = () => reject(request.error);
|
|
2009
|
+
});
|
|
2010
|
+
this.connected = true;
|
|
2011
|
+
}
|
|
2012
|
+
async disconnect() {
|
|
2013
|
+
if (this.db) {
|
|
2014
|
+
this.db.close();
|
|
2015
|
+
this.db = null;
|
|
2016
|
+
}
|
|
2017
|
+
this.connected = false;
|
|
2018
|
+
}
|
|
2019
|
+
async ping() {
|
|
2020
|
+
return this.db !== null && this.connected;
|
|
2021
|
+
}
|
|
2022
|
+
// ============================================
|
|
2023
|
+
// Internal Helpers
|
|
2024
|
+
// ============================================
|
|
2025
|
+
getStore(mode) {
|
|
2026
|
+
if (!this.db) {
|
|
2027
|
+
throw new Error("IndexedDB adapter is not connected. Call connect() first.");
|
|
2028
|
+
}
|
|
2029
|
+
const tx = this.db.transaction(this.storeName, mode);
|
|
2030
|
+
return tx.objectStore(this.storeName);
|
|
2031
|
+
}
|
|
2032
|
+
idbRequest(request) {
|
|
2033
|
+
return new Promise((resolve2, reject) => {
|
|
2034
|
+
request.onsuccess = () => resolve2(request.result);
|
|
2035
|
+
request.onerror = () => reject(request.error);
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
idbTransaction(store) {
|
|
2039
|
+
return new Promise((resolve2, reject) => {
|
|
2040
|
+
const tx = store.transaction;
|
|
2041
|
+
tx.oncomplete = () => resolve2();
|
|
2042
|
+
tx.onerror = () => reject(tx.error);
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
// ============================================
|
|
2046
|
+
// Core Operations
|
|
2047
|
+
// ============================================
|
|
2048
|
+
async get(key) {
|
|
2049
|
+
this.ensureConnected();
|
|
2050
|
+
const store = this.getStore("readonly");
|
|
2051
|
+
const entry = await this.idbRequest(store.get(this.key(key)));
|
|
2052
|
+
if (!entry) return null;
|
|
2053
|
+
if (entry.e && entry.e < Date.now()) {
|
|
2054
|
+
const writeStore = this.getStore("readwrite");
|
|
2055
|
+
writeStore.delete(this.key(key));
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
return entry.v;
|
|
2059
|
+
}
|
|
2060
|
+
async doSet(key, value, options) {
|
|
2061
|
+
if (options?.ifNotExists) {
|
|
2062
|
+
const existing = await this.get(key);
|
|
2063
|
+
if (existing !== null) return;
|
|
2064
|
+
}
|
|
2065
|
+
if (options?.ifExists) {
|
|
2066
|
+
const existing = await this.get(key);
|
|
2067
|
+
if (existing === null) return;
|
|
2068
|
+
}
|
|
2069
|
+
const entry = { k: this.key(key), v: value };
|
|
2070
|
+
if (options?.ttlSeconds) {
|
|
2071
|
+
entry.e = Date.now() + options.ttlSeconds * 1e3;
|
|
2072
|
+
}
|
|
2073
|
+
const store = this.getStore("readwrite");
|
|
2074
|
+
await this.idbRequest(store.put(entry));
|
|
2075
|
+
}
|
|
2076
|
+
async delete(key) {
|
|
2077
|
+
this.ensureConnected();
|
|
2078
|
+
const existing = await this.get(key);
|
|
2079
|
+
if (existing === null) return false;
|
|
2080
|
+
const store = this.getStore("readwrite");
|
|
2081
|
+
await this.idbRequest(store.delete(this.key(key)));
|
|
2082
|
+
return true;
|
|
2083
|
+
}
|
|
2084
|
+
async exists(key) {
|
|
2085
|
+
return await this.get(key) !== null;
|
|
2086
|
+
}
|
|
2087
|
+
// ============================================
|
|
2088
|
+
// TTL Operations
|
|
2089
|
+
// ============================================
|
|
2090
|
+
async expire(key, ttlSeconds) {
|
|
2091
|
+
this.ensureConnected();
|
|
2092
|
+
const value = await this.get(key);
|
|
2093
|
+
if (value === null) return false;
|
|
2094
|
+
await this.doSet(key, value, { ttlSeconds });
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
async ttl(key) {
|
|
2098
|
+
this.ensureConnected();
|
|
2099
|
+
const store = this.getStore("readonly");
|
|
2100
|
+
const entry = await this.idbRequest(store.get(this.key(key)));
|
|
2101
|
+
if (!entry) return null;
|
|
2102
|
+
if (entry.e) {
|
|
2103
|
+
const remaining = Math.ceil((entry.e - Date.now()) / 1e3);
|
|
2104
|
+
return remaining > 0 ? remaining : null;
|
|
2105
|
+
}
|
|
2106
|
+
return -1;
|
|
2107
|
+
}
|
|
2108
|
+
// ============================================
|
|
2109
|
+
// Key Enumeration
|
|
2110
|
+
// ============================================
|
|
2111
|
+
async keys(pattern) {
|
|
2112
|
+
this.ensureConnected();
|
|
2113
|
+
const store = this.getStore("readonly");
|
|
2114
|
+
const allKeys = await this.idbRequest(store.getAllKeys());
|
|
2115
|
+
const now = Date.now();
|
|
2116
|
+
const result = [];
|
|
2117
|
+
for (const fullKey of allKeys) {
|
|
2118
|
+
const keyStr = String(fullKey);
|
|
2119
|
+
if (!keyStr.startsWith(this.prefix)) continue;
|
|
2120
|
+
const key = this.stripPrefix(keyStr);
|
|
2121
|
+
if (pattern && pattern !== "*" && !this.matchPattern(key, pattern)) continue;
|
|
2122
|
+
const entry = await this.idbRequest(store.get(keyStr));
|
|
2123
|
+
if (entry && (!entry.e || entry.e >= now)) {
|
|
2124
|
+
result.push(key);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return result;
|
|
2128
|
+
}
|
|
2129
|
+
// ============================================
|
|
2130
|
+
// Atomic Operations
|
|
2131
|
+
// ============================================
|
|
2132
|
+
async incr(key) {
|
|
2133
|
+
return this.incrBy(key, 1);
|
|
2134
|
+
}
|
|
2135
|
+
async decr(key) {
|
|
2136
|
+
return this.incrBy(key, -1);
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Increment by amount. NOT atomic — uses read-then-write which can race
|
|
2140
|
+
* under concurrent access (e.g., multiple browser tabs).
|
|
2141
|
+
*/
|
|
2142
|
+
async incrBy(key, amount) {
|
|
2143
|
+
this.ensureConnected();
|
|
2144
|
+
const current = await this.get(key);
|
|
2145
|
+
const value = current !== null ? parseInt(current, 10) + amount : amount;
|
|
2146
|
+
if (Number.isNaN(value)) {
|
|
2147
|
+
throw new Error(`Value at "${key}" is not an integer`);
|
|
2148
|
+
}
|
|
2149
|
+
await this.doSet(key, String(value));
|
|
2150
|
+
return value;
|
|
2151
|
+
}
|
|
2152
|
+
// ============================================
|
|
2153
|
+
// Pattern Matching
|
|
2154
|
+
// ============================================
|
|
2155
|
+
matchPattern(key, pattern) {
|
|
2156
|
+
return matchesPattern(key, pattern);
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
var localstorage_exports = {};
|
|
2162
|
+
__export(localstorage_exports, {
|
|
2163
|
+
LocalStorageAdapter: () => LocalStorageAdapter
|
|
2164
|
+
});
|
|
2165
|
+
var LocalStorageAdapter;
|
|
2166
|
+
var init_localstorage = __esm({
|
|
2167
|
+
"libs/utils/src/storage/adapters/localstorage.ts"() {
|
|
2168
|
+
"use strict";
|
|
2169
|
+
init_base();
|
|
2170
|
+
init_crypto();
|
|
2171
|
+
init_pattern();
|
|
2172
|
+
LocalStorageAdapter = class extends BaseStorageAdapter {
|
|
2173
|
+
backendName = "localstorage";
|
|
2174
|
+
prefix;
|
|
2175
|
+
encryptionKey;
|
|
2176
|
+
allowPlaintext;
|
|
2177
|
+
encoder = new TextEncoder();
|
|
2178
|
+
decoder = new TextDecoder();
|
|
2179
|
+
constructor(options) {
|
|
2180
|
+
super();
|
|
2181
|
+
this.prefix = options?.prefix ?? "frontmcp:";
|
|
2182
|
+
this.allowPlaintext = options?.allowPlaintext ?? true;
|
|
2183
|
+
if (options?.encryptionKey) {
|
|
2184
|
+
if (options.encryptionKey.length !== 32) {
|
|
2185
|
+
throw new Error(`encryptionKey must be exactly 32 bytes, got ${options.encryptionKey.length}`);
|
|
2186
|
+
}
|
|
2187
|
+
this.encryptionKey = options.encryptionKey;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
assertAvailable() {
|
|
2191
|
+
if (typeof localStorage === "undefined") {
|
|
2192
|
+
throw new Error("localStorage is not available in this environment");
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
key(k) {
|
|
2196
|
+
return `${this.prefix}${k}`;
|
|
2197
|
+
}
|
|
2198
|
+
stripPrefix(fullKey) {
|
|
2199
|
+
return fullKey.slice(this.prefix.length);
|
|
2200
|
+
}
|
|
2201
|
+
async connect() {
|
|
2202
|
+
this.assertAvailable();
|
|
2203
|
+
const probeKey = this.key("__probe__");
|
|
2204
|
+
try {
|
|
2205
|
+
localStorage.setItem(probeKey, "1");
|
|
2206
|
+
const read = localStorage.getItem(probeKey);
|
|
2207
|
+
if (read !== "1") {
|
|
2208
|
+
throw new Error("localStorage probe: read-back mismatch");
|
|
2209
|
+
}
|
|
2210
|
+
} catch (err) {
|
|
2211
|
+
throw new Error(`localStorage is not writable: ${err instanceof Error ? err.message : String(err)}`);
|
|
2212
|
+
} finally {
|
|
2213
|
+
try {
|
|
2214
|
+
localStorage.removeItem(probeKey);
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
this.connected = true;
|
|
2219
|
+
}
|
|
2220
|
+
async disconnect() {
|
|
2221
|
+
this.connected = false;
|
|
2222
|
+
}
|
|
2223
|
+
async ping() {
|
|
2224
|
+
const probeKey = this.key("__probe__");
|
|
2225
|
+
try {
|
|
2226
|
+
this.assertAvailable();
|
|
2227
|
+
localStorage.setItem(probeKey, "1");
|
|
2228
|
+
return localStorage.getItem(probeKey) === "1";
|
|
2229
|
+
} catch {
|
|
2230
|
+
return false;
|
|
2231
|
+
} finally {
|
|
2232
|
+
try {
|
|
2233
|
+
localStorage.removeItem(probeKey);
|
|
2234
|
+
} catch {
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
async get(key) {
|
|
2239
|
+
const raw = localStorage.getItem(this.key(key));
|
|
2240
|
+
if (raw === null) return null;
|
|
2241
|
+
try {
|
|
2242
|
+
const entry = JSON.parse(raw);
|
|
2243
|
+
if (entry.e && entry.e < Date.now()) {
|
|
2244
|
+
localStorage.removeItem(this.key(key));
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
if (entry._enc) {
|
|
2248
|
+
if (!this.encryptionKey) return null;
|
|
2249
|
+
const iv = base64urlDecode(entry._enc.iv);
|
|
2250
|
+
const tag = base64urlDecode(entry._enc.tag);
|
|
2251
|
+
const ciphertext = base64urlDecode(entry._enc.data);
|
|
2252
|
+
const plainBytes = decryptAesGcm(this.encryptionKey, ciphertext, iv, tag);
|
|
2253
|
+
return this.decoder.decode(plainBytes);
|
|
2254
|
+
}
|
|
2255
|
+
return entry.v;
|
|
2256
|
+
} catch {
|
|
2257
|
+
return null;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
async set(key, value, options) {
|
|
2261
|
+
if (options?.ifNotExists) {
|
|
2262
|
+
const existing = await this.get(key);
|
|
2263
|
+
if (existing !== null) return;
|
|
2264
|
+
}
|
|
2265
|
+
if (options?.ifExists) {
|
|
2266
|
+
const existing = await this.get(key);
|
|
2267
|
+
if (existing === null) return;
|
|
2268
|
+
}
|
|
2269
|
+
await this.doSet(key, value, options);
|
|
2270
|
+
}
|
|
2271
|
+
async doSet(key, value, options) {
|
|
2272
|
+
const entry = { v: "" };
|
|
2273
|
+
if (options?.ttlSeconds) {
|
|
2274
|
+
entry.e = Date.now() + options.ttlSeconds * 1e3;
|
|
2275
|
+
}
|
|
2276
|
+
if (this.encryptionKey) {
|
|
2277
|
+
const iv = randomBytes(12);
|
|
2278
|
+
const plainBytes = this.encoder.encode(value);
|
|
2279
|
+
const { ciphertext, tag } = encryptAesGcm(this.encryptionKey, plainBytes, iv);
|
|
2280
|
+
entry._enc = {
|
|
2281
|
+
alg: "A256GCM",
|
|
2282
|
+
iv: base64urlEncode(iv),
|
|
2283
|
+
tag: base64urlEncode(tag),
|
|
2284
|
+
data: base64urlEncode(ciphertext)
|
|
2285
|
+
};
|
|
2286
|
+
} else {
|
|
2287
|
+
if (!this.allowPlaintext) {
|
|
2288
|
+
throw new Error("Plaintext storage is disabled. Provide an encryptionKey or set allowPlaintext: true.");
|
|
2289
|
+
}
|
|
2290
|
+
entry.v = value;
|
|
2291
|
+
}
|
|
2292
|
+
localStorage.setItem(this.key(key), JSON.stringify(entry));
|
|
2293
|
+
}
|
|
2294
|
+
async delete(key) {
|
|
2295
|
+
const existed = await this.get(key) !== null;
|
|
2296
|
+
localStorage.removeItem(this.key(key));
|
|
2297
|
+
return existed;
|
|
2298
|
+
}
|
|
2299
|
+
async exists(key) {
|
|
2300
|
+
return await this.get(key) !== null;
|
|
2301
|
+
}
|
|
2302
|
+
async mget(keys) {
|
|
2303
|
+
return Promise.all(keys.map((k) => this.get(k)));
|
|
2304
|
+
}
|
|
2305
|
+
async mset(entries) {
|
|
2306
|
+
for (const entry of entries) {
|
|
2307
|
+
await this.set(entry.key, entry.value, entry.options);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
async mdelete(keys) {
|
|
2311
|
+
let count = 0;
|
|
2312
|
+
for (const key of keys) {
|
|
2313
|
+
if (await this.delete(key)) count++;
|
|
2314
|
+
}
|
|
2315
|
+
return count;
|
|
2316
|
+
}
|
|
2317
|
+
async expire(key, ttlSeconds) {
|
|
2318
|
+
const value = await this.get(key);
|
|
2319
|
+
if (value === null) return false;
|
|
2320
|
+
await this.set(key, value, { ttlSeconds });
|
|
2321
|
+
return true;
|
|
2322
|
+
}
|
|
2323
|
+
async ttl(key) {
|
|
2324
|
+
const raw = localStorage.getItem(this.key(key));
|
|
2325
|
+
if (raw === null) return null;
|
|
2326
|
+
try {
|
|
2327
|
+
const entry = JSON.parse(raw);
|
|
2328
|
+
if (!entry.e) return -1;
|
|
2329
|
+
const remaining = Math.ceil((entry.e - Date.now()) / 1e3);
|
|
2330
|
+
return remaining > 0 ? remaining : null;
|
|
2331
|
+
} catch {
|
|
2332
|
+
return null;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
async keys(pattern) {
|
|
2336
|
+
const result = [];
|
|
2337
|
+
const candidates = [];
|
|
2338
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2339
|
+
const fullKey = localStorage.key(i);
|
|
2340
|
+
if (fullKey && fullKey.startsWith(this.prefix)) {
|
|
2341
|
+
candidates.push(this.stripPrefix(fullKey));
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
for (const key of candidates) {
|
|
2345
|
+
if (!pattern || pattern === "*" || this.matchPattern(key, pattern)) {
|
|
2346
|
+
if (await this.get(key) !== null) {
|
|
2347
|
+
result.push(key);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
return result;
|
|
2352
|
+
}
|
|
2353
|
+
async count(pattern) {
|
|
2354
|
+
return (await this.keys(pattern)).length;
|
|
2355
|
+
}
|
|
2356
|
+
async incr(key) {
|
|
2357
|
+
return this.incrBy(key, 1);
|
|
2358
|
+
}
|
|
2359
|
+
async decr(key) {
|
|
2360
|
+
return this.incrBy(key, -1);
|
|
2361
|
+
}
|
|
2362
|
+
async incrBy(key, amount) {
|
|
2363
|
+
if (!Number.isInteger(amount)) {
|
|
2364
|
+
throw new Error(`amount must be an integer, got ${amount}`);
|
|
2365
|
+
}
|
|
2366
|
+
const current = await this.get(key);
|
|
2367
|
+
if (current !== null && !/^-?\d+$/.test(current)) {
|
|
2368
|
+
throw new Error(`Value at "${key}" is not an integer`);
|
|
2369
|
+
}
|
|
2370
|
+
const value = current !== null ? parseInt(current, 10) + amount : amount;
|
|
2371
|
+
await this.set(key, String(value));
|
|
2372
|
+
return value;
|
|
2373
|
+
}
|
|
2374
|
+
async publish() {
|
|
2375
|
+
throw new Error("LocalStorage adapter does not support pub/sub");
|
|
2376
|
+
}
|
|
2377
|
+
async subscribe(_channel, _handler) {
|
|
2378
|
+
throw new Error("LocalStorage adapter does not support pub/sub");
|
|
2379
|
+
}
|
|
2380
|
+
supportsPubSub() {
|
|
2381
|
+
return false;
|
|
2382
|
+
}
|
|
2383
|
+
matchPattern(key, pattern) {
|
|
2384
|
+
return matchesPattern(key, pattern);
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
var DEFAULT_BASE_DIR;
|
|
2390
|
+
var LS_HKDF_IKM;
|
|
2391
|
+
var INSTALL_SALT_KEY;
|
|
2392
|
+
var LEGACY_SALT_KEY;
|
|
2393
|
+
var init_factory = __esm({
|
|
2394
|
+
"libs/utils/src/crypto/key-persistence/factory.ts"() {
|
|
2395
|
+
"use strict";
|
|
2396
|
+
init_runtime();
|
|
2397
|
+
init_memory();
|
|
2398
|
+
init_key_persistence();
|
|
2399
|
+
init_crypto();
|
|
2400
|
+
DEFAULT_BASE_DIR = ".frontmcp/keys";
|
|
2401
|
+
LS_HKDF_IKM = new TextEncoder().encode("frontmcp:localstorage:v1");
|
|
2402
|
+
INSTALL_SALT_KEY = "__frontmcp_internal__:install_salt";
|
|
2403
|
+
LEGACY_SALT_KEY = "frontmcp:_install_salt";
|
|
2404
|
+
}
|
|
2405
|
+
});
|
|
2406
|
+
var init_key_persistence2 = __esm({
|
|
2407
|
+
"libs/utils/src/crypto/key-persistence/index.ts"() {
|
|
2408
|
+
"use strict";
|
|
2409
|
+
init_schemas();
|
|
2410
|
+
init_key_persistence();
|
|
2411
|
+
init_factory();
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
var node_exports = {};
|
|
2415
|
+
__export(node_exports, {
|
|
2416
|
+
createSignedJwt: () => createSignedJwt,
|
|
2417
|
+
cryptoProvider: () => nodeCrypto,
|
|
2418
|
+
generateRsaKeyPair: () => generateRsaKeyPair,
|
|
2419
|
+
isRsaPssAlg: () => isRsaPssAlg,
|
|
2420
|
+
jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
|
|
2421
|
+
nodeCrypto: () => nodeCrypto,
|
|
2422
|
+
pemToPublicJwk: () => pemToPublicJwk,
|
|
2423
|
+
rsaSign: () => rsaSign,
|
|
2424
|
+
rsaSignBase64Url: () => rsaSignBase64Url,
|
|
2425
|
+
rsaVerify: () => rsaVerify,
|
|
2426
|
+
rsaVerifySync: () => rsaVerifySync
|
|
2427
|
+
});
|
|
2428
|
+
function toUint8Array(buf) {
|
|
2429
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
2430
|
+
}
|
|
2431
|
+
function toBuffer(data) {
|
|
2432
|
+
if (typeof data === "string") {
|
|
2433
|
+
return Buffer.from(data, "utf8");
|
|
2434
|
+
}
|
|
2435
|
+
return Buffer.from(data);
|
|
2436
|
+
}
|
|
2437
|
+
function generateRsaKeyPair(modulusLength = 2048, alg = "RS256") {
|
|
2438
|
+
const kid = `rsa-key-${Date.now()}-${crypto2.randomBytes(8).toString("hex")}`;
|
|
2439
|
+
const { privateKey, publicKey } = crypto2.generateKeyPairSync("rsa", {
|
|
2440
|
+
modulusLength
|
|
2441
|
+
});
|
|
2442
|
+
const exported = publicKey.export({ format: "jwk" });
|
|
2443
|
+
const publicJwk = {
|
|
2444
|
+
...exported,
|
|
2445
|
+
kid,
|
|
2446
|
+
alg,
|
|
2447
|
+
use: "sig",
|
|
2448
|
+
kty: "RSA"
|
|
2449
|
+
};
|
|
2450
|
+
return { privateKey, publicKey, publicJwk };
|
|
2451
|
+
}
|
|
2452
|
+
function rsaSign(algorithm, data, privateKey, options) {
|
|
2453
|
+
const signingKey = options ? { key: privateKey, ...options } : privateKey;
|
|
2454
|
+
return crypto2.sign(algorithm, data, signingKey);
|
|
2455
|
+
}
|
|
2456
|
+
function rsaVerify(jwtAlg, data, publicJwk, signature) {
|
|
2457
|
+
const publicKey = crypto2.createPublicKey({ key: publicJwk, format: "jwk" });
|
|
2458
|
+
const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
|
|
2459
|
+
const verifyKey = isRsaPssAlg(jwtAlg) ? {
|
|
2460
|
+
key: publicKey,
|
|
2461
|
+
padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
|
|
2462
|
+
saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
|
|
2463
|
+
} : publicKey;
|
|
2464
|
+
return crypto2.verify(nodeAlgorithm, data, verifyKey, signature);
|
|
2465
|
+
}
|
|
2466
|
+
function rsaSignBase64Url(jwtAlg, data, privateJwk) {
|
|
2467
|
+
const privateKey = crypto2.createPrivateKey({ key: privateJwk, format: "jwk" });
|
|
2468
|
+
const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
|
|
2469
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
2470
|
+
const sig = isRsaPssAlg(jwtAlg) ? rsaSign(nodeAlgorithm, buf, privateKey, {
|
|
2471
|
+
padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
|
|
2472
|
+
saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
|
|
2473
|
+
}) : rsaSign(nodeAlgorithm, buf, privateKey);
|
|
2474
|
+
return sig.toString("base64url");
|
|
2475
|
+
}
|
|
2476
|
+
function rsaVerifySync(jwtAlg, data, publicJwk, signature) {
|
|
2477
|
+
try {
|
|
2478
|
+
const publicKey = crypto2.createPublicKey({ key: publicJwk, format: "jwk" });
|
|
2479
|
+
const dataBuf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
2480
|
+
const sigBuf = Buffer.isBuffer(signature) ? signature : Buffer.from(signature);
|
|
2481
|
+
if (jwtAlg === "EdDSA") {
|
|
2482
|
+
return crypto2.verify(null, dataBuf, publicKey, sigBuf);
|
|
2483
|
+
}
|
|
2484
|
+
const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
|
|
2485
|
+
const verifyKey = isRsaPssAlg(jwtAlg) ? {
|
|
2486
|
+
key: publicKey,
|
|
2487
|
+
padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
|
|
2488
|
+
saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
|
|
2489
|
+
} : publicKey;
|
|
2490
|
+
return crypto2.verify(nodeAlgorithm, dataBuf, verifyKey, sigBuf);
|
|
2491
|
+
} catch {
|
|
2492
|
+
return false;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
function pemToPublicJwk(pem) {
|
|
2496
|
+
const key = crypto2.createPublicKey({ key: pem, format: "pem" });
|
|
2497
|
+
return key.export({ format: "jwk" });
|
|
2498
|
+
}
|
|
2499
|
+
function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
|
|
2500
|
+
const header = { alg, typ: "JWT", kid };
|
|
2501
|
+
const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
2502
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
2503
|
+
const signatureInput = `${headerB64}.${payloadB64}`;
|
|
2504
|
+
const nodeAlgorithm = jwtAlgToNodeAlg(alg);
|
|
2505
|
+
const signature = rsaSign(
|
|
2506
|
+
nodeAlgorithm,
|
|
2507
|
+
Buffer.from(signatureInput),
|
|
2508
|
+
privateKey,
|
|
2509
|
+
isRsaPssAlg(alg) ? {
|
|
2510
|
+
padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
|
|
2511
|
+
saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
|
|
2512
|
+
} : void 0
|
|
2513
|
+
);
|
|
2514
|
+
const signatureB64 = signature.toString("base64url");
|
|
2515
|
+
return `${headerB64}.${payloadB64}.${signatureB64}`;
|
|
2516
|
+
}
|
|
2517
|
+
var nodeCrypto;
|
|
2518
|
+
var init_node = __esm({
|
|
2519
|
+
"libs/utils/src/crypto/node.ts"() {
|
|
2520
|
+
"use strict";
|
|
2521
|
+
init_jwt_alg();
|
|
2522
|
+
init_jwt_alg();
|
|
2523
|
+
nodeCrypto = {
|
|
2524
|
+
randomUUID() {
|
|
2525
|
+
return crypto2.randomUUID();
|
|
2526
|
+
},
|
|
2527
|
+
randomBytes(length) {
|
|
2528
|
+
return toUint8Array(crypto2.randomBytes(length));
|
|
2529
|
+
},
|
|
2530
|
+
sha256(data) {
|
|
2531
|
+
const hash = crypto2.createHash("sha256").update(toBuffer(data)).digest();
|
|
2532
|
+
return toUint8Array(hash);
|
|
2533
|
+
},
|
|
2534
|
+
sha256Hex(data) {
|
|
2535
|
+
return crypto2.createHash("sha256").update(toBuffer(data)).digest("hex");
|
|
2536
|
+
},
|
|
2537
|
+
hmacSha256(key, data) {
|
|
2538
|
+
const hmac2 = crypto2.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
|
|
2539
|
+
return toUint8Array(hmac2);
|
|
2540
|
+
},
|
|
2541
|
+
hkdfSha256(ikm, salt, info, length) {
|
|
2542
|
+
const ikmBuf = Buffer.from(ikm);
|
|
2543
|
+
const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
|
|
2544
|
+
const prk = crypto2.createHmac("sha256", saltBuf).update(ikmBuf).digest();
|
|
2545
|
+
const hashLen = 32;
|
|
2546
|
+
const n = Math.ceil(length / hashLen);
|
|
2547
|
+
const chunks = [];
|
|
2548
|
+
let prev = Buffer.alloc(0);
|
|
2549
|
+
for (let i = 1; i <= n; i++) {
|
|
2550
|
+
prev = crypto2.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
|
|
2551
|
+
chunks.push(prev);
|
|
2552
|
+
}
|
|
2553
|
+
return toUint8Array(Buffer.concat(chunks).subarray(0, length));
|
|
2554
|
+
},
|
|
2555
|
+
encryptAesGcm(key, plaintext, iv) {
|
|
2556
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
|
|
2557
|
+
const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
|
|
2558
|
+
const tag = cipher.getAuthTag();
|
|
2559
|
+
return {
|
|
2560
|
+
ciphertext: toUint8Array(encrypted),
|
|
2561
|
+
tag: toUint8Array(tag)
|
|
2562
|
+
};
|
|
2563
|
+
},
|
|
2564
|
+
decryptAesGcm(key, ciphertext, iv, tag) {
|
|
2565
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
|
|
2566
|
+
decipher.setAuthTag(Buffer.from(tag));
|
|
2567
|
+
const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
|
|
2568
|
+
return toUint8Array(decrypted);
|
|
2569
|
+
},
|
|
2570
|
+
timingSafeEqual(a, b) {
|
|
2571
|
+
if (a.length !== b.length) return false;
|
|
2572
|
+
return crypto2.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
2573
|
+
}
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
});
|
|
2577
|
+
var browser_exports = {};
|
|
2578
|
+
__export(browser_exports, {
|
|
2579
|
+
browserCrypto: () => browserCrypto,
|
|
2580
|
+
cryptoProvider: () => browserCrypto,
|
|
2581
|
+
isRsaPssAlg: () => isRsaPssAlg,
|
|
2582
|
+
jwtAlgToWebCryptoAlg: () => jwtAlgToWebCryptoAlg,
|
|
2583
|
+
rsaVerifyBrowser: () => rsaVerifyBrowser
|
|
2584
|
+
});
|
|
2585
|
+
function toBytes(data) {
|
|
2586
|
+
if (typeof data === "string") {
|
|
2587
|
+
return new TextEncoder().encode(data);
|
|
2588
|
+
}
|
|
2589
|
+
return data;
|
|
2590
|
+
}
|
|
2591
|
+
function toHex(bytes) {
|
|
2592
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2593
|
+
}
|
|
2594
|
+
function generateUUID() {
|
|
2595
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
2596
|
+
return crypto.randomUUID();
|
|
2597
|
+
}
|
|
2598
|
+
const bytes = nobleRandomBytes(16);
|
|
2599
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
2600
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
2601
|
+
const hex = toHex(bytes);
|
|
2602
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
2603
|
+
}
|
|
2604
|
+
function constantTimeEqual(a, b) {
|
|
2605
|
+
if (a.length !== b.length) return false;
|
|
2606
|
+
let result = 0;
|
|
2607
|
+
for (let i = 0; i < a.length; i++) {
|
|
2608
|
+
result |= a[i] ^ b[i];
|
|
2609
|
+
}
|
|
2610
|
+
return result === 0;
|
|
2611
|
+
}
|
|
2612
|
+
async function rsaVerifyBrowser(jwtAlg, data, publicJwk, signature) {
|
|
2613
|
+
if (typeof globalThis.crypto?.subtle === "undefined") {
|
|
2614
|
+
throw new Error("WebCrypto API (crypto.subtle) is not available in this environment");
|
|
2615
|
+
}
|
|
2616
|
+
const webAlg = jwtAlgToWebCryptoAlg(jwtAlg);
|
|
2617
|
+
const isPss = isRsaPssAlg(jwtAlg);
|
|
2618
|
+
const algorithm = {
|
|
2619
|
+
name: isPss ? "RSA-PSS" : "RSASSA-PKCS1-v1_5",
|
|
2620
|
+
hash: { name: webAlg }
|
|
2621
|
+
};
|
|
2622
|
+
const key = await globalThis.crypto.subtle.importKey("jwk", publicJwk, algorithm, false, ["verify"]);
|
|
2623
|
+
const verifyAlgorithm = isPss ? { name: "RSA-PSS", saltLength: getSaltLength(jwtAlg) } : { name: "RSASSA-PKCS1-v1_5" };
|
|
2624
|
+
const sigBuf = new Uint8Array(signature).buffer;
|
|
2625
|
+
const dataBuf = new Uint8Array(data).buffer;
|
|
2626
|
+
return globalThis.crypto.subtle.verify(verifyAlgorithm, key, sigBuf, dataBuf);
|
|
2627
|
+
}
|
|
2628
|
+
function getSaltLength(jwtAlg) {
|
|
2629
|
+
switch (jwtAlg) {
|
|
2630
|
+
case "PS256":
|
|
2631
|
+
return 32;
|
|
2632
|
+
// SHA-256 digest length
|
|
2633
|
+
case "PS384":
|
|
2634
|
+
return 48;
|
|
2635
|
+
// SHA-384 digest length
|
|
2636
|
+
case "PS512":
|
|
2637
|
+
return 64;
|
|
2638
|
+
// SHA-512 digest length
|
|
2639
|
+
default:
|
|
2640
|
+
return 32;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
var browserCrypto;
|
|
2644
|
+
var init_browser = __esm({
|
|
2645
|
+
"libs/utils/src/crypto/browser.ts"() {
|
|
2646
|
+
"use strict";
|
|
2647
|
+
init_jwt_alg();
|
|
2648
|
+
init_jwt_alg();
|
|
2649
|
+
browserCrypto = {
|
|
2650
|
+
randomUUID() {
|
|
2651
|
+
return generateUUID();
|
|
2652
|
+
},
|
|
2653
|
+
randomBytes(length) {
|
|
2654
|
+
return nobleRandomBytes(length);
|
|
2655
|
+
},
|
|
2656
|
+
sha256(data) {
|
|
2657
|
+
return sha256Hash(toBytes(data));
|
|
2658
|
+
},
|
|
2659
|
+
sha256Hex(data) {
|
|
2660
|
+
return toHex(sha256Hash(toBytes(data)));
|
|
2661
|
+
},
|
|
2662
|
+
hmacSha256(key, data) {
|
|
2663
|
+
return hmac(sha256Hash, key, data);
|
|
2664
|
+
},
|
|
2665
|
+
hkdfSha256(ikm, salt, info, length) {
|
|
2666
|
+
const effectiveSalt = salt.length > 0 ? salt : new Uint8Array(32);
|
|
2667
|
+
return hkdf(sha256Hash, ikm, effectiveSalt, info, length);
|
|
2668
|
+
},
|
|
2669
|
+
encryptAesGcm(key, plaintext, iv) {
|
|
2670
|
+
const cipher = gcm(key, iv);
|
|
2671
|
+
const sealed = cipher.encrypt(plaintext);
|
|
2672
|
+
const ciphertext = sealed.slice(0, -16);
|
|
2673
|
+
const tag = sealed.slice(-16);
|
|
2674
|
+
return { ciphertext, tag };
|
|
2675
|
+
},
|
|
2676
|
+
decryptAesGcm(key, ciphertext, iv, tag) {
|
|
2677
|
+
const cipher = gcm(key, iv);
|
|
2678
|
+
const sealed = new Uint8Array(ciphertext.length + tag.length);
|
|
2679
|
+
sealed.set(ciphertext);
|
|
2680
|
+
sealed.set(tag, ciphertext.length);
|
|
2681
|
+
return cipher.decrypt(sealed);
|
|
2682
|
+
},
|
|
2683
|
+
timingSafeEqual(a, b) {
|
|
2684
|
+
return constantTimeEqual(a, b);
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
function getCrypto() {
|
|
2690
|
+
return import_crypto_provider.cryptoProvider;
|
|
2691
|
+
}
|
|
2692
|
+
function randomUUID() {
|
|
2693
|
+
return getCrypto().randomUUID();
|
|
2694
|
+
}
|
|
2695
|
+
function randomBytes(length) {
|
|
2696
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
2697
|
+
throw new Error(`randomBytes length must be a positive integer, got ${length}`);
|
|
2698
|
+
}
|
|
2699
|
+
return getCrypto().randomBytes(length);
|
|
2700
|
+
}
|
|
2701
|
+
function encryptAesGcm(key, plaintext, iv) {
|
|
2702
|
+
if (key.length !== 32) {
|
|
2703
|
+
throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
|
|
2704
|
+
}
|
|
2705
|
+
if (iv.length !== 12) {
|
|
2706
|
+
throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
|
|
2707
|
+
}
|
|
2708
|
+
return getCrypto().encryptAesGcm(key, plaintext, iv);
|
|
2709
|
+
}
|
|
2710
|
+
function decryptAesGcm(key, ciphertext, iv, tag) {
|
|
2711
|
+
if (key.length !== 32) {
|
|
2712
|
+
throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
|
|
2713
|
+
}
|
|
2714
|
+
if (iv.length !== 12) {
|
|
2715
|
+
throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
|
|
2716
|
+
}
|
|
2717
|
+
if (tag.length !== 16) {
|
|
2718
|
+
throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`);
|
|
2719
|
+
}
|
|
2720
|
+
return getCrypto().decryptAesGcm(key, ciphertext, iv, tag);
|
|
2721
|
+
}
|
|
2722
|
+
function bytesToHex(data) {
|
|
2723
|
+
return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2724
|
+
}
|
|
2725
|
+
function base64urlEncode(data) {
|
|
2726
|
+
let base64;
|
|
2727
|
+
if (typeof Buffer !== "undefined") {
|
|
2728
|
+
base64 = Buffer.from(data).toString("base64");
|
|
2729
|
+
} else {
|
|
2730
|
+
const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
|
|
2731
|
+
base64 = btoa(binString);
|
|
2732
|
+
}
|
|
2733
|
+
const result = base64.replace(/\+/g, "-").replace(/\//g, "_");
|
|
2734
|
+
let end = result.length;
|
|
2735
|
+
while (end > 0 && result[end - 1] === "=") {
|
|
2736
|
+
end--;
|
|
2737
|
+
}
|
|
2738
|
+
return result.slice(0, end);
|
|
2739
|
+
}
|
|
2740
|
+
function base64urlDecode(data) {
|
|
2741
|
+
let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
|
|
2742
|
+
const pad = base64.length % 4;
|
|
2743
|
+
if (pad) {
|
|
2744
|
+
base64 += "=".repeat(4 - pad);
|
|
2745
|
+
}
|
|
2746
|
+
if (typeof Buffer !== "undefined") {
|
|
2747
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
2748
|
+
} else {
|
|
2749
|
+
const binString = atob(base64);
|
|
2750
|
+
return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
var HKDF_SHA256_MAX_LENGTH;
|
|
2754
|
+
var init_crypto = __esm({
|
|
2755
|
+
"libs/utils/src/crypto/index.ts"() {
|
|
2756
|
+
"use strict";
|
|
2757
|
+
init_runtime();
|
|
2758
|
+
init_jwt_alg();
|
|
2759
|
+
init_runtime();
|
|
2760
|
+
init_encrypted_blob();
|
|
2761
|
+
init_pkce2();
|
|
2762
|
+
init_secret_persistence();
|
|
2763
|
+
init_hmac_signing();
|
|
2764
|
+
init_key_persistence2();
|
|
2765
|
+
HKDF_SHA256_MAX_LENGTH = 255 * 32;
|
|
2766
|
+
}
|
|
2767
|
+
});
|
|
2768
|
+
var init_utils = __esm({
|
|
2769
|
+
"libs/utils/src/storage/utils/index.ts"() {
|
|
2770
|
+
"use strict";
|
|
2771
|
+
init_ttl();
|
|
2772
|
+
}
|
|
2773
|
+
});
|
|
2774
|
+
var redis_exports = {};
|
|
2775
|
+
__export(redis_exports, {
|
|
2776
|
+
RedisStorageAdapter: () => RedisStorageAdapter
|
|
2777
|
+
});
|
|
2778
|
+
function getRedisClass() {
|
|
2779
|
+
try {
|
|
2780
|
+
return __require2("ioredis").default || __require2("ioredis");
|
|
2781
|
+
} catch (error) {
|
|
2782
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2783
|
+
if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
|
|
2784
|
+
throw new Error(
|
|
2785
|
+
`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
var RedisStorageAdapter;
|
|
2792
|
+
var init_redis = __esm({
|
|
2793
|
+
"libs/utils/src/storage/adapters/redis.ts"() {
|
|
2794
|
+
"use strict";
|
|
2795
|
+
init_base();
|
|
2796
|
+
init_errors();
|
|
2797
|
+
init_utils();
|
|
2798
|
+
RedisStorageAdapter = class extends BaseStorageAdapter {
|
|
2799
|
+
backendName = "redis";
|
|
2800
|
+
client;
|
|
2801
|
+
subscriber;
|
|
2802
|
+
options;
|
|
2803
|
+
ownsClient;
|
|
2804
|
+
keyPrefix;
|
|
2805
|
+
subscriptionHandlers = /* @__PURE__ */ new Map();
|
|
2806
|
+
constructor(options = {}) {
|
|
2807
|
+
super();
|
|
2808
|
+
const hasClient = options.client !== void 0;
|
|
2809
|
+
const hasConfig = options.config !== void 0 || options.url !== void 0;
|
|
2810
|
+
if (hasClient && hasConfig) {
|
|
2811
|
+
throw new StorageConfigError("redis", 'Cannot specify both "client" and "config"/"url". Use one or the other.');
|
|
2812
|
+
}
|
|
2813
|
+
if (!hasClient && !hasConfig) {
|
|
2814
|
+
const envUrl = process.env["REDIS_URL"] || process.env["REDIS_HOST"];
|
|
2815
|
+
if (envUrl) {
|
|
2816
|
+
options = { ...options, url: envUrl };
|
|
2817
|
+
} else {
|
|
2818
|
+
throw new StorageConfigError(
|
|
2819
|
+
"redis",
|
|
2820
|
+
'Either "client", "config", "url", or REDIS_URL environment variable must be provided.'
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
this.options = options;
|
|
2825
|
+
this.ownsClient = !hasClient;
|
|
2826
|
+
this.keyPrefix = options.keyPrefix ?? "";
|
|
2827
|
+
}
|
|
2828
|
+
// ============================================
|
|
2829
|
+
// Connection Lifecycle
|
|
2830
|
+
// ============================================
|
|
2831
|
+
async connect() {
|
|
2832
|
+
if (this.connected) return;
|
|
2833
|
+
try {
|
|
2834
|
+
if (this.options.client) {
|
|
2835
|
+
this.client = this.options.client;
|
|
2836
|
+
} else {
|
|
2837
|
+
const RedisClass = getRedisClass();
|
|
2838
|
+
if (this.options.url) {
|
|
2839
|
+
this.client = new RedisClass(this.options.url, this.buildRedisOptions());
|
|
2840
|
+
} else {
|
|
2841
|
+
this.client = new RedisClass(this.buildRedisOptions());
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
await this.client.ping();
|
|
2845
|
+
this.connected = true;
|
|
2846
|
+
} catch (e) {
|
|
2847
|
+
throw new StorageConnectionError("Failed to connect to Redis", e instanceof Error ? e : void 0, "redis");
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
async disconnect() {
|
|
2851
|
+
if (!this.connected) return;
|
|
2852
|
+
if (this.subscriber) {
|
|
2853
|
+
await this.subscriber.quit();
|
|
2854
|
+
this.subscriber = void 0;
|
|
2855
|
+
}
|
|
2856
|
+
if (this.ownsClient && this.client) {
|
|
2857
|
+
await this.client.quit();
|
|
2858
|
+
}
|
|
2859
|
+
this.client = void 0;
|
|
2860
|
+
this.connected = false;
|
|
2861
|
+
this.subscriptionHandlers.clear();
|
|
2862
|
+
}
|
|
2863
|
+
async ping() {
|
|
2864
|
+
if (!this.client) return false;
|
|
2865
|
+
try {
|
|
2866
|
+
const result = await this.client.ping();
|
|
2867
|
+
return result === "PONG";
|
|
2868
|
+
} catch {
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
// ============================================
|
|
2873
|
+
// Connection Helpers
|
|
2874
|
+
// ============================================
|
|
2875
|
+
/**
|
|
2876
|
+
* Get the connected Redis client, throwing if not connected.
|
|
2877
|
+
*/
|
|
2878
|
+
getConnectedClient() {
|
|
2879
|
+
this.ensureConnected();
|
|
2880
|
+
if (!this.client) {
|
|
2881
|
+
throw new StorageConnectionError("Redis client not connected", void 0, "redis");
|
|
2882
|
+
}
|
|
2883
|
+
return this.client;
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Get the connected Redis subscriber, throwing if not created.
|
|
2887
|
+
*/
|
|
2888
|
+
getConnectedSubscriber() {
|
|
2889
|
+
if (!this.subscriber) {
|
|
2890
|
+
throw new StorageConnectionError("Redis subscriber not created", void 0, "redis");
|
|
2891
|
+
}
|
|
2892
|
+
return this.subscriber;
|
|
2893
|
+
}
|
|
2894
|
+
// ============================================
|
|
2895
|
+
// Core Operations
|
|
2896
|
+
// ============================================
|
|
2897
|
+
async get(key) {
|
|
2898
|
+
return this.getConnectedClient().get(this.prefixKey(key));
|
|
2899
|
+
}
|
|
2900
|
+
async doSet(key, value, options) {
|
|
2901
|
+
const client = this.getConnectedClient();
|
|
2902
|
+
const prefixedKey = this.prefixKey(key);
|
|
2903
|
+
if (options?.ttlSeconds) {
|
|
2904
|
+
if (options.ifNotExists) {
|
|
2905
|
+
await client.set(prefixedKey, value, "EX", options.ttlSeconds, "NX");
|
|
2906
|
+
} else if (options.ifExists) {
|
|
2907
|
+
await client.set(prefixedKey, value, "EX", options.ttlSeconds, "XX");
|
|
2908
|
+
} else {
|
|
2909
|
+
await client.set(prefixedKey, value, "EX", options.ttlSeconds);
|
|
2910
|
+
}
|
|
2911
|
+
} else if (options?.ifNotExists) {
|
|
2912
|
+
await client.set(prefixedKey, value, "NX");
|
|
2913
|
+
} else if (options?.ifExists) {
|
|
2914
|
+
await client.set(prefixedKey, value, "XX");
|
|
2915
|
+
} else {
|
|
2916
|
+
await client.set(prefixedKey, value);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
async delete(key) {
|
|
2920
|
+
const result = await this.getConnectedClient().del(this.prefixKey(key));
|
|
2921
|
+
return result > 0;
|
|
2922
|
+
}
|
|
2923
|
+
async exists(key) {
|
|
2924
|
+
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
2925
|
+
return result > 0;
|
|
2926
|
+
}
|
|
2927
|
+
// ============================================
|
|
2928
|
+
// Batch Operations (pipelined)
|
|
2929
|
+
// ============================================
|
|
2930
|
+
async mget(keys) {
|
|
2931
|
+
if (keys.length === 0) return [];
|
|
2932
|
+
const prefixedKeys = keys.map((k) => this.prefixKey(k));
|
|
2933
|
+
return this.getConnectedClient().mget(...prefixedKeys);
|
|
2934
|
+
}
|
|
2935
|
+
async mset(entries) {
|
|
2936
|
+
if (entries.length === 0) return;
|
|
2937
|
+
for (const entry of entries) {
|
|
2938
|
+
this.validateSetOptions(entry.options);
|
|
2939
|
+
}
|
|
2940
|
+
const pipeline = this.getConnectedClient().pipeline();
|
|
2941
|
+
for (const entry of entries) {
|
|
2942
|
+
const prefixedKey = this.prefixKey(entry.key);
|
|
2943
|
+
if (entry.options?.ttlSeconds) {
|
|
2944
|
+
if (entry.options.ifNotExists) {
|
|
2945
|
+
pipeline.set(prefixedKey, entry.value, "EX", entry.options.ttlSeconds, "NX");
|
|
2946
|
+
} else if (entry.options.ifExists) {
|
|
2947
|
+
pipeline.set(prefixedKey, entry.value, "EX", entry.options.ttlSeconds, "XX");
|
|
2948
|
+
} else {
|
|
2949
|
+
pipeline.set(prefixedKey, entry.value, "EX", entry.options.ttlSeconds);
|
|
2950
|
+
}
|
|
2951
|
+
} else if (entry.options?.ifNotExists) {
|
|
2952
|
+
pipeline.set(prefixedKey, entry.value, "NX");
|
|
2953
|
+
} else if (entry.options?.ifExists) {
|
|
2954
|
+
pipeline.set(prefixedKey, entry.value, "XX");
|
|
2955
|
+
} else {
|
|
2956
|
+
pipeline.set(prefixedKey, entry.value);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
await pipeline.exec();
|
|
2960
|
+
}
|
|
2961
|
+
async mdelete(keys) {
|
|
2962
|
+
if (keys.length === 0) return 0;
|
|
2963
|
+
const prefixedKeys = keys.map((k) => this.prefixKey(k));
|
|
2964
|
+
return this.getConnectedClient().del(...prefixedKeys);
|
|
2965
|
+
}
|
|
2966
|
+
// ============================================
|
|
2967
|
+
// TTL Operations
|
|
2968
|
+
// ============================================
|
|
2969
|
+
async expire(key, ttlSeconds) {
|
|
2970
|
+
validateTTL(ttlSeconds);
|
|
2971
|
+
const result = await this.getConnectedClient().expire(this.prefixKey(key), ttlSeconds);
|
|
2972
|
+
return result === 1;
|
|
2973
|
+
}
|
|
2974
|
+
async ttl(key) {
|
|
2975
|
+
const result = await this.getConnectedClient().ttl(this.prefixKey(key));
|
|
2976
|
+
if (result === -2) return null;
|
|
2977
|
+
return result;
|
|
2978
|
+
}
|
|
2979
|
+
// ============================================
|
|
2980
|
+
// Key Enumeration (SCAN)
|
|
2981
|
+
// ============================================
|
|
2982
|
+
async keys(pattern = "*") {
|
|
2983
|
+
const client = this.getConnectedClient();
|
|
2984
|
+
const prefixedPattern = this.prefixKey(pattern);
|
|
2985
|
+
const result = [];
|
|
2986
|
+
let cursor = "0";
|
|
2987
|
+
do {
|
|
2988
|
+
const [nextCursor, keys] = await client.scan(cursor, "MATCH", prefixedPattern, "COUNT", 100);
|
|
2989
|
+
cursor = nextCursor;
|
|
2990
|
+
for (const key of keys) {
|
|
2991
|
+
result.push(this.unprefixKey(key));
|
|
2992
|
+
}
|
|
2993
|
+
} while (cursor !== "0");
|
|
2994
|
+
return result;
|
|
2995
|
+
}
|
|
2996
|
+
// ============================================
|
|
2997
|
+
// Atomic Operations
|
|
2998
|
+
// ============================================
|
|
2999
|
+
async incr(key) {
|
|
3000
|
+
return this.getConnectedClient().incr(this.prefixKey(key));
|
|
3001
|
+
}
|
|
3002
|
+
async decr(key) {
|
|
3003
|
+
return this.getConnectedClient().decr(this.prefixKey(key));
|
|
3004
|
+
}
|
|
3005
|
+
async incrBy(key, amount) {
|
|
3006
|
+
return this.getConnectedClient().incrby(this.prefixKey(key), amount);
|
|
3007
|
+
}
|
|
3008
|
+
// ============================================
|
|
3009
|
+
// Pub/Sub
|
|
3010
|
+
// ============================================
|
|
3011
|
+
supportsPubSub() {
|
|
3012
|
+
return true;
|
|
3013
|
+
}
|
|
3014
|
+
async publish(channel, message) {
|
|
3015
|
+
const prefixedChannel = this.prefixKey(channel);
|
|
3016
|
+
return this.getConnectedClient().publish(prefixedChannel, message);
|
|
3017
|
+
}
|
|
3018
|
+
async subscribe(channel, handler) {
|
|
3019
|
+
this.ensureConnected();
|
|
3020
|
+
const prefixedChannel = this.prefixKey(channel);
|
|
3021
|
+
if (!this.subscriber) {
|
|
3022
|
+
await this.createSubscriber();
|
|
3023
|
+
}
|
|
3024
|
+
const subscriber = this.getConnectedSubscriber();
|
|
3025
|
+
if (!this.subscriptionHandlers.has(prefixedChannel)) {
|
|
3026
|
+
this.subscriptionHandlers.set(prefixedChannel, /* @__PURE__ */ new Set());
|
|
3027
|
+
await subscriber.subscribe(prefixedChannel);
|
|
3028
|
+
}
|
|
3029
|
+
const handlers = this.subscriptionHandlers.get(prefixedChannel);
|
|
3030
|
+
if (handlers) {
|
|
3031
|
+
handlers.add(handler);
|
|
3032
|
+
}
|
|
3033
|
+
return async () => {
|
|
3034
|
+
const handlers2 = this.subscriptionHandlers.get(prefixedChannel);
|
|
3035
|
+
if (handlers2) {
|
|
3036
|
+
handlers2.delete(handler);
|
|
3037
|
+
if (handlers2.size === 0) {
|
|
3038
|
+
this.subscriptionHandlers.delete(prefixedChannel);
|
|
3039
|
+
await this.subscriber?.unsubscribe(prefixedChannel);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
// ============================================
|
|
3045
|
+
// Internal Helpers
|
|
3046
|
+
// ============================================
|
|
3047
|
+
/**
|
|
3048
|
+
* Build Redis options from config.
|
|
3049
|
+
*/
|
|
3050
|
+
buildRedisOptions() {
|
|
3051
|
+
if (this.options.url) {
|
|
3052
|
+
return {
|
|
3053
|
+
lazyConnect: false,
|
|
3054
|
+
maxRetriesPerRequest: 3
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
const config = this.options.config;
|
|
3058
|
+
if (!config) {
|
|
3059
|
+
throw new StorageConfigError("redis", "Redis config is required when URL is not provided");
|
|
3060
|
+
}
|
|
3061
|
+
return {
|
|
3062
|
+
host: config.host,
|
|
3063
|
+
port: config.port ?? 6379,
|
|
3064
|
+
password: config.password,
|
|
3065
|
+
db: config.db ?? 0,
|
|
3066
|
+
tls: config.tls ? {} : void 0,
|
|
3067
|
+
lazyConnect: false,
|
|
3068
|
+
maxRetriesPerRequest: 3
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Create subscriber connection.
|
|
3073
|
+
*/
|
|
3074
|
+
async createSubscriber() {
|
|
3075
|
+
const RedisClass = getRedisClass();
|
|
3076
|
+
let subscriber;
|
|
3077
|
+
if (this.options.url) {
|
|
3078
|
+
subscriber = new RedisClass(this.options.url);
|
|
3079
|
+
} else if (this.options.config) {
|
|
3080
|
+
subscriber = new RedisClass(this.buildRedisOptions());
|
|
3081
|
+
} else if (this.options.client) {
|
|
3082
|
+
subscriber = this.options.client.duplicate();
|
|
3083
|
+
} else {
|
|
3084
|
+
throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
|
|
3085
|
+
}
|
|
3086
|
+
subscriber.on("message", (channel, message) => {
|
|
3087
|
+
const handlers = this.subscriptionHandlers.get(channel);
|
|
3088
|
+
if (handlers) {
|
|
3089
|
+
const unprefixedChannel = this.unprefixKey(channel);
|
|
3090
|
+
for (const handler of handlers) {
|
|
3091
|
+
try {
|
|
3092
|
+
handler(message, unprefixedChannel);
|
|
3093
|
+
} catch {
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
});
|
|
3098
|
+
this.subscriber = subscriber;
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Add prefix to a key.
|
|
3102
|
+
*/
|
|
3103
|
+
prefixKey(key) {
|
|
3104
|
+
return this.keyPrefix + key;
|
|
3105
|
+
}
|
|
3106
|
+
/**
|
|
3107
|
+
* Remove prefix from a key.
|
|
3108
|
+
*/
|
|
3109
|
+
unprefixKey(key) {
|
|
3110
|
+
if (this.keyPrefix && key.startsWith(this.keyPrefix)) {
|
|
3111
|
+
return key.slice(this.keyPrefix.length);
|
|
3112
|
+
}
|
|
3113
|
+
return key;
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Get the underlying Redis client (for advanced use).
|
|
3117
|
+
*/
|
|
3118
|
+
getClient() {
|
|
3119
|
+
return this.client;
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
var vercel_kv_exports = {};
|
|
3125
|
+
__export(vercel_kv_exports, {
|
|
3126
|
+
VercelKvStorageAdapter: () => VercelKvStorageAdapter
|
|
3127
|
+
});
|
|
3128
|
+
function getVercelKv() {
|
|
3129
|
+
try {
|
|
3130
|
+
return __require2("@vercel/kv");
|
|
3131
|
+
} catch {
|
|
3132
|
+
throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv");
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
var VercelKvStorageAdapter;
|
|
3136
|
+
var init_vercel_kv = __esm({
|
|
3137
|
+
"libs/utils/src/storage/adapters/vercel-kv.ts"() {
|
|
3138
|
+
"use strict";
|
|
3139
|
+
init_base();
|
|
3140
|
+
init_errors();
|
|
3141
|
+
init_ttl();
|
|
3142
|
+
VercelKvStorageAdapter = class extends BaseStorageAdapter {
|
|
3143
|
+
backendName = "vercel-kv";
|
|
3144
|
+
client;
|
|
3145
|
+
options;
|
|
3146
|
+
keyPrefix;
|
|
3147
|
+
constructor(options = {}) {
|
|
3148
|
+
super();
|
|
3149
|
+
const url = options.url ?? process.env["KV_REST_API_URL"];
|
|
3150
|
+
const token = options.token ?? process.env["KV_REST_API_TOKEN"];
|
|
3151
|
+
if (!url || !token) {
|
|
3152
|
+
throw new StorageConfigError(
|
|
3153
|
+
"vercel-kv",
|
|
3154
|
+
"KV_REST_API_URL and KV_REST_API_TOKEN must be provided via options or environment variables."
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
this.options = { ...options, url, token };
|
|
3158
|
+
this.keyPrefix = options.keyPrefix ?? "";
|
|
3159
|
+
}
|
|
3160
|
+
// ============================================
|
|
3161
|
+
// Connection Lifecycle
|
|
3162
|
+
// ============================================
|
|
3163
|
+
async connect() {
|
|
3164
|
+
if (this.connected) return;
|
|
3165
|
+
try {
|
|
3166
|
+
const { createClient, kv } = getVercelKv();
|
|
3167
|
+
if (this.options.url === process.env["KV_REST_API_URL"]) {
|
|
3168
|
+
this.client = kv;
|
|
3169
|
+
} else {
|
|
3170
|
+
const url = this.options.url;
|
|
3171
|
+
const token = this.options.token;
|
|
3172
|
+
if (!url || !token) {
|
|
3173
|
+
throw new StorageConfigError("vercel-kv", "URL and token are required");
|
|
3174
|
+
}
|
|
3175
|
+
this.client = createClient({ url, token });
|
|
3176
|
+
}
|
|
3177
|
+
await this.client.exists("__healthcheck__");
|
|
3178
|
+
this.connected = true;
|
|
3179
|
+
} catch (e) {
|
|
3180
|
+
throw new StorageConnectionError(
|
|
3181
|
+
"Failed to connect to Vercel KV",
|
|
3182
|
+
e instanceof Error ? e : void 0,
|
|
3183
|
+
"vercel-kv"
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
async disconnect() {
|
|
3188
|
+
this.client = void 0;
|
|
3189
|
+
this.connected = false;
|
|
3190
|
+
}
|
|
3191
|
+
async ping() {
|
|
3192
|
+
if (!this.client) return false;
|
|
3193
|
+
try {
|
|
3194
|
+
await this.client.exists("__healthcheck__");
|
|
3195
|
+
return true;
|
|
3196
|
+
} catch {
|
|
3197
|
+
return false;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
// ============================================
|
|
3201
|
+
// Connection Helpers
|
|
3202
|
+
// ============================================
|
|
3203
|
+
/**
|
|
3204
|
+
* Get the connected Vercel KV client, throwing if not connected.
|
|
3205
|
+
*/
|
|
3206
|
+
getConnectedClient() {
|
|
3207
|
+
this.ensureConnected();
|
|
3208
|
+
if (!this.client) {
|
|
3209
|
+
throw new StorageConnectionError("Vercel KV client not connected", void 0, "vercel-kv");
|
|
3210
|
+
}
|
|
3211
|
+
return this.client;
|
|
3212
|
+
}
|
|
3213
|
+
// ============================================
|
|
3214
|
+
// Core Operations
|
|
3215
|
+
// ============================================
|
|
3216
|
+
async get(key) {
|
|
3217
|
+
const result = await this.getConnectedClient().get(this.prefixKey(key));
|
|
3218
|
+
return result;
|
|
3219
|
+
}
|
|
3220
|
+
async doSet(key, value, options) {
|
|
3221
|
+
const client = this.getConnectedClient();
|
|
3222
|
+
const prefixedKey = this.prefixKey(key);
|
|
3223
|
+
const setOptions = {};
|
|
3224
|
+
if (options?.ttlSeconds) {
|
|
3225
|
+
setOptions.ex = options.ttlSeconds;
|
|
3226
|
+
}
|
|
3227
|
+
if (options?.ifNotExists) {
|
|
3228
|
+
setOptions.nx = true;
|
|
3229
|
+
} else if (options?.ifExists) {
|
|
3230
|
+
setOptions.xx = true;
|
|
3231
|
+
}
|
|
3232
|
+
await client.set(prefixedKey, value, Object.keys(setOptions).length > 0 ? setOptions : void 0);
|
|
3233
|
+
}
|
|
3234
|
+
async delete(key) {
|
|
3235
|
+
const result = await this.getConnectedClient().del(this.prefixKey(key));
|
|
3236
|
+
return result > 0;
|
|
3237
|
+
}
|
|
3238
|
+
async exists(key) {
|
|
3239
|
+
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3240
|
+
return result > 0;
|
|
3241
|
+
}
|
|
3242
|
+
// ============================================
|
|
3243
|
+
// Batch Operations
|
|
3244
|
+
// ============================================
|
|
3245
|
+
async mget(keys) {
|
|
3246
|
+
if (keys.length === 0) return [];
|
|
3247
|
+
const prefixedKeys = keys.map((k) => this.prefixKey(k));
|
|
3248
|
+
return this.getConnectedClient().mget(...prefixedKeys);
|
|
3249
|
+
}
|
|
3250
|
+
async mdelete(keys) {
|
|
3251
|
+
if (keys.length === 0) return 0;
|
|
3252
|
+
const prefixedKeys = keys.map((k) => this.prefixKey(k));
|
|
3253
|
+
return this.getConnectedClient().del(...prefixedKeys);
|
|
3254
|
+
}
|
|
3255
|
+
// ============================================
|
|
3256
|
+
// TTL Operations
|
|
3257
|
+
// ============================================
|
|
3258
|
+
async expire(key, ttlSeconds) {
|
|
3259
|
+
validateTTL(ttlSeconds);
|
|
3260
|
+
const result = await this.getConnectedClient().expire(this.prefixKey(key), ttlSeconds);
|
|
3261
|
+
return result === 1;
|
|
3262
|
+
}
|
|
3263
|
+
async ttl(key) {
|
|
3264
|
+
const result = await this.getConnectedClient().ttl(this.prefixKey(key));
|
|
3265
|
+
if (result === -2) return null;
|
|
3266
|
+
return result;
|
|
3267
|
+
}
|
|
3268
|
+
// ============================================
|
|
3269
|
+
// Key Enumeration
|
|
3270
|
+
// ============================================
|
|
3271
|
+
async keys(pattern = "*") {
|
|
3272
|
+
const client = this.getConnectedClient();
|
|
3273
|
+
const prefixedPattern = this.prefixKey(pattern);
|
|
3274
|
+
try {
|
|
3275
|
+
const result = [];
|
|
3276
|
+
let cursor = 0;
|
|
3277
|
+
do {
|
|
3278
|
+
const [nextCursor, keys] = await client.scan(cursor, {
|
|
3279
|
+
match: prefixedPattern,
|
|
3280
|
+
count: 100
|
|
3281
|
+
});
|
|
3282
|
+
const parsedCursor = typeof nextCursor === "string" ? parseInt(nextCursor, 10) : nextCursor;
|
|
3283
|
+
cursor = Number.isNaN(parsedCursor) ? 0 : parsedCursor;
|
|
3284
|
+
for (const key of keys) {
|
|
3285
|
+
result.push(this.unprefixKey(key));
|
|
3286
|
+
}
|
|
3287
|
+
} while (cursor !== 0);
|
|
3288
|
+
return result;
|
|
3289
|
+
} catch {
|
|
3290
|
+
const allKeys = await client.keys(prefixedPattern);
|
|
3291
|
+
return allKeys.map((k) => this.unprefixKey(k));
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
// ============================================
|
|
3295
|
+
// Atomic Operations
|
|
3296
|
+
// ============================================
|
|
3297
|
+
async incr(key) {
|
|
3298
|
+
return this.getConnectedClient().incr(this.prefixKey(key));
|
|
3299
|
+
}
|
|
3300
|
+
async decr(key) {
|
|
3301
|
+
return this.getConnectedClient().decr(this.prefixKey(key));
|
|
3302
|
+
}
|
|
3303
|
+
async incrBy(key, amount) {
|
|
3304
|
+
return this.getConnectedClient().incrby(this.prefixKey(key), amount);
|
|
3305
|
+
}
|
|
3306
|
+
// ============================================
|
|
3307
|
+
// Pub/Sub (NOT SUPPORTED)
|
|
3308
|
+
// ============================================
|
|
3309
|
+
supportsPubSub() {
|
|
3310
|
+
return false;
|
|
3311
|
+
}
|
|
3312
|
+
getPubSubSuggestion() {
|
|
3313
|
+
return "Vercel KV is REST-based and does not support pub/sub. Use Upstash adapter for pub/sub support.";
|
|
3314
|
+
}
|
|
3315
|
+
// ============================================
|
|
3316
|
+
// Internal Helpers
|
|
3317
|
+
// ============================================
|
|
3318
|
+
/**
|
|
3319
|
+
* Add prefix to a key.
|
|
3320
|
+
*/
|
|
3321
|
+
prefixKey(key) {
|
|
3322
|
+
return this.keyPrefix + key;
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Remove prefix from a key.
|
|
3326
|
+
*/
|
|
3327
|
+
unprefixKey(key) {
|
|
3328
|
+
if (this.keyPrefix && key.startsWith(this.keyPrefix)) {
|
|
3329
|
+
return key.slice(this.keyPrefix.length);
|
|
3330
|
+
}
|
|
3331
|
+
return key;
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
});
|
|
3336
|
+
var upstash_exports = {};
|
|
3337
|
+
__export(upstash_exports, {
|
|
3338
|
+
UpstashStorageAdapter: () => UpstashStorageAdapter
|
|
3339
|
+
});
|
|
3340
|
+
function getUpstashRedis() {
|
|
3341
|
+
try {
|
|
3342
|
+
return __require2("@upstash/redis");
|
|
3343
|
+
} catch {
|
|
3344
|
+
throw new Error(
|
|
3345
|
+
"@upstash/redis is required for Upstash storage adapter. Install it with: npm install @upstash/redis"
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
var PUBSUB_POLL_INTERVAL_MS;
|
|
3350
|
+
var UpstashStorageAdapter;
|
|
3351
|
+
var init_upstash = __esm({
|
|
3352
|
+
"libs/utils/src/storage/adapters/upstash.ts"() {
|
|
3353
|
+
"use strict";
|
|
3354
|
+
init_base();
|
|
3355
|
+
init_errors();
|
|
3356
|
+
init_ttl();
|
|
3357
|
+
PUBSUB_POLL_INTERVAL_MS = 100;
|
|
3358
|
+
UpstashStorageAdapter = class extends BaseStorageAdapter {
|
|
3359
|
+
backendName = "upstash";
|
|
3360
|
+
client;
|
|
3361
|
+
options;
|
|
3362
|
+
keyPrefix;
|
|
3363
|
+
pubSubEnabled;
|
|
3364
|
+
// Pub/sub state
|
|
3365
|
+
subscriptionHandlers = /* @__PURE__ */ new Map();
|
|
3366
|
+
pollingIntervals = /* @__PURE__ */ new Map();
|
|
3367
|
+
constructor(options = {}) {
|
|
3368
|
+
super();
|
|
3369
|
+
const url = options.url ?? process.env["UPSTASH_REDIS_REST_URL"];
|
|
3370
|
+
const token = options.token ?? process.env["UPSTASH_REDIS_REST_TOKEN"];
|
|
3371
|
+
if (!url || !token) {
|
|
3372
|
+
throw new StorageConfigError(
|
|
3373
|
+
"upstash",
|
|
3374
|
+
"UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN must be provided via options or environment variables."
|
|
3375
|
+
);
|
|
3376
|
+
}
|
|
3377
|
+
this.options = { ...options, url, token };
|
|
3378
|
+
this.keyPrefix = options.keyPrefix ?? "";
|
|
3379
|
+
this.pubSubEnabled = options.enablePubSub ?? false;
|
|
3380
|
+
}
|
|
3381
|
+
// ============================================
|
|
3382
|
+
// Connection Lifecycle
|
|
3383
|
+
// ============================================
|
|
3384
|
+
async connect() {
|
|
3385
|
+
if (this.connected) return;
|
|
3386
|
+
try {
|
|
3387
|
+
const { Redis } = getUpstashRedis();
|
|
3388
|
+
const url = this.options.url;
|
|
3389
|
+
const token = this.options.token;
|
|
3390
|
+
if (!url || !token) {
|
|
3391
|
+
throw new StorageConfigError("upstash", "URL and token are required");
|
|
3392
|
+
}
|
|
3393
|
+
this.client = new Redis({ url, token });
|
|
3394
|
+
await this.client.exists("__healthcheck__");
|
|
3395
|
+
this.connected = true;
|
|
3396
|
+
} catch (e) {
|
|
3397
|
+
throw new StorageConnectionError(
|
|
3398
|
+
"Failed to connect to Upstash Redis",
|
|
3399
|
+
e instanceof Error ? e : void 0,
|
|
3400
|
+
"upstash"
|
|
3401
|
+
);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
async disconnect() {
|
|
3405
|
+
if (!this.connected) return;
|
|
3406
|
+
for (const interval of this.pollingIntervals.values()) {
|
|
3407
|
+
clearInterval(interval);
|
|
3408
|
+
}
|
|
3409
|
+
this.pollingIntervals.clear();
|
|
3410
|
+
this.subscriptionHandlers.clear();
|
|
3411
|
+
this.client = void 0;
|
|
3412
|
+
this.connected = false;
|
|
3413
|
+
}
|
|
3414
|
+
async ping() {
|
|
3415
|
+
if (!this.client) return false;
|
|
3416
|
+
try {
|
|
3417
|
+
await this.client.exists("__healthcheck__");
|
|
3418
|
+
return true;
|
|
3419
|
+
} catch {
|
|
3420
|
+
return false;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
// ============================================
|
|
3424
|
+
// Connection Helpers
|
|
3425
|
+
// ============================================
|
|
3426
|
+
/**
|
|
3427
|
+
* Get the connected Upstash client, throwing if not connected.
|
|
3428
|
+
*/
|
|
3429
|
+
getConnectedClient() {
|
|
3430
|
+
this.ensureConnected();
|
|
3431
|
+
if (!this.client) {
|
|
3432
|
+
throw new StorageConnectionError("Upstash client not connected", void 0, "upstash");
|
|
3433
|
+
}
|
|
3434
|
+
return this.client;
|
|
3435
|
+
}
|
|
3436
|
+
// ============================================
|
|
3437
|
+
// Core Operations
|
|
3438
|
+
// ============================================
|
|
3439
|
+
async get(key) {
|
|
3440
|
+
const result = await this.getConnectedClient().get(this.prefixKey(key));
|
|
3441
|
+
return result;
|
|
3442
|
+
}
|
|
3443
|
+
async doSet(key, value, options) {
|
|
3444
|
+
const client = this.getConnectedClient();
|
|
3445
|
+
const prefixedKey = this.prefixKey(key);
|
|
3446
|
+
const setOptions = {};
|
|
3447
|
+
if (options?.ttlSeconds) {
|
|
3448
|
+
setOptions.ex = options.ttlSeconds;
|
|
3449
|
+
}
|
|
3450
|
+
if (options?.ifNotExists) {
|
|
3451
|
+
setOptions.nx = true;
|
|
3452
|
+
} else if (options?.ifExists) {
|
|
3453
|
+
setOptions.xx = true;
|
|
3454
|
+
}
|
|
3455
|
+
await client.set(prefixedKey, value, Object.keys(setOptions).length > 0 ? setOptions : void 0);
|
|
3456
|
+
}
|
|
3457
|
+
async delete(key) {
|
|
3458
|
+
const result = await this.getConnectedClient().del(this.prefixKey(key));
|
|
3459
|
+
return result > 0;
|
|
3460
|
+
}
|
|
3461
|
+
async exists(key) {
|
|
3462
|
+
const result = await this.getConnectedClient().exists(this.prefixKey(key));
|
|
3463
|
+
return result > 0;
|
|
3464
|
+
}
|
|
3465
|
+
// ============================================
|
|
3466
|
+
// Batch Operations
|
|
3467
|
+
// ============================================
|
|
3468
|
+
async mget(keys) {
|
|
3469
|
+
if (keys.length === 0) return [];
|
|
3470
|
+
const prefixedKeys = keys.map((k) => this.prefixKey(k));
|
|
3471
|
+
return this.getConnectedClient().mget(...prefixedKeys);
|
|
3472
|
+
}
|
|
3473
|
+
async mdelete(keys) {
|
|
3474
|
+
if (keys.length === 0) return 0;
|
|
3475
|
+
const prefixedKeys = keys.map((k) => this.prefixKey(k));
|
|
3476
|
+
return this.getConnectedClient().del(...prefixedKeys);
|
|
3477
|
+
}
|
|
3478
|
+
// ============================================
|
|
3479
|
+
// TTL Operations
|
|
3480
|
+
// ============================================
|
|
3481
|
+
async expire(key, ttlSeconds) {
|
|
3482
|
+
validateTTL(ttlSeconds);
|
|
3483
|
+
const result = await this.getConnectedClient().expire(this.prefixKey(key), ttlSeconds);
|
|
3484
|
+
return result === 1;
|
|
3485
|
+
}
|
|
3486
|
+
async ttl(key) {
|
|
3487
|
+
const result = await this.getConnectedClient().ttl(this.prefixKey(key));
|
|
3488
|
+
if (result === -2) return null;
|
|
3489
|
+
return result;
|
|
3490
|
+
}
|
|
3491
|
+
// ============================================
|
|
3492
|
+
// Key Enumeration
|
|
3493
|
+
// ============================================
|
|
3494
|
+
async keys(pattern = "*") {
|
|
3495
|
+
const client = this.getConnectedClient();
|
|
3496
|
+
const prefixedPattern = this.prefixKey(pattern);
|
|
3497
|
+
const result = [];
|
|
3498
|
+
let cursor = 0;
|
|
3499
|
+
do {
|
|
3500
|
+
const [nextCursor, keys] = await client.scan(cursor, {
|
|
3501
|
+
match: prefixedPattern,
|
|
3502
|
+
count: 100
|
|
3503
|
+
});
|
|
3504
|
+
const parsedCursor = typeof nextCursor === "string" ? parseInt(nextCursor, 10) : nextCursor;
|
|
3505
|
+
cursor = Number.isNaN(parsedCursor) ? 0 : parsedCursor;
|
|
3506
|
+
for (const key of keys) {
|
|
3507
|
+
result.push(this.unprefixKey(key));
|
|
3508
|
+
}
|
|
3509
|
+
} while (cursor !== 0);
|
|
3510
|
+
return result;
|
|
3511
|
+
}
|
|
3512
|
+
// ============================================
|
|
3513
|
+
// Atomic Operations
|
|
3514
|
+
// ============================================
|
|
3515
|
+
async incr(key) {
|
|
3516
|
+
return this.getConnectedClient().incr(this.prefixKey(key));
|
|
3517
|
+
}
|
|
3518
|
+
async decr(key) {
|
|
3519
|
+
return this.getConnectedClient().decr(this.prefixKey(key));
|
|
3520
|
+
}
|
|
3521
|
+
async incrBy(key, amount) {
|
|
3522
|
+
return this.getConnectedClient().incrby(this.prefixKey(key), amount);
|
|
3523
|
+
}
|
|
3524
|
+
// ============================================
|
|
3525
|
+
// Pub/Sub (via list-based polling)
|
|
3526
|
+
// ============================================
|
|
3527
|
+
supportsPubSub() {
|
|
3528
|
+
return this.pubSubEnabled;
|
|
3529
|
+
}
|
|
3530
|
+
async publish(channel, message) {
|
|
3531
|
+
if (!this.pubSubEnabled) {
|
|
3532
|
+
return super.publish(channel, message);
|
|
3533
|
+
}
|
|
3534
|
+
const prefixedChannel = this.prefixKey(`__pubsub__:${channel}`);
|
|
3535
|
+
const listKey = `${prefixedChannel}:queue`;
|
|
3536
|
+
await this.getConnectedClient().lpush(listKey, message);
|
|
3537
|
+
return 1;
|
|
3538
|
+
}
|
|
3539
|
+
async subscribe(channel, handler) {
|
|
3540
|
+
const client = this.getConnectedClient();
|
|
3541
|
+
if (!this.pubSubEnabled) {
|
|
3542
|
+
return super.subscribe(channel, handler);
|
|
3543
|
+
}
|
|
3544
|
+
const prefixedChannel = this.prefixKey(`__pubsub__:${channel}`);
|
|
3545
|
+
if (!this.subscriptionHandlers.has(prefixedChannel)) {
|
|
3546
|
+
this.subscriptionHandlers.set(prefixedChannel, /* @__PURE__ */ new Set());
|
|
3547
|
+
const listKey = `${prefixedChannel}:queue`;
|
|
3548
|
+
const interval = setInterval(async () => {
|
|
3549
|
+
try {
|
|
3550
|
+
const message = await client.rpop(listKey);
|
|
3551
|
+
if (message) {
|
|
3552
|
+
const handlers2 = this.subscriptionHandlers.get(prefixedChannel);
|
|
3553
|
+
if (handlers2) {
|
|
3554
|
+
for (const h of handlers2) {
|
|
3555
|
+
try {
|
|
3556
|
+
h(message, channel);
|
|
3557
|
+
} catch {
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
} catch {
|
|
3563
|
+
}
|
|
3564
|
+
}, PUBSUB_POLL_INTERVAL_MS);
|
|
3565
|
+
if (interval.unref) {
|
|
3566
|
+
interval.unref();
|
|
3567
|
+
}
|
|
3568
|
+
this.pollingIntervals.set(prefixedChannel, interval);
|
|
3569
|
+
}
|
|
3570
|
+
const handlers = this.subscriptionHandlers.get(prefixedChannel);
|
|
3571
|
+
if (handlers) {
|
|
3572
|
+
handlers.add(handler);
|
|
3573
|
+
}
|
|
3574
|
+
return async () => {
|
|
3575
|
+
const handlers2 = this.subscriptionHandlers.get(prefixedChannel);
|
|
3576
|
+
if (handlers2) {
|
|
3577
|
+
handlers2.delete(handler);
|
|
3578
|
+
if (handlers2.size === 0) {
|
|
3579
|
+
this.subscriptionHandlers.delete(prefixedChannel);
|
|
3580
|
+
const interval = this.pollingIntervals.get(prefixedChannel);
|
|
3581
|
+
if (interval) {
|
|
3582
|
+
clearInterval(interval);
|
|
3583
|
+
this.pollingIntervals.delete(prefixedChannel);
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
};
|
|
3588
|
+
}
|
|
3589
|
+
/**
|
|
3590
|
+
* Publish using list-based approach (for polling subscribers).
|
|
3591
|
+
* This is an alternative to native PUBLISH when subscribers are polling.
|
|
3592
|
+
*/
|
|
3593
|
+
async publishToQueue(channel, message) {
|
|
3594
|
+
const prefixedChannel = this.prefixKey(`__pubsub__:${channel}`);
|
|
3595
|
+
const listKey = `${prefixedChannel}:queue`;
|
|
3596
|
+
await this.getConnectedClient().lpush(listKey, message);
|
|
3597
|
+
}
|
|
3598
|
+
getPubSubSuggestion() {
|
|
3599
|
+
return "Enable pub/sub by setting enablePubSub: true in the adapter options.";
|
|
3600
|
+
}
|
|
3601
|
+
// ============================================
|
|
3602
|
+
// Internal Helpers
|
|
3603
|
+
// ============================================
|
|
3604
|
+
/**
|
|
3605
|
+
* Add prefix to a key.
|
|
3606
|
+
*/
|
|
3607
|
+
prefixKey(key) {
|
|
3608
|
+
return this.keyPrefix + key;
|
|
3609
|
+
}
|
|
3610
|
+
/**
|
|
3611
|
+
* Remove prefix from a key.
|
|
3612
|
+
*/
|
|
3613
|
+
unprefixKey(key) {
|
|
3614
|
+
if (this.keyPrefix && key.startsWith(this.keyPrefix)) {
|
|
3615
|
+
return key.slice(this.keyPrefix.length);
|
|
3616
|
+
}
|
|
3617
|
+
return key;
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3621
|
+
});
|
|
3622
|
+
var cloudflare_kv_exports = {};
|
|
3623
|
+
__export(cloudflare_kv_exports, {
|
|
3624
|
+
CloudflareKvStorageAdapter: () => CloudflareKvStorageAdapter
|
|
3625
|
+
});
|
|
3626
|
+
function literalPrefix(pattern) {
|
|
3627
|
+
const star = pattern.indexOf("*");
|
|
3628
|
+
const q = pattern.indexOf("?");
|
|
3629
|
+
const idx = star === -1 ? q : q === -1 ? star : Math.min(star, q);
|
|
3630
|
+
return idx === -1 ? pattern : pattern.slice(0, idx);
|
|
3631
|
+
}
|
|
3632
|
+
var CF_KV_MIN_TTL_SECONDS;
|
|
3633
|
+
var CloudflareKvStorageAdapter;
|
|
3634
|
+
var init_cloudflare_kv = __esm({
|
|
3635
|
+
"libs/utils/src/storage/adapters/cloudflare-kv.ts"() {
|
|
3636
|
+
"use strict";
|
|
3637
|
+
init_errors();
|
|
3638
|
+
init_pattern();
|
|
3639
|
+
init_ttl();
|
|
3640
|
+
init_base();
|
|
3641
|
+
CF_KV_MIN_TTL_SECONDS = 60;
|
|
3642
|
+
CloudflareKvStorageAdapter = class extends BaseStorageAdapter {
|
|
3643
|
+
backendName = "cloudflare-kv";
|
|
3644
|
+
kv;
|
|
3645
|
+
keyPrefix;
|
|
3646
|
+
constructor(options) {
|
|
3647
|
+
super();
|
|
3648
|
+
if (!options?.namespace) {
|
|
3649
|
+
throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).");
|
|
3650
|
+
}
|
|
3651
|
+
this.kv = options.namespace;
|
|
3652
|
+
this.keyPrefix = options.keyPrefix ?? "";
|
|
3653
|
+
}
|
|
3654
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
3655
|
+
// A KV binding is always available; "connection" is just a readiness flag.
|
|
3656
|
+
async connect() {
|
|
3657
|
+
this.connected = true;
|
|
3658
|
+
}
|
|
3659
|
+
async disconnect() {
|
|
3660
|
+
this.connected = false;
|
|
3661
|
+
}
|
|
3662
|
+
async ping() {
|
|
3663
|
+
if (!this.connected) return false;
|
|
3664
|
+
try {
|
|
3665
|
+
await this.kv.list({ limit: 1 });
|
|
3666
|
+
return true;
|
|
3667
|
+
} catch {
|
|
3668
|
+
return false;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
// ── Core ───────────────────────────────────────────────────────────────
|
|
3672
|
+
async get(key) {
|
|
3673
|
+
this.ensureConnected();
|
|
3674
|
+
return this.kv.get(this.prefixKey(key), "text");
|
|
3675
|
+
}
|
|
3676
|
+
async doSet(key, value, options) {
|
|
3677
|
+
if (options?.ifNotExists || options?.ifExists) {
|
|
3678
|
+
throw new StorageNotSupportedError(
|
|
3679
|
+
"conditional set (ifNotExists/ifExists)",
|
|
3680
|
+
this.backendName,
|
|
3681
|
+
"Cloudflare KV has no compare-and-set; use a Durable Object for atomic conditional writes."
|
|
3682
|
+
);
|
|
3683
|
+
}
|
|
3684
|
+
const putOptions = options?.ttlSeconds !== void 0 ? { expirationTtl: this.toExpirationTtl(options.ttlSeconds, key) } : void 0;
|
|
3685
|
+
await this.kv.put(this.prefixKey(key), value, putOptions);
|
|
3686
|
+
}
|
|
3687
|
+
async delete(key) {
|
|
3688
|
+
this.ensureConnected();
|
|
3689
|
+
const existed = await this.exists(key);
|
|
3690
|
+
await this.kv.delete(this.prefixKey(key));
|
|
3691
|
+
return existed;
|
|
3692
|
+
}
|
|
3693
|
+
async exists(key) {
|
|
3694
|
+
this.ensureConnected();
|
|
3695
|
+
return await this.kv.get(this.prefixKey(key), "text") !== null;
|
|
3696
|
+
}
|
|
3697
|
+
// ── TTL ────────────────────────────────────────────────────────────────
|
|
3698
|
+
async expire(key, ttlSeconds) {
|
|
3699
|
+
this.ensureConnected();
|
|
3700
|
+
const ttl = this.toExpirationTtl(ttlSeconds, key);
|
|
3701
|
+
const value = await this.kv.get(this.prefixKey(key), "text");
|
|
3702
|
+
if (value === null) return false;
|
|
3703
|
+
await this.kv.put(this.prefixKey(key), value, { expirationTtl: ttl });
|
|
3704
|
+
return true;
|
|
3705
|
+
}
|
|
3706
|
+
async ttl(_key) {
|
|
3707
|
+
throw new StorageNotSupportedError(
|
|
3708
|
+
"ttl",
|
|
3709
|
+
this.backendName,
|
|
3710
|
+
"Cloudflare KV does not expose a key's remaining TTL. Track expiry alongside the value if you need it."
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
// ── Enumeration ──────────────────────────────────────────────────────────
|
|
3714
|
+
async keys(pattern = "*") {
|
|
3715
|
+
this.ensureConnected();
|
|
3716
|
+
const prefixedPattern = this.prefixKey(pattern);
|
|
3717
|
+
const listPrefix = literalPrefix(prefixedPattern);
|
|
3718
|
+
const out = [];
|
|
3719
|
+
let cursor;
|
|
3720
|
+
do {
|
|
3721
|
+
const page = await this.kv.list({ prefix: listPrefix || void 0, cursor });
|
|
3722
|
+
for (const { name } of page.keys) {
|
|
3723
|
+
if (matchesPattern(name, prefixedPattern)) out.push(this.unprefixKey(name));
|
|
3724
|
+
}
|
|
3725
|
+
cursor = page.list_complete ? void 0 : page.cursor;
|
|
3726
|
+
} while (cursor);
|
|
3727
|
+
return out;
|
|
3728
|
+
}
|
|
3729
|
+
// ── Atomic (unsupported on KV) ───────────────────────────────────────────
|
|
3730
|
+
async incr(key) {
|
|
3731
|
+
return this.incrBy(key, 1);
|
|
3732
|
+
}
|
|
3733
|
+
async decr(key) {
|
|
3734
|
+
return this.incrBy(key, -1);
|
|
3735
|
+
}
|
|
3736
|
+
async incrBy(_key, _amount) {
|
|
3737
|
+
throw new StorageNotSupportedError(
|
|
3738
|
+
"incr/decr/incrBy",
|
|
3739
|
+
this.backendName,
|
|
3740
|
+
"Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters."
|
|
3741
|
+
);
|
|
3742
|
+
}
|
|
3743
|
+
// ── Pub/Sub (unsupported) ────────────────────────────────────────────────
|
|
3744
|
+
getPubSubSuggestion() {
|
|
3745
|
+
return "Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out.";
|
|
3746
|
+
}
|
|
3747
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
3748
|
+
/** Validate + enforce KV's 60s minimum `expirationTtl`. */
|
|
3749
|
+
toExpirationTtl(ttlSeconds, key) {
|
|
3750
|
+
validateTTL(ttlSeconds);
|
|
3751
|
+
if (ttlSeconds < CF_KV_MIN_TTL_SECONDS) {
|
|
3752
|
+
throw new StorageOperationError(
|
|
3753
|
+
"set",
|
|
3754
|
+
key,
|
|
3755
|
+
`cloudflare-kv: expirationTtl must be >= ${CF_KV_MIN_TTL_SECONDS}s (Cloudflare KV minimum); got ${ttlSeconds}s.`
|
|
3756
|
+
);
|
|
3757
|
+
}
|
|
3758
|
+
return ttlSeconds;
|
|
3759
|
+
}
|
|
3760
|
+
prefixKey(key) {
|
|
3761
|
+
return this.keyPrefix + key;
|
|
3762
|
+
}
|
|
3763
|
+
unprefixKey(key) {
|
|
3764
|
+
return this.keyPrefix && key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key;
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
init_runtime();
|
|
3770
|
+
init_fs2();
|
|
3771
|
+
init_crypto();
|
|
3772
|
+
var entryAvailabilitySchema = z.object({
|
|
3773
|
+
os: z.array(z.string().min(1)).optional(),
|
|
3774
|
+
platform: z.array(z.string().min(1)).optional(),
|
|
3775
|
+
runtime: z.array(z.string().min(1)).optional(),
|
|
3776
|
+
deployment: z.array(z.string().min(1)).optional(),
|
|
3777
|
+
provider: z.array(z.string().min(1)).optional(),
|
|
3778
|
+
target: z.array(z.string().min(1)).optional(),
|
|
3779
|
+
surface: z.array(z.enum(["mcp", "cli", "http-trigger", "job", "agent"])).optional(),
|
|
3780
|
+
env: z.array(z.string().min(1)).optional()
|
|
3781
|
+
}).strict();
|
|
3782
|
+
init_errors();
|
|
3783
|
+
init_memory();
|
|
3784
|
+
init_errors();
|
|
3785
|
+
init_crypto();
|
|
3786
|
+
init_errors();
|
|
3787
|
+
var textEncoder2 = new TextEncoder();
|
|
3788
|
+
var textDecoder2 = new TextDecoder();
|
|
3789
|
+
init_base();
|
|
3790
|
+
init_memory();
|
|
3791
|
+
init_redis();
|
|
3792
|
+
init_vercel_kv();
|
|
3793
|
+
init_upstash();
|
|
3794
|
+
init_filesystem();
|
|
3795
|
+
init_pattern();
|
|
3796
|
+
init_ttl();
|
|
3797
|
+
init_crypto();
|
|
3798
|
+
init_runtime();
|
|
3799
|
+
|
|
3800
|
+
// libs/edge/src/session-host.ts
|
|
3801
|
+
var SESSION_ID_HEADER = "x-frontmcp-session-id";
|
|
3802
|
+
function createEdgeSessionRouter(bindingName) {
|
|
3803
|
+
return async (request, env) => {
|
|
3804
|
+
const ns = env?.[bindingName];
|
|
3805
|
+
if (!ns || typeof ns.idFromName !== "function" || typeof ns.get !== "function") return void 0;
|
|
3806
|
+
if (request.method.toUpperCase() === "OPTIONS") return void 0;
|
|
3807
|
+
const sessionId = request.headers.get("mcp-session-id") ?? randomUUID();
|
|
3808
|
+
const headers = new Headers(request.headers);
|
|
3809
|
+
headers.set(SESSION_ID_HEADER, sessionId);
|
|
3810
|
+
const stub = ns.get(ns.idFromName(sessionId));
|
|
3811
|
+
return stub.fetch(new Request(request, { headers }));
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
function createEdgeSessionDurableObject(buildScope, bridgeEnv) {
|
|
3815
|
+
return class FrontMcpSessionDurableObject {
|
|
3816
|
+
#scopePromise;
|
|
3817
|
+
#pair;
|
|
3818
|
+
#doEnv;
|
|
3819
|
+
constructor(_state, env) {
|
|
3820
|
+
this.#doEnv = env;
|
|
3821
|
+
}
|
|
3822
|
+
async fetch(request) {
|
|
3823
|
+
bridgeEnv(this.#doEnv);
|
|
3824
|
+
const sessionId = request.headers.get(SESSION_ID_HEADER) ?? request.headers.get("mcp-session-id") ?? randomUUID();
|
|
3825
|
+
let scope;
|
|
3826
|
+
try {
|
|
3827
|
+
scope = await (this.#scopePromise ??= buildScope(this.#doEnv));
|
|
3828
|
+
} catch (error) {
|
|
3829
|
+
this.#scopePromise = void 0;
|
|
3830
|
+
throw error;
|
|
3831
|
+
}
|
|
3832
|
+
if (!this.#pair) {
|
|
3833
|
+
this.#pair = await buildPersistentWebStandardMcp(scope, { sessionId });
|
|
3834
|
+
}
|
|
3835
|
+
const response = await runHttpRequestFlowWeb(scope, request, { persistent: this.#pair });
|
|
3836
|
+
return response ?? new Response(JSON.stringify({ error: "Not Found" }), {
|
|
3837
|
+
status: 404,
|
|
3838
|
+
headers: { "content-type": "application/json" }
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
// libs/edge/src/skill-index-cache.ts
|
|
3845
|
+
var DEFAULT_KEY_PREFIX = "frontmcp:skill-index:";
|
|
3846
|
+
function createKvSkillIndexCache(kv, options = {}) {
|
|
3847
|
+
const prefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
|
|
3848
|
+
return {
|
|
3849
|
+
async get(key) {
|
|
3850
|
+
const raw = await kv.get(prefix + key, "text");
|
|
3851
|
+
if (raw == null) return void 0;
|
|
3852
|
+
try {
|
|
3853
|
+
return JSON.parse(raw);
|
|
3854
|
+
} catch {
|
|
3855
|
+
return void 0;
|
|
3856
|
+
}
|
|
3857
|
+
},
|
|
3858
|
+
async set(key, snapshot) {
|
|
3859
|
+
const value = JSON.stringify(snapshot);
|
|
3860
|
+
await kv.put(prefix + key, value, options.expirationTtl != null ? { expirationTtl: options.expirationTtl } : void 0);
|
|
3861
|
+
}
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
function kvSkillIndexCacheFromEnv(binding, options) {
|
|
3865
|
+
return (env) => {
|
|
3866
|
+
const ns = env?.[binding];
|
|
3867
|
+
if (!ns) return void 0;
|
|
3868
|
+
return createKvSkillIndexCache(ns, options);
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
// libs/edge/src/kv-cache.ts
|
|
3873
|
+
var DEFAULT_KEY = "frontmcp:bundle:last-good";
|
|
3874
|
+
function createKvBundleCache(kv, options = {}) {
|
|
3875
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
3876
|
+
return {
|
|
3877
|
+
async read() {
|
|
3878
|
+
const raw = await kv.get(key, "text");
|
|
3879
|
+
if (raw == null) return void 0;
|
|
3880
|
+
try {
|
|
3881
|
+
return JSON.parse(raw);
|
|
3882
|
+
} catch {
|
|
3883
|
+
return void 0;
|
|
3884
|
+
}
|
|
3885
|
+
},
|
|
3886
|
+
async write(bundle) {
|
|
3887
|
+
const value = JSON.stringify(bundle);
|
|
3888
|
+
await kv.put(key, value, options.expirationTtl != null ? { expirationTtl: options.expirationTtl } : void 0);
|
|
3889
|
+
}
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
function kvBundleCacheFromEnv(binding, options) {
|
|
3893
|
+
return (env) => {
|
|
3894
|
+
const ns = env?.[binding];
|
|
3895
|
+
if (!ns) return void 0;
|
|
3896
|
+
return createKvBundleCache(ns, options);
|
|
3897
|
+
};
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
// libs/edge/src/index.ts
|
|
3901
|
+
var RUNTIME_DEPS_TOKEN = /* @__PURE__ */ Symbol.for("frontmcp:skilled-openapi:runtime-deps");
|
|
3902
|
+
var EdgeRefreshController = class {
|
|
3903
|
+
constructor(cache, disablePolling = true) {
|
|
3904
|
+
this.cache = cache;
|
|
3905
|
+
this.disablePolling = disablePolling;
|
|
3906
|
+
}
|
|
3907
|
+
cache;
|
|
3908
|
+
disablePolling;
|
|
3909
|
+
source;
|
|
3910
|
+
attach(source) {
|
|
3911
|
+
this.source = source;
|
|
3912
|
+
}
|
|
3913
|
+
async refresh() {
|
|
3914
|
+
if (!this.source) {
|
|
3915
|
+
throw new Error(
|
|
3916
|
+
"createEdgeMcp(managed): no bundle source attached \u2014 the skilled-openapi plugin failed to construct it; Cron refresh cannot run."
|
|
3917
|
+
);
|
|
3918
|
+
}
|
|
3919
|
+
return this.source.refresh?.();
|
|
3920
|
+
}
|
|
3921
|
+
};
|
|
3922
|
+
function resolveCache(cache, env) {
|
|
3923
|
+
if (!cache) return void 0;
|
|
3924
|
+
return typeof cache === "function" ? cache(env) : cache;
|
|
3925
|
+
}
|
|
3926
|
+
var envBridged = false;
|
|
3927
|
+
function bridgeEnvToProcessEnv(env) {
|
|
3928
|
+
if (envBridged || !env || typeof env !== "object") return;
|
|
3929
|
+
const proc = globalThis.process;
|
|
3930
|
+
if (!proc?.env) return;
|
|
3931
|
+
for (const [key, value] of Object.entries(env)) {
|
|
3932
|
+
if (typeof value === "string" && proc.env[key] === void 0) proc.env[key] = value;
|
|
3933
|
+
}
|
|
3934
|
+
envBridged = true;
|
|
3935
|
+
}
|
|
3936
|
+
var DEFAULT_SESSION_BINDING = "FRONTMCP_SESSIONS";
|
|
3937
|
+
var DEFAULT_SKILL_INDEX_BINDING = "FRONTMCP_SKILL_INDEX";
|
|
3938
|
+
function resolveSkillIndexCache(config, env) {
|
|
3939
|
+
const cfg = config.skillIndex;
|
|
3940
|
+
if (!cfg) return void 0;
|
|
3941
|
+
if (typeof cfg === "function") return cfg(env);
|
|
3942
|
+
const factory = kvSkillIndexCacheFromEnv(
|
|
3943
|
+
cfg.binding ?? DEFAULT_SKILL_INDEX_BINDING,
|
|
3944
|
+
cfg.ttlSeconds != null ? { expirationTtl: cfg.ttlSeconds } : void 0
|
|
3945
|
+
);
|
|
3946
|
+
return factory(env);
|
|
3947
|
+
}
|
|
3948
|
+
async function wireSkillIndexCache(scope, config, env) {
|
|
3949
|
+
const cache = resolveSkillIndexCache(config, env);
|
|
3950
|
+
if (!cache) return;
|
|
3951
|
+
const skills = scope.skills;
|
|
3952
|
+
if (typeof skills?.setIndexCache !== "function" || typeof skills.warmIndex !== "function") return;
|
|
3953
|
+
skills.setIndexCache(cache);
|
|
3954
|
+
try {
|
|
3955
|
+
await skills.warmIndex();
|
|
3956
|
+
} catch {
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
function createEdgeMcp(config) {
|
|
3960
|
+
let handlerPromise;
|
|
3961
|
+
let controller;
|
|
3962
|
+
const buildScope = async (env) => {
|
|
3963
|
+
const { managed, sessions: _sessions, skillIndex: _skillIndex, ...rest } = config;
|
|
3964
|
+
const base = { ...rest, serve: false };
|
|
3965
|
+
let frontmcpConfig = base;
|
|
3966
|
+
if (managed) {
|
|
3967
|
+
controller = new EdgeRefreshController(resolveCache(managed.cache, env));
|
|
3968
|
+
const mod = await import("@frontmcp/plugin-skilled-openapi");
|
|
3969
|
+
const SkilledOpenApiPlugin = mod.default;
|
|
3970
|
+
const plugin = SkilledOpenApiPlugin.init(buildManagedOpenApiPluginOptions(managed));
|
|
3971
|
+
const existingPlugins = base.plugins ?? [];
|
|
3972
|
+
const existingProviders = base.providers ?? [];
|
|
3973
|
+
frontmcpConfig = {
|
|
3974
|
+
...base,
|
|
3975
|
+
plugins: [...existingPlugins, plugin],
|
|
3976
|
+
providers: [
|
|
3977
|
+
...existingProviders,
|
|
3978
|
+
{ name: "edge:skilled-openapi-runtime-deps", provide: RUNTIME_DEPS_TOKEN, useValue: controller }
|
|
3979
|
+
]
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
const instance = await FrontMcpInstance.createForGraph(frontmcpConfig);
|
|
3983
|
+
const scope = instance.getScopes()[0];
|
|
3984
|
+
if (!scope) {
|
|
3985
|
+
throw new Error("createEdgeMcp: the config produced no scope \u2014 declare an app/tool or `managed`.");
|
|
3986
|
+
}
|
|
3987
|
+
await wireSkillIndexCache(scope, config, env);
|
|
3988
|
+
return scope;
|
|
3989
|
+
};
|
|
3990
|
+
const sessionRouter = config.sessions ? createEdgeSessionRouter(config.sessions.binding ?? DEFAULT_SESSION_BINDING) : void 0;
|
|
3991
|
+
const build = async (env) => {
|
|
3992
|
+
const scope = await buildScope(env);
|
|
3993
|
+
return createWebFetchHandler(scope, sessionRouter ? { sessionRouter } : {});
|
|
3994
|
+
};
|
|
3995
|
+
const ensureHandler = async (env) => {
|
|
3996
|
+
if (!handlerPromise) handlerPromise = build(env);
|
|
3997
|
+
try {
|
|
3998
|
+
return await handlerPromise;
|
|
3999
|
+
} catch (e) {
|
|
4000
|
+
handlerPromise = void 0;
|
|
4001
|
+
throw e;
|
|
4002
|
+
}
|
|
4003
|
+
};
|
|
4004
|
+
const mcp = {
|
|
4005
|
+
async fetch(request, env, ctx) {
|
|
4006
|
+
bridgeEnvToProcessEnv(env);
|
|
4007
|
+
const handler = await ensureHandler(env);
|
|
4008
|
+
return handler(request, ctx, env);
|
|
4009
|
+
},
|
|
4010
|
+
// The DO builds its own session-local scope (in its isolate) via buildScope,
|
|
4011
|
+
// and bridges `env`→`process.env` the same way the worker does.
|
|
4012
|
+
SessionDurableObject: createEdgeSessionDurableObject(buildScope, bridgeEnvToProcessEnv)
|
|
4013
|
+
};
|
|
4014
|
+
if (config.managed) {
|
|
4015
|
+
mcp.scheduled = async (_event, env) => {
|
|
4016
|
+
bridgeEnvToProcessEnv(env);
|
|
4017
|
+
await ensureHandler(env);
|
|
4018
|
+
await controller?.refresh();
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
return mcp;
|
|
4022
|
+
}
|
|
4023
|
+
export {
|
|
4024
|
+
buildManagedOpenApiPluginOptions,
|
|
4025
|
+
createEdgeMcp,
|
|
4026
|
+
createKvBundleCache,
|
|
4027
|
+
createKvSkillIndexCache,
|
|
4028
|
+
kvBundleCacheFromEnv,
|
|
4029
|
+
kvSkillIndexCacheFromEnv
|
|
4030
|
+
};
|