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