@frontmcp/utils 0.7.2 → 0.8.0

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/esm/index.mjs CHANGED
@@ -52,219 +52,8 @@ var init_jwt_alg = __esm({
52
52
  }
53
53
  });
54
54
 
55
- // libs/utils/src/crypto/node.ts
56
- var node_exports = {};
57
- __export(node_exports, {
58
- createSignedJwt: () => createSignedJwt,
59
- generateRsaKeyPair: () => generateRsaKeyPair,
60
- isRsaPssAlg: () => isRsaPssAlg,
61
- jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
62
- nodeCrypto: () => nodeCrypto,
63
- rsaSign: () => rsaSign,
64
- rsaVerify: () => rsaVerify
65
- });
66
- import crypto2 from "node:crypto";
67
- function toUint8Array(buf) {
68
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
69
- }
70
- function toBuffer(data) {
71
- if (typeof data === "string") {
72
- return Buffer.from(data, "utf8");
73
- }
74
- return Buffer.from(data);
75
- }
76
- function generateRsaKeyPair(modulusLength = 2048, alg = "RS256") {
77
- const kid = `rsa-key-${Date.now()}-${crypto2.randomBytes(8).toString("hex")}`;
78
- const { privateKey, publicKey } = crypto2.generateKeyPairSync("rsa", {
79
- modulusLength
80
- });
81
- const exported = publicKey.export({ format: "jwk" });
82
- const publicJwk = {
83
- ...exported,
84
- kid,
85
- alg,
86
- use: "sig",
87
- kty: "RSA"
88
- };
89
- return { privateKey, publicKey, publicJwk };
90
- }
91
- function rsaSign(algorithm, data, privateKey, options) {
92
- const signingKey = options ? { key: privateKey, ...options } : privateKey;
93
- return crypto2.sign(algorithm, data, signingKey);
94
- }
95
- function rsaVerify(jwtAlg, data, publicJwk, signature) {
96
- const publicKey = crypto2.createPublicKey({ key: publicJwk, format: "jwk" });
97
- const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
98
- const verifyKey = isRsaPssAlg(jwtAlg) ? {
99
- key: publicKey,
100
- padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
101
- saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
102
- } : publicKey;
103
- return crypto2.verify(nodeAlgorithm, data, verifyKey, signature);
104
- }
105
- function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
106
- const header = { alg, typ: "JWT", kid };
107
- const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
108
- const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
109
- const signatureInput = `${headerB64}.${payloadB64}`;
110
- const nodeAlgorithm = jwtAlgToNodeAlg(alg);
111
- const signature = rsaSign(
112
- nodeAlgorithm,
113
- Buffer.from(signatureInput),
114
- privateKey,
115
- isRsaPssAlg(alg) ? {
116
- padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
117
- saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
118
- } : void 0
119
- );
120
- const signatureB64 = signature.toString("base64url");
121
- return `${headerB64}.${payloadB64}.${signatureB64}`;
122
- }
123
- var nodeCrypto;
124
- var init_node = __esm({
125
- "libs/utils/src/crypto/node.ts"() {
126
- "use strict";
127
- init_jwt_alg();
128
- init_jwt_alg();
129
- nodeCrypto = {
130
- randomUUID() {
131
- return crypto2.randomUUID();
132
- },
133
- randomBytes(length) {
134
- return toUint8Array(crypto2.randomBytes(length));
135
- },
136
- sha256(data) {
137
- const hash = crypto2.createHash("sha256").update(toBuffer(data)).digest();
138
- return toUint8Array(hash);
139
- },
140
- sha256Hex(data) {
141
- return crypto2.createHash("sha256").update(toBuffer(data)).digest("hex");
142
- },
143
- hmacSha256(key, data) {
144
- const hmac2 = crypto2.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
145
- return toUint8Array(hmac2);
146
- },
147
- hkdfSha256(ikm, salt, info, length) {
148
- const ikmBuf = Buffer.from(ikm);
149
- const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
150
- const prk = crypto2.createHmac("sha256", saltBuf).update(ikmBuf).digest();
151
- const hashLen = 32;
152
- const n = Math.ceil(length / hashLen);
153
- const chunks = [];
154
- let prev = Buffer.alloc(0);
155
- for (let i = 1; i <= n; i++) {
156
- prev = crypto2.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
157
- chunks.push(prev);
158
- }
159
- return toUint8Array(Buffer.concat(chunks).subarray(0, length));
160
- },
161
- encryptAesGcm(key, plaintext, iv) {
162
- const cipher = crypto2.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
163
- const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
164
- const tag = cipher.getAuthTag();
165
- return {
166
- ciphertext: toUint8Array(encrypted),
167
- tag: toUint8Array(tag)
168
- };
169
- },
170
- decryptAesGcm(key, ciphertext, iv, tag) {
171
- const decipher = crypto2.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
172
- decipher.setAuthTag(Buffer.from(tag));
173
- const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
174
- return toUint8Array(decrypted);
175
- },
176
- timingSafeEqual(a, b) {
177
- if (a.length !== b.length) return false;
178
- return crypto2.timingSafeEqual(Buffer.from(a), Buffer.from(b));
179
- }
180
- };
181
- }
182
- });
183
-
184
- // libs/utils/src/crypto/browser.ts
185
- var browser_exports = {};
186
- __export(browser_exports, {
187
- browserCrypto: () => browserCrypto
188
- });
189
- import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
190
- import { hmac } from "@noble/hashes/hmac.js";
191
- import { hkdf } from "@noble/hashes/hkdf.js";
192
- import { randomBytes as nobleRandomBytes } from "@noble/hashes/utils.js";
193
- import { gcm } from "@noble/ciphers/aes.js";
194
- function toBytes(data) {
195
- if (typeof data === "string") {
196
- return new TextEncoder().encode(data);
197
- }
198
- return data;
199
- }
200
- function toHex(bytes) {
201
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
202
- }
203
- function generateUUID() {
204
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
205
- return crypto.randomUUID();
206
- }
207
- const bytes = nobleRandomBytes(16);
208
- bytes[6] = bytes[6] & 15 | 64;
209
- bytes[8] = bytes[8] & 63 | 128;
210
- const hex = toHex(bytes);
211
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
212
- }
213
- function constantTimeEqual(a, b) {
214
- if (a.length !== b.length) return false;
215
- let result = 0;
216
- for (let i = 0; i < a.length; i++) {
217
- result |= a[i] ^ b[i];
218
- }
219
- return result === 0;
220
- }
221
- var browserCrypto;
222
- var init_browser = __esm({
223
- "libs/utils/src/crypto/browser.ts"() {
224
- "use strict";
225
- browserCrypto = {
226
- randomUUID() {
227
- return generateUUID();
228
- },
229
- randomBytes(length) {
230
- return nobleRandomBytes(length);
231
- },
232
- sha256(data) {
233
- return sha256Hash(toBytes(data));
234
- },
235
- sha256Hex(data) {
236
- return toHex(sha256Hash(toBytes(data)));
237
- },
238
- hmacSha256(key, data) {
239
- return hmac(sha256Hash, key, data);
240
- },
241
- hkdfSha256(ikm, salt, info, length) {
242
- const effectiveSalt = salt.length > 0 ? salt : new Uint8Array(32);
243
- return hkdf(sha256Hash, ikm, effectiveSalt, info, length);
244
- },
245
- encryptAesGcm(key, plaintext, iv) {
246
- const cipher = gcm(key, iv);
247
- const sealed = cipher.encrypt(plaintext);
248
- const ciphertext = sealed.slice(0, -16);
249
- const tag = sealed.slice(-16);
250
- return { ciphertext, tag };
251
- },
252
- decryptAesGcm(key, ciphertext, iv, tag) {
253
- const cipher = gcm(key, iv);
254
- const sealed = new Uint8Array(ciphertext.length + tag.length);
255
- sealed.set(ciphertext);
256
- sealed.set(tag, ciphertext.length);
257
- return cipher.decrypt(sealed);
258
- },
259
- timingSafeEqual(a, b) {
260
- return constantTimeEqual(a, b);
261
- }
262
- };
263
- }
264
- });
265
-
266
55
  // libs/utils/src/storage/errors.ts
267
- var StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError;
56
+ var StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError;
268
57
  var init_errors = __esm({
269
58
  "libs/utils/src/storage/errors.ts"() {
270
59
  "use strict";
@@ -282,7 +71,8 @@ var init_errors = __esm({
282
71
  };
283
72
  StorageConnectionError = class extends StorageError {
284
73
  constructor(message, cause, backend) {
285
- super(message, cause);
74
+ const fullMessage = cause ? `${message}: ${cause.message}` : message;
75
+ super(fullMessage, cause);
286
76
  this.backend = backend;
287
77
  this.name = "StorageConnectionError";
288
78
  }
@@ -332,6 +122,12 @@ var init_errors = __esm({
332
122
  this.name = "StorageNotConnectedError";
333
123
  }
334
124
  };
125
+ EncryptedStorageError = class extends StorageError {
126
+ constructor(message) {
127
+ super(message);
128
+ this.name = "EncryptedStorageError";
129
+ }
130
+ };
335
131
  }
336
132
  });
337
133
 
@@ -497,15 +293,309 @@ var init_base = __esm({
497
293
  }
498
294
  });
499
295
 
500
- // libs/utils/src/storage/adapters/redis.ts
501
- var redis_exports = {};
502
- __export(redis_exports, {
503
- RedisStorageAdapter: () => RedisStorageAdapter
504
- });
505
- function getRedisClass() {
296
+ // libs/utils/src/storage/utils/pattern.ts
297
+ function globToRegex(pattern) {
298
+ if (pattern.length > MAX_PATTERN_LENGTH) {
299
+ throw new StoragePatternError(
300
+ pattern.substring(0, 50) + "...",
301
+ `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
302
+ );
303
+ }
304
+ const wildcardCount = (pattern.match(/[*?]/g) || []).length;
305
+ if (wildcardCount > MAX_WILDCARDS) {
306
+ throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
307
+ }
308
+ if (pattern === "" || pattern === "*") {
309
+ return /^.*$/;
310
+ }
311
+ let regexStr = "^";
312
+ let prevChar = "";
313
+ for (let i = 0; i < pattern.length; i++) {
314
+ const char = pattern[i];
315
+ switch (char) {
316
+ case "*":
317
+ if (prevChar !== "*") {
318
+ regexStr += ".*";
319
+ }
320
+ break;
321
+ case "?":
322
+ regexStr += ".";
323
+ break;
324
+ default:
325
+ regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
326
+ }
327
+ prevChar = char;
328
+ }
329
+ regexStr += "$";
506
330
  try {
507
- return __require("ioredis").default || __require("ioredis");
331
+ return new RegExp(regexStr);
508
332
  } catch {
333
+ throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
334
+ }
335
+ }
336
+ function matchesPattern(key, pattern) {
337
+ const regex = globToRegex(pattern);
338
+ return regex.test(key);
339
+ }
340
+ function validatePattern(pattern) {
341
+ try {
342
+ globToRegex(pattern);
343
+ return { valid: true };
344
+ } catch (e) {
345
+ return {
346
+ valid: false,
347
+ error: e instanceof Error ? e.message : "Invalid pattern"
348
+ };
349
+ }
350
+ }
351
+ function escapeGlob(literal) {
352
+ return literal.replace(/[*?\\]/g, "\\$&");
353
+ }
354
+ var MAX_PATTERN_LENGTH, MAX_WILDCARDS, REGEX_SPECIAL_CHARS;
355
+ var init_pattern = __esm({
356
+ "libs/utils/src/storage/utils/pattern.ts"() {
357
+ "use strict";
358
+ init_errors();
359
+ MAX_PATTERN_LENGTH = 500;
360
+ MAX_WILDCARDS = 20;
361
+ REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
362
+ }
363
+ });
364
+
365
+ // libs/utils/src/crypto/node.ts
366
+ var node_exports = {};
367
+ __export(node_exports, {
368
+ createSignedJwt: () => createSignedJwt,
369
+ generateRsaKeyPair: () => generateRsaKeyPair,
370
+ isRsaPssAlg: () => isRsaPssAlg,
371
+ jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
372
+ nodeCrypto: () => nodeCrypto,
373
+ rsaSign: () => rsaSign,
374
+ rsaVerify: () => rsaVerify
375
+ });
376
+ import crypto2 from "node:crypto";
377
+ function toUint8Array(buf) {
378
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
379
+ }
380
+ function toBuffer(data) {
381
+ if (typeof data === "string") {
382
+ return Buffer.from(data, "utf8");
383
+ }
384
+ return Buffer.from(data);
385
+ }
386
+ function generateRsaKeyPair(modulusLength = 2048, alg = "RS256") {
387
+ const kid = `rsa-key-${Date.now()}-${crypto2.randomBytes(8).toString("hex")}`;
388
+ const { privateKey, publicKey } = crypto2.generateKeyPairSync("rsa", {
389
+ modulusLength
390
+ });
391
+ const exported = publicKey.export({ format: "jwk" });
392
+ const publicJwk = {
393
+ ...exported,
394
+ kid,
395
+ alg,
396
+ use: "sig",
397
+ kty: "RSA"
398
+ };
399
+ return { privateKey, publicKey, publicJwk };
400
+ }
401
+ function rsaSign(algorithm, data, privateKey, options) {
402
+ const signingKey = options ? { key: privateKey, ...options } : privateKey;
403
+ return crypto2.sign(algorithm, data, signingKey);
404
+ }
405
+ function rsaVerify(jwtAlg, data, publicJwk, signature) {
406
+ const publicKey = crypto2.createPublicKey({ key: publicJwk, format: "jwk" });
407
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
408
+ const verifyKey = isRsaPssAlg(jwtAlg) ? {
409
+ key: publicKey,
410
+ padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
411
+ saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
412
+ } : publicKey;
413
+ return crypto2.verify(nodeAlgorithm, data, verifyKey, signature);
414
+ }
415
+ function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
416
+ const header = { alg, typ: "JWT", kid };
417
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
418
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
419
+ const signatureInput = `${headerB64}.${payloadB64}`;
420
+ const nodeAlgorithm = jwtAlgToNodeAlg(alg);
421
+ const signature = rsaSign(
422
+ nodeAlgorithm,
423
+ Buffer.from(signatureInput),
424
+ privateKey,
425
+ isRsaPssAlg(alg) ? {
426
+ padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
427
+ saltLength: crypto2.constants.RSA_PSS_SALTLEN_DIGEST
428
+ } : void 0
429
+ );
430
+ const signatureB64 = signature.toString("base64url");
431
+ return `${headerB64}.${payloadB64}.${signatureB64}`;
432
+ }
433
+ var nodeCrypto;
434
+ var init_node = __esm({
435
+ "libs/utils/src/crypto/node.ts"() {
436
+ "use strict";
437
+ init_jwt_alg();
438
+ init_jwt_alg();
439
+ nodeCrypto = {
440
+ randomUUID() {
441
+ return crypto2.randomUUID();
442
+ },
443
+ randomBytes(length) {
444
+ return toUint8Array(crypto2.randomBytes(length));
445
+ },
446
+ sha256(data) {
447
+ const hash = crypto2.createHash("sha256").update(toBuffer(data)).digest();
448
+ return toUint8Array(hash);
449
+ },
450
+ sha256Hex(data) {
451
+ return crypto2.createHash("sha256").update(toBuffer(data)).digest("hex");
452
+ },
453
+ hmacSha256(key, data) {
454
+ const hmac2 = crypto2.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
455
+ return toUint8Array(hmac2);
456
+ },
457
+ hkdfSha256(ikm, salt, info, length) {
458
+ const ikmBuf = Buffer.from(ikm);
459
+ const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
460
+ const prk = crypto2.createHmac("sha256", saltBuf).update(ikmBuf).digest();
461
+ const hashLen = 32;
462
+ const n = Math.ceil(length / hashLen);
463
+ const chunks = [];
464
+ let prev = Buffer.alloc(0);
465
+ for (let i = 1; i <= n; i++) {
466
+ prev = crypto2.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
467
+ chunks.push(prev);
468
+ }
469
+ return toUint8Array(Buffer.concat(chunks).subarray(0, length));
470
+ },
471
+ encryptAesGcm(key, plaintext, iv) {
472
+ const cipher = crypto2.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
473
+ const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
474
+ const tag = cipher.getAuthTag();
475
+ return {
476
+ ciphertext: toUint8Array(encrypted),
477
+ tag: toUint8Array(tag)
478
+ };
479
+ },
480
+ decryptAesGcm(key, ciphertext, iv, tag) {
481
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
482
+ decipher.setAuthTag(Buffer.from(tag));
483
+ const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
484
+ return toUint8Array(decrypted);
485
+ },
486
+ timingSafeEqual(a, b) {
487
+ if (a.length !== b.length) return false;
488
+ return crypto2.timingSafeEqual(Buffer.from(a), Buffer.from(b));
489
+ }
490
+ };
491
+ }
492
+ });
493
+
494
+ // libs/utils/src/crypto/browser.ts
495
+ var browser_exports = {};
496
+ __export(browser_exports, {
497
+ browserCrypto: () => browserCrypto
498
+ });
499
+ import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
500
+ import { hmac } from "@noble/hashes/hmac.js";
501
+ import { hkdf } from "@noble/hashes/hkdf.js";
502
+ import { randomBytes as nobleRandomBytes } from "@noble/hashes/utils.js";
503
+ import { gcm } from "@noble/ciphers/aes.js";
504
+ function toBytes(data) {
505
+ if (typeof data === "string") {
506
+ return new TextEncoder().encode(data);
507
+ }
508
+ return data;
509
+ }
510
+ function toHex(bytes) {
511
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
512
+ }
513
+ function generateUUID() {
514
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
515
+ return crypto.randomUUID();
516
+ }
517
+ const bytes = nobleRandomBytes(16);
518
+ bytes[6] = bytes[6] & 15 | 64;
519
+ bytes[8] = bytes[8] & 63 | 128;
520
+ const hex = toHex(bytes);
521
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
522
+ }
523
+ function constantTimeEqual(a, b) {
524
+ if (a.length !== b.length) return false;
525
+ let result = 0;
526
+ for (let i = 0; i < a.length; i++) {
527
+ result |= a[i] ^ b[i];
528
+ }
529
+ return result === 0;
530
+ }
531
+ var browserCrypto;
532
+ var init_browser = __esm({
533
+ "libs/utils/src/crypto/browser.ts"() {
534
+ "use strict";
535
+ browserCrypto = {
536
+ randomUUID() {
537
+ return generateUUID();
538
+ },
539
+ randomBytes(length) {
540
+ return nobleRandomBytes(length);
541
+ },
542
+ sha256(data) {
543
+ return sha256Hash(toBytes(data));
544
+ },
545
+ sha256Hex(data) {
546
+ return toHex(sha256Hash(toBytes(data)));
547
+ },
548
+ hmacSha256(key, data) {
549
+ return hmac(sha256Hash, key, data);
550
+ },
551
+ hkdfSha256(ikm, salt, info, length) {
552
+ const effectiveSalt = salt.length > 0 ? salt : new Uint8Array(32);
553
+ return hkdf(sha256Hash, ikm, effectiveSalt, info, length);
554
+ },
555
+ encryptAesGcm(key, plaintext, iv) {
556
+ const cipher = gcm(key, iv);
557
+ const sealed = cipher.encrypt(plaintext);
558
+ const ciphertext = sealed.slice(0, -16);
559
+ const tag = sealed.slice(-16);
560
+ return { ciphertext, tag };
561
+ },
562
+ decryptAesGcm(key, ciphertext, iv, tag) {
563
+ const cipher = gcm(key, iv);
564
+ const sealed = new Uint8Array(ciphertext.length + tag.length);
565
+ sealed.set(ciphertext);
566
+ sealed.set(tag, ciphertext.length);
567
+ return cipher.decrypt(sealed);
568
+ },
569
+ timingSafeEqual(a, b) {
570
+ return constantTimeEqual(a, b);
571
+ }
572
+ };
573
+ }
574
+ });
575
+
576
+ // libs/utils/src/storage/utils/index.ts
577
+ var init_utils = __esm({
578
+ "libs/utils/src/storage/utils/index.ts"() {
579
+ "use strict";
580
+ init_ttl();
581
+ }
582
+ });
583
+
584
+ // libs/utils/src/storage/adapters/redis.ts
585
+ var redis_exports = {};
586
+ __export(redis_exports, {
587
+ RedisStorageAdapter: () => RedisStorageAdapter
588
+ });
589
+ function getRedisClass() {
590
+ try {
591
+ return __require("ioredis").default || __require("ioredis");
592
+ } catch (error) {
593
+ const msg = error instanceof Error ? error.message : String(error);
594
+ if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
595
+ throw new Error(
596
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`
597
+ );
598
+ }
509
599
  throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
510
600
  }
511
601
  }
@@ -515,7 +605,7 @@ var init_redis = __esm({
515
605
  "use strict";
516
606
  init_base();
517
607
  init_errors();
518
- init_ttl();
608
+ init_utils();
519
609
  RedisStorageAdapter = class extends BaseStorageAdapter {
520
610
  backendName = "redis";
521
611
  client;
@@ -1841,6 +1931,7 @@ function assertNode(feature) {
1841
1931
 
1842
1932
  // libs/utils/src/fs/fs.ts
1843
1933
  var _fsp = null;
1934
+ var _fs = null;
1844
1935
  var _spawn = null;
1845
1936
  function getFsp() {
1846
1937
  if (!_fsp) {
@@ -1849,8 +1940,15 @@ function getFsp() {
1849
1940
  }
1850
1941
  return _fsp;
1851
1942
  }
1852
- function getSpawn() {
1853
- if (!_spawn) {
1943
+ function getFs() {
1944
+ if (!_fs) {
1945
+ assertNode("File system operations");
1946
+ _fs = __require("fs");
1947
+ }
1948
+ return _fs;
1949
+ }
1950
+ function getSpawn() {
1951
+ if (!_spawn) {
1854
1952
  assertNode("Child process operations");
1855
1953
  _spawn = __require("child_process").spawn;
1856
1954
  }
@@ -1861,6 +1959,10 @@ async function readFile(p, encoding = "utf8") {
1861
1959
  const fsp = getFsp();
1862
1960
  return fsp.readFile(p, encoding);
1863
1961
  }
1962
+ function readFileSync(p, encoding = "utf8") {
1963
+ const fs = getFs();
1964
+ return fs.readFileSync(p, encoding);
1965
+ }
1864
1966
  async function readFileBuffer(p) {
1865
1967
  const fsp = getFsp();
1866
1968
  return fsp.readFile(p);
@@ -2356,379 +2458,372 @@ function isSecretCached(options) {
2356
2458
  return secretCache.has(cacheKey);
2357
2459
  }
2358
2460
 
2359
- // libs/utils/src/crypto/index.ts
2360
- var _provider = null;
2361
- function getCrypto() {
2362
- if (!_provider) {
2363
- if (isNode()) {
2364
- _provider = (init_node(), __toCommonJS(node_exports)).nodeCrypto;
2365
- } else {
2366
- _provider = (init_browser(), __toCommonJS(browser_exports)).browserCrypto;
2367
- }
2368
- }
2369
- return _provider;
2370
- }
2371
- function rsaVerify2(jwtAlg, data, publicJwk, signature) {
2372
- assertNode("rsaVerify");
2373
- return (init_node(), __toCommonJS(node_exports)).rsaVerify(jwtAlg, data, publicJwk, signature);
2374
- }
2375
- function randomUUID() {
2376
- return getCrypto().randomUUID();
2461
+ // libs/utils/src/crypto/hmac-signing.ts
2462
+ function computeSignature(data, secret) {
2463
+ const encoder = new TextEncoder();
2464
+ const keyBytes = encoder.encode(secret);
2465
+ const dataBytes = encoder.encode(data);
2466
+ const hmac2 = hmacSha256(keyBytes, dataBytes);
2467
+ return base64urlEncode(hmac2);
2468
+ }
2469
+ function signData(data, config) {
2470
+ const jsonData = JSON.stringify(data);
2471
+ const sig = computeSignature(jsonData, config.secret);
2472
+ const signed = {
2473
+ data,
2474
+ sig,
2475
+ v: 1
2476
+ };
2477
+ return JSON.stringify(signed);
2377
2478
  }
2378
- function randomBytes(length) {
2379
- if (!Number.isInteger(length) || length <= 0) {
2380
- throw new Error(`randomBytes length must be a positive integer, got ${length}`);
2479
+ function verifyData(signedJson, config) {
2480
+ try {
2481
+ const parsed = JSON.parse(signedJson);
2482
+ if (!parsed || typeof parsed !== "object" || !("sig" in parsed)) {
2483
+ return null;
2484
+ }
2485
+ const signed = parsed;
2486
+ if (signed.v !== 1) {
2487
+ return null;
2488
+ }
2489
+ const jsonData = JSON.stringify(signed.data);
2490
+ const expectedSig = computeSignature(jsonData, config.secret);
2491
+ const sigBytes = base64urlDecode(signed.sig);
2492
+ const expectedBytes = base64urlDecode(expectedSig);
2493
+ if (sigBytes.length !== expectedBytes.length) {
2494
+ return null;
2495
+ }
2496
+ if (!timingSafeEqual(sigBytes, expectedBytes)) {
2497
+ return null;
2498
+ }
2499
+ return signed.data;
2500
+ } catch {
2501
+ return null;
2381
2502
  }
2382
- return getCrypto().randomBytes(length);
2383
- }
2384
- function sha256(data) {
2385
- return getCrypto().sha256(data);
2386
- }
2387
- function sha256Hex(data) {
2388
- return getCrypto().sha256Hex(data);
2389
- }
2390
- function hmacSha256(key, data) {
2391
- return getCrypto().hmacSha256(key, data);
2392
2503
  }
2393
- var HKDF_SHA256_MAX_LENGTH = 255 * 32;
2394
- function hkdfSha256(ikm, salt, info, length) {
2395
- if (!Number.isInteger(length) || length <= 0) {
2396
- throw new Error(`HKDF length must be a positive integer, got ${length}`);
2397
- }
2398
- if (length > HKDF_SHA256_MAX_LENGTH) {
2399
- throw new Error(`HKDF-SHA256 length cannot exceed ${HKDF_SHA256_MAX_LENGTH} bytes, got ${length}`);
2504
+ function isSignedData(json) {
2505
+ try {
2506
+ const parsed = JSON.parse(json);
2507
+ return parsed && typeof parsed === "object" && "sig" in parsed && "v" in parsed;
2508
+ } catch {
2509
+ return false;
2400
2510
  }
2401
- return getCrypto().hkdfSha256(ikm, salt, info, length);
2402
2511
  }
2403
- function encryptAesGcm(key, plaintext, iv) {
2404
- if (key.length !== 32) {
2405
- throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
2512
+ function verifyOrParseData(json, config) {
2513
+ if (isSignedData(json)) {
2514
+ return verifyData(json, config);
2406
2515
  }
2407
- if (iv.length !== 12) {
2408
- throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
2516
+ try {
2517
+ return JSON.parse(json);
2518
+ } catch {
2519
+ return null;
2409
2520
  }
2410
- return getCrypto().encryptAesGcm(key, plaintext, iv);
2411
2521
  }
2412
- function decryptAesGcm(key, ciphertext, iv, tag) {
2413
- if (key.length !== 32) {
2414
- throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
2522
+
2523
+ // libs/utils/src/crypto/key-persistence/schemas.ts
2524
+ import { z as z2 } from "zod";
2525
+ var MAX_CLOCK_DRIFT_MS2 = 6e4;
2526
+ var MAX_KEY_AGE_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
2527
+ var asymmetricAlgSchema = z2.enum(["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]);
2528
+ var baseKeyDataSchema = z2.object({
2529
+ kid: z2.string().min(1),
2530
+ createdAt: z2.number().positive().int(),
2531
+ version: z2.number().positive().int()
2532
+ });
2533
+ var jsonWebKeySchema = z2.object({
2534
+ kty: z2.string().optional(),
2535
+ use: z2.string().optional(),
2536
+ alg: z2.string().optional(),
2537
+ kid: z2.string().optional(),
2538
+ n: z2.string().optional(),
2539
+ e: z2.string().optional(),
2540
+ d: z2.string().optional(),
2541
+ p: z2.string().optional(),
2542
+ q: z2.string().optional(),
2543
+ dp: z2.string().optional(),
2544
+ dq: z2.string().optional(),
2545
+ qi: z2.string().optional(),
2546
+ x: z2.string().optional(),
2547
+ y: z2.string().optional(),
2548
+ crv: z2.string().optional()
2549
+ }).passthrough();
2550
+ var secretKeyDataSchema = baseKeyDataSchema.extend({
2551
+ type: z2.literal("secret"),
2552
+ secret: z2.string().min(1),
2553
+ bytes: z2.number().positive().int()
2554
+ });
2555
+ var asymmetricKeyDataSchema = baseKeyDataSchema.extend({
2556
+ type: z2.literal("asymmetric"),
2557
+ alg: asymmetricAlgSchema,
2558
+ privateKey: jsonWebKeySchema,
2559
+ publicJwk: z2.object({
2560
+ keys: z2.array(jsonWebKeySchema).min(1)
2561
+ })
2562
+ });
2563
+ var anyKeyDataSchema = z2.discriminatedUnion("type", [secretKeyDataSchema, asymmetricKeyDataSchema]);
2564
+ function validateKeyData(data) {
2565
+ const result = anyKeyDataSchema.safeParse(data);
2566
+ if (!result.success) {
2567
+ return {
2568
+ valid: false,
2569
+ error: result.error.issues[0]?.message ?? "Invalid key structure"
2570
+ };
2415
2571
  }
2416
- if (iv.length !== 12) {
2417
- throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
2572
+ const parsed = result.data;
2573
+ const now = Date.now();
2574
+ if (parsed.createdAt > now + MAX_CLOCK_DRIFT_MS2) {
2575
+ return { valid: false, error: "createdAt is in the future" };
2418
2576
  }
2419
- if (tag.length !== 16) {
2420
- throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`);
2577
+ if (parsed.createdAt < now - MAX_KEY_AGE_MS) {
2578
+ return { valid: false, error: "createdAt is too old" };
2421
2579
  }
2422
- return getCrypto().decryptAesGcm(key, ciphertext, iv, tag);
2580
+ return { valid: true, data: parsed };
2423
2581
  }
2424
- function timingSafeEqual(a, b) {
2425
- if (a.length !== b.length) {
2426
- throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`);
2582
+ function parseKeyData(data) {
2583
+ const validation = validateKeyData(data);
2584
+ if (!validation.valid) {
2585
+ return null;
2427
2586
  }
2428
- return getCrypto().timingSafeEqual(a, b);
2587
+ return validation.data;
2429
2588
  }
2430
- function bytesToHex(data) {
2431
- return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
2589
+ function isSecretKeyData(data) {
2590
+ return data.type === "secret";
2432
2591
  }
2433
- function base64urlEncode(data) {
2434
- let base64;
2435
- if (typeof Buffer !== "undefined") {
2436
- base64 = Buffer.from(data).toString("base64");
2437
- } else {
2438
- const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
2439
- base64 = btoa(binString);
2440
- }
2441
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2592
+ function isAsymmetricKeyData(data) {
2593
+ return data.type === "asymmetric";
2442
2594
  }
2443
- function base64urlDecode(data) {
2444
- let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
2445
- const pad = base64.length % 4;
2446
- if (pad) {
2447
- base64 += "=".repeat(4 - pad);
2595
+
2596
+ // libs/utils/src/crypto/key-persistence/key-persistence.ts
2597
+ var KeyPersistence = class {
2598
+ storage;
2599
+ cache = /* @__PURE__ */ new Map();
2600
+ enableCache;
2601
+ throwOnInvalid;
2602
+ constructor(options) {
2603
+ this.storage = options.storage;
2604
+ this.enableCache = options.enableCache ?? true;
2605
+ this.throwOnInvalid = options.throwOnInvalid ?? false;
2448
2606
  }
2449
- if (typeof Buffer !== "undefined") {
2450
- return new Uint8Array(Buffer.from(base64, "base64"));
2451
- } else {
2452
- const binString = atob(base64);
2453
- return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
2607
+ /**
2608
+ * Get a key by ID.
2609
+ *
2610
+ * @param kid - Key identifier
2611
+ * @returns Key data or null if not found
2612
+ */
2613
+ async get(kid) {
2614
+ if (this.enableCache) {
2615
+ const cached = this.cache.get(kid);
2616
+ if (cached) return cached;
2617
+ }
2618
+ const raw = await this.storage.get(kid);
2619
+ if (!raw) return null;
2620
+ let parsed;
2621
+ try {
2622
+ parsed = JSON.parse(raw);
2623
+ } catch {
2624
+ if (this.throwOnInvalid) {
2625
+ throw new Error(`Invalid JSON for key "${kid}"`);
2626
+ }
2627
+ return null;
2628
+ }
2629
+ const validation = validateKeyData(parsed);
2630
+ if (!validation.valid) {
2631
+ if (this.throwOnInvalid) {
2632
+ throw new Error(`Invalid key data for "${kid}": ${validation.error}`);
2633
+ }
2634
+ return null;
2635
+ }
2636
+ const key = validation.data;
2637
+ if (this.enableCache) {
2638
+ this.cache.set(kid, key);
2639
+ }
2640
+ return key;
2454
2641
  }
2455
- }
2456
- function sha256Base64url(data) {
2457
- return base64urlEncode(sha256(data));
2458
- }
2459
-
2460
- // libs/utils/src/storage/factory.ts
2461
- init_errors();
2462
-
2463
- // libs/utils/src/storage/namespace.ts
2464
- var NAMESPACE_SEPARATOR = ":";
2465
- function buildPrefix(name, id) {
2466
- if (id) {
2467
- return `${name}${NAMESPACE_SEPARATOR}${id}${NAMESPACE_SEPARATOR}`;
2642
+ /**
2643
+ * Get a secret key by ID.
2644
+ *
2645
+ * @param kid - Key identifier
2646
+ * @returns Secret key data or null if not found or wrong type
2647
+ */
2648
+ async getSecret(kid) {
2649
+ const key = await this.get(kid);
2650
+ if (!key || !isSecretKeyData(key)) return null;
2651
+ return key;
2468
2652
  }
2469
- return `${name}${NAMESPACE_SEPARATOR}`;
2470
- }
2471
- var NamespacedStorageImpl = class _NamespacedStorageImpl {
2472
- constructor(adapter, prefix = "", root = adapter) {
2473
- this.adapter = adapter;
2474
- this.prefix = prefix;
2475
- this.root = root;
2653
+ /**
2654
+ * Get an asymmetric key by ID.
2655
+ *
2656
+ * @param kid - Key identifier
2657
+ * @returns Asymmetric key data or null if not found or wrong type
2658
+ */
2659
+ async getAsymmetric(kid) {
2660
+ const key = await this.get(kid);
2661
+ if (!key || !isAsymmetricKeyData(key)) return null;
2662
+ return key;
2476
2663
  }
2477
- // ============================================
2478
- // Key Prefixing Helpers
2479
- // ============================================
2480
2664
  /**
2481
- * Add prefix to a key.
2665
+ * Store a key.
2666
+ *
2667
+ * @param key - Key data to store
2482
2668
  */
2483
- prefixKey(key) {
2484
- return this.prefix + key;
2669
+ async set(key) {
2670
+ const validation = validateKeyData(key);
2671
+ if (!validation.valid) {
2672
+ throw new Error(`Invalid key data: ${validation.error}`);
2673
+ }
2674
+ await this.storage.set(key.kid, JSON.stringify(key, null, 2));
2675
+ if (this.enableCache) {
2676
+ this.cache.set(key.kid, key);
2677
+ }
2485
2678
  }
2486
2679
  /**
2487
- * Remove prefix from a key.
2680
+ * Delete a key.
2681
+ *
2682
+ * @param kid - Key identifier
2683
+ * @returns true if key existed and was deleted
2488
2684
  */
2489
- unprefixKey(key) {
2490
- if (this.prefix && key.startsWith(this.prefix)) {
2491
- return key.slice(this.prefix.length);
2685
+ async delete(kid) {
2686
+ this.cache.delete(kid);
2687
+ return this.storage.delete(kid);
2688
+ }
2689
+ /**
2690
+ * Check if a key exists.
2691
+ *
2692
+ * @param kid - Key identifier
2693
+ * @returns true if key exists
2694
+ */
2695
+ async has(kid) {
2696
+ if (this.enableCache && this.cache.has(kid)) {
2697
+ return true;
2492
2698
  }
2493
- return key;
2699
+ return this.storage.exists(kid);
2494
2700
  }
2495
2701
  /**
2496
- * Add prefix to a pattern (for keys() operation).
2702
+ * List all key IDs.
2703
+ *
2704
+ * @returns Array of key identifiers
2497
2705
  */
2498
- prefixPattern(pattern) {
2499
- return this.prefix + pattern;
2706
+ async list() {
2707
+ return this.storage.keys();
2500
2708
  }
2501
2709
  /**
2502
- * Add prefix to a channel (for pub/sub).
2710
+ * Get or create a secret key.
2711
+ *
2712
+ * If a key with the given ID exists and is a valid secret key,
2713
+ * it is returned. Otherwise, a new secret key is generated.
2714
+ *
2715
+ * @param kid - Key identifier
2716
+ * @param options - Options for key creation
2717
+ * @returns Secret key data
2718
+ *
2719
+ * @example
2720
+ * ```typescript
2721
+ * const secret = await keys.getOrCreateSecret('encryption-key');
2722
+ * const bytes = base64urlDecode(secret.secret);
2723
+ * ```
2503
2724
  */
2504
- prefixChannel(channel) {
2505
- return this.prefix + channel;
2725
+ async getOrCreateSecret(kid, options) {
2726
+ const existing = await this.getSecret(kid);
2727
+ if (existing) {
2728
+ return existing;
2729
+ }
2730
+ const bytes = options?.bytes ?? 32;
2731
+ const secret = {
2732
+ type: "secret",
2733
+ kid,
2734
+ secret: base64urlEncode(randomBytes(bytes)),
2735
+ bytes,
2736
+ createdAt: Date.now(),
2737
+ version: 1
2738
+ };
2739
+ await this.set(secret);
2740
+ return secret;
2506
2741
  }
2507
- // ============================================
2508
- // Namespace API
2509
- // ============================================
2510
- namespace(name, id) {
2511
- const newPrefix = this.prefix + buildPrefix(name, id);
2512
- return new _NamespacedStorageImpl(this.adapter, newPrefix, this.root);
2742
+ /**
2743
+ * Clear the in-memory cache.
2744
+ *
2745
+ * Useful when you want to force a reload from storage.
2746
+ */
2747
+ clearCache() {
2748
+ this.cache.clear();
2749
+ }
2750
+ /**
2751
+ * Clear cache for a specific key.
2752
+ *
2753
+ * @param kid - Key identifier
2754
+ */
2755
+ clearCacheFor(kid) {
2756
+ this.cache.delete(kid);
2757
+ }
2758
+ /**
2759
+ * Check if a key is in the cache.
2760
+ *
2761
+ * @param kid - Key identifier
2762
+ * @returns true if key is cached
2763
+ */
2764
+ isCached(kid) {
2765
+ return this.cache.has(kid);
2766
+ }
2767
+ /**
2768
+ * Get the underlying storage adapter.
2769
+ *
2770
+ * Use with caution - direct storage access bypasses validation.
2771
+ */
2772
+ getAdapter() {
2773
+ return this.storage;
2774
+ }
2775
+ };
2776
+
2777
+ // libs/utils/src/storage/adapters/memory.ts
2778
+ init_base();
2779
+ init_errors();
2780
+ init_pattern();
2781
+ init_ttl();
2782
+ import { EventEmitter } from "events";
2783
+ var DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
2784
+ var MAX_TIMEOUT_MS = 2147483647;
2785
+ var MemoryStorageAdapter = class extends BaseStorageAdapter {
2786
+ backendName = "memory";
2787
+ store = /* @__PURE__ */ new Map();
2788
+ emitter = new EventEmitter();
2789
+ sweepInterval;
2790
+ options;
2791
+ // LRU tracking (simple linked list via insertion order in Map)
2792
+ accessOrder = [];
2793
+ constructor(options = {}) {
2794
+ super();
2795
+ this.options = {
2796
+ enableSweeper: options.enableSweeper ?? true,
2797
+ sweepIntervalSeconds: options.sweepIntervalSeconds ?? DEFAULT_SWEEP_INTERVAL_SECONDS,
2798
+ maxEntries: options.maxEntries ?? 0
2799
+ // 0 = unlimited
2800
+ };
2801
+ this.emitter.setMaxListeners(1e3);
2513
2802
  }
2514
2803
  // ============================================
2515
- // Connection Lifecycle (delegated to root)
2804
+ // Connection Lifecycle
2516
2805
  // ============================================
2517
2806
  async connect() {
2518
- return this.adapter.connect();
2807
+ if (this.connected) return;
2808
+ this.connected = true;
2809
+ if (this.options.enableSweeper && this.options.sweepIntervalSeconds > 0) {
2810
+ this.startSweeper();
2811
+ }
2519
2812
  }
2520
2813
  async disconnect() {
2521
- return this.adapter.disconnect();
2814
+ if (!this.connected) return;
2815
+ this.stopSweeper();
2816
+ this.clearAllTimeouts();
2817
+ this.store.clear();
2818
+ this.accessOrder = [];
2819
+ this.emitter.removeAllListeners();
2820
+ this.connected = false;
2522
2821
  }
2523
2822
  async ping() {
2524
- return this.adapter.ping();
2823
+ return this.connected;
2525
2824
  }
2526
2825
  // ============================================
2527
- // Core Operations (with prefixing)
2528
- // ============================================
2529
- async get(key) {
2530
- return this.adapter.get(this.prefixKey(key));
2531
- }
2532
- async set(key, value, options) {
2533
- return this.adapter.set(this.prefixKey(key), value, options);
2534
- }
2535
- async delete(key) {
2536
- return this.adapter.delete(this.prefixKey(key));
2537
- }
2538
- async exists(key) {
2539
- return this.adapter.exists(this.prefixKey(key));
2540
- }
2541
- // ============================================
2542
- // Batch Operations (with prefixing)
2543
- // ============================================
2544
- async mget(keys) {
2545
- const prefixedKeys = keys.map((k) => this.prefixKey(k));
2546
- return this.adapter.mget(prefixedKeys);
2547
- }
2548
- async mset(entries) {
2549
- const prefixedEntries = entries.map((e) => ({
2550
- ...e,
2551
- key: this.prefixKey(e.key)
2552
- }));
2553
- return this.adapter.mset(prefixedEntries);
2554
- }
2555
- async mdelete(keys) {
2556
- const prefixedKeys = keys.map((k) => this.prefixKey(k));
2557
- return this.adapter.mdelete(prefixedKeys);
2558
- }
2559
- // ============================================
2560
- // TTL Operations (with prefixing)
2561
- // ============================================
2562
- async expire(key, ttlSeconds) {
2563
- return this.adapter.expire(this.prefixKey(key), ttlSeconds);
2564
- }
2565
- async ttl(key) {
2566
- return this.adapter.ttl(this.prefixKey(key));
2567
- }
2568
- // ============================================
2569
- // Key Enumeration (with prefixing)
2570
- // ============================================
2571
- async keys(pattern = "*") {
2572
- const prefixedPattern = this.prefixPattern(pattern);
2573
- const keys = await this.adapter.keys(prefixedPattern);
2574
- return keys.map((k) => this.unprefixKey(k));
2575
- }
2576
- async count(pattern = "*") {
2577
- const prefixedPattern = this.prefixPattern(pattern);
2578
- return this.adapter.count(prefixedPattern);
2579
- }
2580
- // ============================================
2581
- // Atomic Operations (with prefixing)
2582
- // ============================================
2583
- async incr(key) {
2584
- return this.adapter.incr(this.prefixKey(key));
2585
- }
2586
- async decr(key) {
2587
- return this.adapter.decr(this.prefixKey(key));
2588
- }
2589
- async incrBy(key, amount) {
2590
- return this.adapter.incrBy(this.prefixKey(key), amount);
2591
- }
2592
- // ============================================
2593
- // Pub/Sub (with channel prefixing)
2594
- // ============================================
2595
- async publish(channel, message) {
2596
- return this.adapter.publish(this.prefixChannel(channel), message);
2597
- }
2598
- async subscribe(channel, handler) {
2599
- const prefixedChannel = this.prefixChannel(channel);
2600
- const wrappedHandler = (message, ch) => {
2601
- const unprefixedChannel = ch.startsWith(this.prefix) ? ch.slice(this.prefix.length) : ch;
2602
- handler(message, unprefixedChannel);
2603
- };
2604
- return this.adapter.subscribe(prefixedChannel, wrappedHandler);
2605
- }
2606
- supportsPubSub() {
2607
- return this.adapter.supportsPubSub();
2608
- }
2609
- };
2610
- function createRootStorage(adapter) {
2611
- return new NamespacedStorageImpl(adapter, "", adapter);
2612
- }
2613
- function createNamespacedStorage(adapter, prefix) {
2614
- const normalizedPrefix = prefix && !prefix.endsWith(NAMESPACE_SEPARATOR) ? prefix + NAMESPACE_SEPARATOR : prefix;
2615
- return new NamespacedStorageImpl(adapter, normalizedPrefix, adapter);
2616
- }
2617
-
2618
- // libs/utils/src/storage/adapters/memory.ts
2619
- init_base();
2620
- init_errors();
2621
- import { EventEmitter } from "events";
2622
-
2623
- // libs/utils/src/storage/utils/pattern.ts
2624
- init_errors();
2625
- var MAX_PATTERN_LENGTH = 500;
2626
- var MAX_WILDCARDS = 20;
2627
- var REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
2628
- function globToRegex(pattern) {
2629
- if (pattern.length > MAX_PATTERN_LENGTH) {
2630
- throw new StoragePatternError(
2631
- pattern.substring(0, 50) + "...",
2632
- `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
2633
- );
2634
- }
2635
- const wildcardCount = (pattern.match(/[*?]/g) || []).length;
2636
- if (wildcardCount > MAX_WILDCARDS) {
2637
- throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
2638
- }
2639
- if (pattern === "" || pattern === "*") {
2640
- return /^.*$/;
2641
- }
2642
- let regexStr = "^";
2643
- let prevChar = "";
2644
- for (let i = 0; i < pattern.length; i++) {
2645
- const char = pattern[i];
2646
- switch (char) {
2647
- case "*":
2648
- if (prevChar !== "*") {
2649
- regexStr += ".*";
2650
- }
2651
- break;
2652
- case "?":
2653
- regexStr += ".";
2654
- break;
2655
- default:
2656
- regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
2657
- }
2658
- prevChar = char;
2659
- }
2660
- regexStr += "$";
2661
- try {
2662
- return new RegExp(regexStr);
2663
- } catch {
2664
- throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
2665
- }
2666
- }
2667
- function matchesPattern(key, pattern) {
2668
- const regex = globToRegex(pattern);
2669
- return regex.test(key);
2670
- }
2671
- function validatePattern(pattern) {
2672
- try {
2673
- globToRegex(pattern);
2674
- return { valid: true };
2675
- } catch (e) {
2676
- return {
2677
- valid: false,
2678
- error: e instanceof Error ? e.message : "Invalid pattern"
2679
- };
2680
- }
2681
- }
2682
- function escapeGlob(literal) {
2683
- return literal.replace(/[*?\\]/g, "\\$&");
2684
- }
2685
-
2686
- // libs/utils/src/storage/adapters/memory.ts
2687
- init_ttl();
2688
- var DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
2689
- var MAX_TIMEOUT_MS = 2147483647;
2690
- var MemoryStorageAdapter = class extends BaseStorageAdapter {
2691
- backendName = "memory";
2692
- store = /* @__PURE__ */ new Map();
2693
- emitter = new EventEmitter();
2694
- sweepInterval;
2695
- options;
2696
- // LRU tracking (simple linked list via insertion order in Map)
2697
- accessOrder = [];
2698
- constructor(options = {}) {
2699
- super();
2700
- this.options = {
2701
- enableSweeper: options.enableSweeper ?? true,
2702
- sweepIntervalSeconds: options.sweepIntervalSeconds ?? DEFAULT_SWEEP_INTERVAL_SECONDS,
2703
- maxEntries: options.maxEntries ?? 0
2704
- // 0 = unlimited
2705
- };
2706
- this.emitter.setMaxListeners(1e3);
2707
- }
2708
- // ============================================
2709
- // Connection Lifecycle
2710
- // ============================================
2711
- async connect() {
2712
- if (this.connected) return;
2713
- this.connected = true;
2714
- if (this.options.enableSweeper && this.options.sweepIntervalSeconds > 0) {
2715
- this.startSweeper();
2716
- }
2717
- }
2718
- async disconnect() {
2719
- if (!this.connected) return;
2720
- this.stopSweeper();
2721
- this.clearAllTimeouts();
2722
- this.store.clear();
2723
- this.accessOrder = [];
2724
- this.emitter.removeAllListeners();
2725
- this.connected = false;
2726
- }
2727
- async ping() {
2728
- return this.connected;
2729
- }
2730
- // ============================================
2731
- // Core Operations
2826
+ // Core Operations
2732
2827
  // ============================================
2733
2828
  async get(key) {
2734
2829
  this.ensureConnected();
@@ -3008,42 +3103,624 @@ var MemoryStorageAdapter = class extends BaseStorageAdapter {
3008
3103
  }
3009
3104
  };
3010
3105
 
3011
- // libs/utils/src/storage/factory.ts
3012
- function isProduction() {
3013
- return process.env["NODE_ENV"] === "production";
3014
- }
3015
- function detectStorageType() {
3016
- if (process.env["UPSTASH_REDIS_REST_URL"] && process.env["UPSTASH_REDIS_REST_TOKEN"]) {
3017
- return "upstash";
3106
+ // libs/utils/src/storage/adapters/filesystem.ts
3107
+ init_base();
3108
+ init_errors();
3109
+ init_pattern();
3110
+ var FileSystemStorageAdapter = class extends BaseStorageAdapter {
3111
+ backendName = "filesystem";
3112
+ baseDir;
3113
+ extension;
3114
+ dirMode;
3115
+ fileMode;
3116
+ constructor(options) {
3117
+ super();
3118
+ this.baseDir = options.baseDir;
3119
+ this.extension = options.extension ?? ".json";
3120
+ this.dirMode = options.dirMode ?? 448;
3121
+ this.fileMode = options.fileMode ?? 384;
3018
3122
  }
3019
- if (process.env["KV_REST_API_URL"] && process.env["KV_REST_API_TOKEN"]) {
3020
- return "vercel-kv";
3123
+ // ============================================
3124
+ // Connection Lifecycle
3125
+ // ============================================
3126
+ async connect() {
3127
+ if (this.connected) return;
3128
+ await ensureDir(this.baseDir);
3129
+ try {
3130
+ await mkdir(this.baseDir, { recursive: true, mode: this.dirMode });
3131
+ } catch {
3132
+ }
3133
+ this.connected = true;
3021
3134
  }
3022
- if (process.env["REDIS_URL"] || process.env["REDIS_HOST"]) {
3023
- return "redis";
3135
+ async disconnect() {
3136
+ if (!this.connected) return;
3137
+ this.connected = false;
3024
3138
  }
3025
- return "memory";
3026
- }
3027
- async function createAdapter(type, config) {
3028
- switch (type) {
3029
- case "memory": {
3030
- return new MemoryStorageAdapter(config.memory);
3139
+ async ping() {
3140
+ if (!this.connected) return false;
3141
+ try {
3142
+ return await fileExists(this.baseDir);
3143
+ } catch {
3144
+ return false;
3031
3145
  }
3032
- case "redis": {
3033
- const { RedisStorageAdapter: RedisStorageAdapter2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
3034
- return new RedisStorageAdapter2(config.redis);
3146
+ }
3147
+ // ============================================
3148
+ // Core Operations
3149
+ // ============================================
3150
+ async get(key) {
3151
+ this.ensureConnected();
3152
+ const filePath = this.keyToPath(key);
3153
+ try {
3154
+ const entry = await readJSON(filePath);
3155
+ if (!entry) return null;
3156
+ if (entry.expiresAt && Date.now() >= entry.expiresAt) {
3157
+ await this.deleteFile(filePath);
3158
+ return null;
3159
+ }
3160
+ return entry.value;
3161
+ } catch (e) {
3162
+ const error = e;
3163
+ if (error.code === "ENOENT") {
3164
+ return null;
3165
+ }
3166
+ throw e;
3035
3167
  }
3036
- case "vercel-kv": {
3037
- const { VercelKvStorageAdapter: VercelKvStorageAdapter2 } = await Promise.resolve().then(() => (init_vercel_kv(), vercel_kv_exports));
3038
- return new VercelKvStorageAdapter2(config.vercelKv);
3168
+ }
3169
+ async doSet(key, value, options) {
3170
+ const filePath = this.keyToPath(key);
3171
+ const existingEntry = await this.readEntry(filePath);
3172
+ const exists = existingEntry !== null && !this.isExpired(existingEntry);
3173
+ if (options?.ifNotExists && exists) {
3174
+ return;
3039
3175
  }
3040
- case "upstash": {
3041
- const { UpstashStorageAdapter: UpstashStorageAdapter2 } = await Promise.resolve().then(() => (init_upstash(), upstash_exports));
3042
- return new UpstashStorageAdapter2(config.upstash);
3176
+ if (options?.ifExists && !exists) {
3177
+ return;
3043
3178
  }
3044
- case "auto":
3045
- throw new StorageConfigError("auto", "Auto type should be resolved before calling createAdapter");
3046
- default:
3179
+ const entry = { value };
3180
+ if (options?.ttlSeconds) {
3181
+ entry.expiresAt = Date.now() + options.ttlSeconds * 1e3;
3182
+ }
3183
+ await this.atomicWrite(filePath, entry);
3184
+ }
3185
+ async delete(key) {
3186
+ this.ensureConnected();
3187
+ const filePath = this.keyToPath(key);
3188
+ return this.deleteFile(filePath);
3189
+ }
3190
+ async exists(key) {
3191
+ this.ensureConnected();
3192
+ const filePath = this.keyToPath(key);
3193
+ try {
3194
+ const entry = await this.readEntry(filePath);
3195
+ if (!entry) return false;
3196
+ if (this.isExpired(entry)) {
3197
+ await this.deleteFile(filePath);
3198
+ return false;
3199
+ }
3200
+ return true;
3201
+ } catch {
3202
+ return false;
3203
+ }
3204
+ }
3205
+ // ============================================
3206
+ // TTL Operations
3207
+ // ============================================
3208
+ async expire(key, ttlSeconds) {
3209
+ this.ensureConnected();
3210
+ const filePath = this.keyToPath(key);
3211
+ const entry = await this.readEntry(filePath);
3212
+ if (!entry || this.isExpired(entry)) {
3213
+ return false;
3214
+ }
3215
+ entry.expiresAt = Date.now() + ttlSeconds * 1e3;
3216
+ await this.atomicWrite(filePath, entry);
3217
+ return true;
3218
+ }
3219
+ async ttl(key) {
3220
+ this.ensureConnected();
3221
+ const filePath = this.keyToPath(key);
3222
+ const entry = await this.readEntry(filePath);
3223
+ if (!entry) return null;
3224
+ if (this.isExpired(entry)) {
3225
+ await this.deleteFile(filePath);
3226
+ return null;
3227
+ }
3228
+ if (entry.expiresAt === void 0) {
3229
+ return -1;
3230
+ }
3231
+ return Math.max(0, Math.ceil((entry.expiresAt - Date.now()) / 1e3));
3232
+ }
3233
+ // ============================================
3234
+ // Key Enumeration
3235
+ // ============================================
3236
+ async keys(pattern = "*") {
3237
+ this.ensureConnected();
3238
+ const regex = globToRegex(pattern);
3239
+ const result = [];
3240
+ try {
3241
+ const files = await readdir(this.baseDir);
3242
+ for (const file of files) {
3243
+ if (!file.endsWith(this.extension)) continue;
3244
+ const key = this.pathToKey(file);
3245
+ if (!regex.test(key)) continue;
3246
+ const filePath = this.keyToPath(key);
3247
+ const entry = await this.readEntry(filePath);
3248
+ if (entry && !this.isExpired(entry)) {
3249
+ result.push(key);
3250
+ } else if (entry && this.isExpired(entry)) {
3251
+ await this.deleteFile(filePath);
3252
+ }
3253
+ }
3254
+ } catch (e) {
3255
+ const error = e;
3256
+ if (error.code === "ENOENT") {
3257
+ return [];
3258
+ }
3259
+ throw e;
3260
+ }
3261
+ return result;
3262
+ }
3263
+ // ============================================
3264
+ // Atomic Operations
3265
+ // ============================================
3266
+ async incr(key) {
3267
+ return this.incrBy(key, 1);
3268
+ }
3269
+ async decr(key) {
3270
+ return this.incrBy(key, -1);
3271
+ }
3272
+ async incrBy(key, amount) {
3273
+ this.ensureConnected();
3274
+ const filePath = this.keyToPath(key);
3275
+ const entry = await this.readEntry(filePath);
3276
+ let currentValue = 0;
3277
+ if (entry && !this.isExpired(entry)) {
3278
+ const parsed = parseInt(entry.value, 10);
3279
+ if (isNaN(parsed)) {
3280
+ throw new StorageOperationError("incrBy", key, "Value is not an integer");
3281
+ }
3282
+ currentValue = parsed;
3283
+ }
3284
+ const newValue = currentValue + amount;
3285
+ const newEntry = { value: String(newValue) };
3286
+ if (entry?.expiresAt && !this.isExpired(entry)) {
3287
+ newEntry.expiresAt = entry.expiresAt;
3288
+ }
3289
+ await this.atomicWrite(filePath, newEntry);
3290
+ return newValue;
3291
+ }
3292
+ // ============================================
3293
+ // Internal Helpers
3294
+ // ============================================
3295
+ /**
3296
+ * Convert a storage key to a file path.
3297
+ * Sanitizes the key to be filesystem-safe.
3298
+ */
3299
+ keyToPath(key) {
3300
+ const safeKey = this.encodeKey(key);
3301
+ return `${this.baseDir}/${safeKey}${this.extension}`;
3302
+ }
3303
+ /**
3304
+ * Convert a filename back to a storage key.
3305
+ */
3306
+ pathToKey(filename) {
3307
+ const safeKey = filename.slice(0, -this.extension.length);
3308
+ return this.decodeKey(safeKey);
3309
+ }
3310
+ /**
3311
+ * Encode a key to be filesystem-safe.
3312
+ * Uses URL encoding to handle special characters.
3313
+ */
3314
+ encodeKey(key) {
3315
+ return encodeURIComponent(key).replace(/\./g, "%2E");
3316
+ }
3317
+ /**
3318
+ * Decode a filesystem-safe key back to original.
3319
+ */
3320
+ decodeKey(encoded) {
3321
+ return decodeURIComponent(encoded);
3322
+ }
3323
+ /**
3324
+ * Read an entry from a file.
3325
+ */
3326
+ async readEntry(filePath) {
3327
+ try {
3328
+ return await readJSON(filePath);
3329
+ } catch {
3330
+ return null;
3331
+ }
3332
+ }
3333
+ /**
3334
+ * Check if an entry is expired.
3335
+ */
3336
+ isExpired(entry) {
3337
+ return entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt;
3338
+ }
3339
+ /**
3340
+ * Delete a file, returning true if it existed.
3341
+ */
3342
+ async deleteFile(filePath) {
3343
+ try {
3344
+ await unlink(filePath);
3345
+ return true;
3346
+ } catch (e) {
3347
+ const error = e;
3348
+ if (error.code === "ENOENT") {
3349
+ return false;
3350
+ }
3351
+ throw e;
3352
+ }
3353
+ }
3354
+ /**
3355
+ * Atomic write: write to temp file then rename.
3356
+ * This ensures we don't corrupt the file if write is interrupted.
3357
+ */
3358
+ async atomicWrite(filePath, entry) {
3359
+ const tempSuffix = bytesToHex(randomBytes(8));
3360
+ const tempPath = `${filePath}.${tempSuffix}.tmp`;
3361
+ try {
3362
+ const content = JSON.stringify(entry, null, 2);
3363
+ await writeFile(tempPath, content, { mode: this.fileMode });
3364
+ await rename(tempPath, filePath);
3365
+ } catch (e) {
3366
+ try {
3367
+ await unlink(tempPath);
3368
+ } catch {
3369
+ }
3370
+ throw e;
3371
+ }
3372
+ }
3373
+ /**
3374
+ * Get suggestion message for pub/sub not supported error.
3375
+ */
3376
+ getPubSubSuggestion() {
3377
+ return "FileSystem adapter does not support pub/sub. Use Redis or Memory adapter for pub/sub support.";
3378
+ }
3379
+ };
3380
+
3381
+ // libs/utils/src/crypto/key-persistence/factory.ts
3382
+ var DEFAULT_BASE_DIR = ".frontmcp/keys";
3383
+ async function createKeyPersistence(options) {
3384
+ const type = options?.type ?? "auto";
3385
+ const baseDir = options?.baseDir ?? DEFAULT_BASE_DIR;
3386
+ let adapter;
3387
+ if (type === "memory") {
3388
+ adapter = new MemoryStorageAdapter();
3389
+ } else if (type === "filesystem") {
3390
+ adapter = new FileSystemStorageAdapter({ baseDir });
3391
+ } else {
3392
+ if (isNode()) {
3393
+ adapter = new FileSystemStorageAdapter({ baseDir });
3394
+ } else {
3395
+ adapter = new MemoryStorageAdapter();
3396
+ }
3397
+ }
3398
+ await adapter.connect();
3399
+ return new KeyPersistence({
3400
+ storage: adapter,
3401
+ throwOnInvalid: options?.throwOnInvalid ?? false,
3402
+ enableCache: options?.enableCache ?? true
3403
+ });
3404
+ }
3405
+ function createKeyPersistenceWithStorage(storage, options) {
3406
+ return new KeyPersistence({
3407
+ storage,
3408
+ throwOnInvalid: options?.throwOnInvalid ?? false,
3409
+ enableCache: options?.enableCache ?? true
3410
+ });
3411
+ }
3412
+
3413
+ // libs/utils/src/crypto/index.ts
3414
+ var _provider = null;
3415
+ function getCrypto() {
3416
+ if (!_provider) {
3417
+ if (isNode()) {
3418
+ _provider = (init_node(), __toCommonJS(node_exports)).nodeCrypto;
3419
+ } else {
3420
+ _provider = (init_browser(), __toCommonJS(browser_exports)).browserCrypto;
3421
+ }
3422
+ }
3423
+ return _provider;
3424
+ }
3425
+ function rsaVerify2(jwtAlg, data, publicJwk, signature) {
3426
+ assertNode("rsaVerify");
3427
+ return (init_node(), __toCommonJS(node_exports)).rsaVerify(jwtAlg, data, publicJwk, signature);
3428
+ }
3429
+ function randomUUID() {
3430
+ return getCrypto().randomUUID();
3431
+ }
3432
+ function randomBytes(length) {
3433
+ if (!Number.isInteger(length) || length <= 0) {
3434
+ throw new Error(`randomBytes length must be a positive integer, got ${length}`);
3435
+ }
3436
+ return getCrypto().randomBytes(length);
3437
+ }
3438
+ function sha256(data) {
3439
+ return getCrypto().sha256(data);
3440
+ }
3441
+ function sha256Hex(data) {
3442
+ return getCrypto().sha256Hex(data);
3443
+ }
3444
+ function hmacSha256(key, data) {
3445
+ return getCrypto().hmacSha256(key, data);
3446
+ }
3447
+ var HKDF_SHA256_MAX_LENGTH = 255 * 32;
3448
+ function hkdfSha256(ikm, salt, info, length) {
3449
+ if (!Number.isInteger(length) || length <= 0) {
3450
+ throw new Error(`HKDF length must be a positive integer, got ${length}`);
3451
+ }
3452
+ if (length > HKDF_SHA256_MAX_LENGTH) {
3453
+ throw new Error(`HKDF-SHA256 length cannot exceed ${HKDF_SHA256_MAX_LENGTH} bytes, got ${length}`);
3454
+ }
3455
+ return getCrypto().hkdfSha256(ikm, salt, info, length);
3456
+ }
3457
+ function encryptAesGcm(key, plaintext, iv) {
3458
+ if (key.length !== 32) {
3459
+ throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
3460
+ }
3461
+ if (iv.length !== 12) {
3462
+ throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
3463
+ }
3464
+ return getCrypto().encryptAesGcm(key, plaintext, iv);
3465
+ }
3466
+ function decryptAesGcm(key, ciphertext, iv, tag) {
3467
+ if (key.length !== 32) {
3468
+ throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
3469
+ }
3470
+ if (iv.length !== 12) {
3471
+ throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
3472
+ }
3473
+ if (tag.length !== 16) {
3474
+ throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`);
3475
+ }
3476
+ return getCrypto().decryptAesGcm(key, ciphertext, iv, tag);
3477
+ }
3478
+ function timingSafeEqual(a, b) {
3479
+ if (a.length !== b.length) {
3480
+ throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`);
3481
+ }
3482
+ return getCrypto().timingSafeEqual(a, b);
3483
+ }
3484
+ function bytesToHex(data) {
3485
+ return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
3486
+ }
3487
+ function base64urlEncode(data) {
3488
+ let base64;
3489
+ if (typeof Buffer !== "undefined") {
3490
+ base64 = Buffer.from(data).toString("base64");
3491
+ } else {
3492
+ const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
3493
+ base64 = btoa(binString);
3494
+ }
3495
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3496
+ }
3497
+ function base64urlDecode(data) {
3498
+ let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
3499
+ const pad = base64.length % 4;
3500
+ if (pad) {
3501
+ base64 += "=".repeat(4 - pad);
3502
+ }
3503
+ if (typeof Buffer !== "undefined") {
3504
+ return new Uint8Array(Buffer.from(base64, "base64"));
3505
+ } else {
3506
+ const binString = atob(base64);
3507
+ return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
3508
+ }
3509
+ }
3510
+ function base64Encode(data) {
3511
+ if (typeof Buffer !== "undefined") {
3512
+ return Buffer.from(data).toString("base64");
3513
+ } else {
3514
+ const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
3515
+ return btoa(binString);
3516
+ }
3517
+ }
3518
+ function base64Decode(data) {
3519
+ if (typeof Buffer !== "undefined") {
3520
+ return new Uint8Array(Buffer.from(data, "base64"));
3521
+ } else {
3522
+ const binString = atob(data);
3523
+ return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
3524
+ }
3525
+ }
3526
+ function sha256Base64url(data) {
3527
+ return base64urlEncode(sha256(data));
3528
+ }
3529
+
3530
+ // libs/utils/src/storage/factory.ts
3531
+ init_errors();
3532
+
3533
+ // libs/utils/src/storage/namespace.ts
3534
+ var NAMESPACE_SEPARATOR = ":";
3535
+ function buildPrefix(name, id) {
3536
+ if (id) {
3537
+ return `${name}${NAMESPACE_SEPARATOR}${id}${NAMESPACE_SEPARATOR}`;
3538
+ }
3539
+ return `${name}${NAMESPACE_SEPARATOR}`;
3540
+ }
3541
+ var NamespacedStorageImpl = class _NamespacedStorageImpl {
3542
+ constructor(adapter, prefix = "", root = adapter) {
3543
+ this.adapter = adapter;
3544
+ this.prefix = prefix;
3545
+ this.root = root;
3546
+ }
3547
+ // ============================================
3548
+ // Key Prefixing Helpers
3549
+ // ============================================
3550
+ /**
3551
+ * Add prefix to a key.
3552
+ */
3553
+ prefixKey(key) {
3554
+ return this.prefix + key;
3555
+ }
3556
+ /**
3557
+ * Remove prefix from a key.
3558
+ */
3559
+ unprefixKey(key) {
3560
+ if (this.prefix && key.startsWith(this.prefix)) {
3561
+ return key.slice(this.prefix.length);
3562
+ }
3563
+ return key;
3564
+ }
3565
+ /**
3566
+ * Add prefix to a pattern (for keys() operation).
3567
+ */
3568
+ prefixPattern(pattern) {
3569
+ return this.prefix + pattern;
3570
+ }
3571
+ /**
3572
+ * Add prefix to a channel (for pub/sub).
3573
+ */
3574
+ prefixChannel(channel) {
3575
+ return this.prefix + channel;
3576
+ }
3577
+ // ============================================
3578
+ // Namespace API
3579
+ // ============================================
3580
+ namespace(name, id) {
3581
+ const newPrefix = this.prefix + buildPrefix(name, id);
3582
+ return new _NamespacedStorageImpl(this.adapter, newPrefix, this.root);
3583
+ }
3584
+ // ============================================
3585
+ // Connection Lifecycle (delegated to root)
3586
+ // ============================================
3587
+ async connect() {
3588
+ return this.adapter.connect();
3589
+ }
3590
+ async disconnect() {
3591
+ return this.adapter.disconnect();
3592
+ }
3593
+ async ping() {
3594
+ return this.adapter.ping();
3595
+ }
3596
+ // ============================================
3597
+ // Core Operations (with prefixing)
3598
+ // ============================================
3599
+ async get(key) {
3600
+ return this.adapter.get(this.prefixKey(key));
3601
+ }
3602
+ async set(key, value, options) {
3603
+ return this.adapter.set(this.prefixKey(key), value, options);
3604
+ }
3605
+ async delete(key) {
3606
+ return this.adapter.delete(this.prefixKey(key));
3607
+ }
3608
+ async exists(key) {
3609
+ return this.adapter.exists(this.prefixKey(key));
3610
+ }
3611
+ // ============================================
3612
+ // Batch Operations (with prefixing)
3613
+ // ============================================
3614
+ async mget(keys) {
3615
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
3616
+ return this.adapter.mget(prefixedKeys);
3617
+ }
3618
+ async mset(entries) {
3619
+ const prefixedEntries = entries.map((e) => ({
3620
+ ...e,
3621
+ key: this.prefixKey(e.key)
3622
+ }));
3623
+ return this.adapter.mset(prefixedEntries);
3624
+ }
3625
+ async mdelete(keys) {
3626
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
3627
+ return this.adapter.mdelete(prefixedKeys);
3628
+ }
3629
+ // ============================================
3630
+ // TTL Operations (with prefixing)
3631
+ // ============================================
3632
+ async expire(key, ttlSeconds) {
3633
+ return this.adapter.expire(this.prefixKey(key), ttlSeconds);
3634
+ }
3635
+ async ttl(key) {
3636
+ return this.adapter.ttl(this.prefixKey(key));
3637
+ }
3638
+ // ============================================
3639
+ // Key Enumeration (with prefixing)
3640
+ // ============================================
3641
+ async keys(pattern = "*") {
3642
+ const prefixedPattern = this.prefixPattern(pattern);
3643
+ const keys = await this.adapter.keys(prefixedPattern);
3644
+ return keys.map((k) => this.unprefixKey(k));
3645
+ }
3646
+ async count(pattern = "*") {
3647
+ const prefixedPattern = this.prefixPattern(pattern);
3648
+ return this.adapter.count(prefixedPattern);
3649
+ }
3650
+ // ============================================
3651
+ // Atomic Operations (with prefixing)
3652
+ // ============================================
3653
+ async incr(key) {
3654
+ return this.adapter.incr(this.prefixKey(key));
3655
+ }
3656
+ async decr(key) {
3657
+ return this.adapter.decr(this.prefixKey(key));
3658
+ }
3659
+ async incrBy(key, amount) {
3660
+ return this.adapter.incrBy(this.prefixKey(key), amount);
3661
+ }
3662
+ // ============================================
3663
+ // Pub/Sub (with channel prefixing)
3664
+ // ============================================
3665
+ async publish(channel, message) {
3666
+ return this.adapter.publish(this.prefixChannel(channel), message);
3667
+ }
3668
+ async subscribe(channel, handler) {
3669
+ const prefixedChannel = this.prefixChannel(channel);
3670
+ const wrappedHandler = (message, ch) => {
3671
+ const unprefixedChannel = ch.startsWith(this.prefix) ? ch.slice(this.prefix.length) : ch;
3672
+ handler(message, unprefixedChannel);
3673
+ };
3674
+ return this.adapter.subscribe(prefixedChannel, wrappedHandler);
3675
+ }
3676
+ supportsPubSub() {
3677
+ return this.adapter.supportsPubSub();
3678
+ }
3679
+ };
3680
+ function createRootStorage(adapter) {
3681
+ return new NamespacedStorageImpl(adapter, "", adapter);
3682
+ }
3683
+ function createNamespacedStorage(adapter, prefix) {
3684
+ const normalizedPrefix = prefix && !prefix.endsWith(NAMESPACE_SEPARATOR) ? prefix + NAMESPACE_SEPARATOR : prefix;
3685
+ return new NamespacedStorageImpl(adapter, normalizedPrefix, adapter);
3686
+ }
3687
+
3688
+ // libs/utils/src/storage/factory.ts
3689
+ function isProduction() {
3690
+ return process.env["NODE_ENV"] === "production";
3691
+ }
3692
+ function detectStorageType() {
3693
+ if (process.env["UPSTASH_REDIS_REST_URL"] && process.env["UPSTASH_REDIS_REST_TOKEN"]) {
3694
+ return "upstash";
3695
+ }
3696
+ if (process.env["KV_REST_API_URL"] && process.env["KV_REST_API_TOKEN"]) {
3697
+ return "vercel-kv";
3698
+ }
3699
+ if (process.env["REDIS_URL"] || process.env["REDIS_HOST"]) {
3700
+ return "redis";
3701
+ }
3702
+ return "memory";
3703
+ }
3704
+ async function createAdapter(type, config) {
3705
+ switch (type) {
3706
+ case "memory": {
3707
+ return new MemoryStorageAdapter(config.memory);
3708
+ }
3709
+ case "redis": {
3710
+ const { RedisStorageAdapter: RedisStorageAdapter2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
3711
+ return new RedisStorageAdapter2(config.redis);
3712
+ }
3713
+ case "vercel-kv": {
3714
+ const { VercelKvStorageAdapter: VercelKvStorageAdapter2 } = await Promise.resolve().then(() => (init_vercel_kv(), vercel_kv_exports));
3715
+ return new VercelKvStorageAdapter2(config.vercelKv);
3716
+ }
3717
+ case "upstash": {
3718
+ const { UpstashStorageAdapter: UpstashStorageAdapter2 } = await Promise.resolve().then(() => (init_upstash(), upstash_exports));
3719
+ return new UpstashStorageAdapter2(config.upstash);
3720
+ }
3721
+ case "auto":
3722
+ throw new StorageConfigError("auto", "Auto type should be resolved before calling createAdapter");
3723
+ default:
3047
3724
  throw new StorageConfigError("unknown", `Unknown storage type: ${type}`);
3048
3725
  }
3049
3726
  }
@@ -3103,6 +3780,501 @@ function getDetectedStorageType() {
3103
3780
  // libs/utils/src/storage/index.ts
3104
3781
  init_errors();
3105
3782
 
3783
+ // libs/utils/src/storage/typed-storage.ts
3784
+ var TypedStorage = class {
3785
+ storage;
3786
+ serialize;
3787
+ deserialize;
3788
+ schema;
3789
+ throwOnInvalid;
3790
+ constructor(storage, options = {}) {
3791
+ this.storage = storage;
3792
+ this.serialize = options.serialize ?? JSON.stringify;
3793
+ this.deserialize = options.deserialize ?? JSON.parse;
3794
+ this.schema = options.schema;
3795
+ this.throwOnInvalid = options.throwOnInvalid ?? false;
3796
+ }
3797
+ /**
3798
+ * Get a typed value by key.
3799
+ *
3800
+ * @param key - Storage key
3801
+ * @returns The typed value, or null if not found or invalid
3802
+ */
3803
+ async get(key) {
3804
+ const raw = await this.storage.get(key);
3805
+ if (raw === null) {
3806
+ return null;
3807
+ }
3808
+ return this.parseValue(raw, key);
3809
+ }
3810
+ /**
3811
+ * Set a typed value with optional TTL.
3812
+ *
3813
+ * @param key - Storage key
3814
+ * @param value - Typed value to store
3815
+ * @param options - Optional TTL and conditional flags
3816
+ */
3817
+ async set(key, value, options) {
3818
+ const serialized = this.serialize(value);
3819
+ await this.storage.set(key, serialized, options);
3820
+ }
3821
+ /**
3822
+ * Delete a key.
3823
+ *
3824
+ * @param key - Storage key
3825
+ * @returns true if key existed and was deleted
3826
+ */
3827
+ async delete(key) {
3828
+ return this.storage.delete(key);
3829
+ }
3830
+ /**
3831
+ * Check if a key exists.
3832
+ *
3833
+ * @param key - Storage key
3834
+ * @returns true if key exists
3835
+ */
3836
+ async exists(key) {
3837
+ return this.storage.exists(key);
3838
+ }
3839
+ /**
3840
+ * Get multiple typed values.
3841
+ *
3842
+ * @param keys - Array of storage keys
3843
+ * @returns Array of typed values (null for missing/invalid keys)
3844
+ */
3845
+ async mget(keys) {
3846
+ if (keys.length === 0) {
3847
+ return [];
3848
+ }
3849
+ const rawValues = await this.storage.mget(keys);
3850
+ return rawValues.map((raw, index) => {
3851
+ if (raw === null) {
3852
+ return null;
3853
+ }
3854
+ return this.parseValue(raw, keys[index]);
3855
+ });
3856
+ }
3857
+ /**
3858
+ * Set multiple typed values.
3859
+ *
3860
+ * @param entries - Array of key-value-options entries
3861
+ */
3862
+ async mset(entries) {
3863
+ if (entries.length === 0) {
3864
+ return;
3865
+ }
3866
+ const rawEntries = entries.map((entry) => ({
3867
+ key: entry.key,
3868
+ value: this.serialize(entry.value),
3869
+ options: entry.options
3870
+ }));
3871
+ await this.storage.mset(rawEntries);
3872
+ }
3873
+ /**
3874
+ * Delete multiple keys.
3875
+ *
3876
+ * @param keys - Array of storage keys
3877
+ * @returns Number of keys actually deleted
3878
+ */
3879
+ async mdelete(keys) {
3880
+ return this.storage.mdelete(keys);
3881
+ }
3882
+ /**
3883
+ * Update TTL on an existing key.
3884
+ *
3885
+ * @param key - Storage key
3886
+ * @param ttlSeconds - New TTL in seconds
3887
+ * @returns true if key exists and TTL was set
3888
+ */
3889
+ async expire(key, ttlSeconds) {
3890
+ return this.storage.expire(key, ttlSeconds);
3891
+ }
3892
+ /**
3893
+ * Get remaining TTL for a key.
3894
+ *
3895
+ * @param key - Storage key
3896
+ * @returns TTL in seconds, -1 if no TTL, or null if key doesn't exist
3897
+ */
3898
+ async ttl(key) {
3899
+ return this.storage.ttl(key);
3900
+ }
3901
+ /**
3902
+ * List keys matching a pattern.
3903
+ *
3904
+ * @param pattern - Glob pattern (default: '*' for all keys)
3905
+ * @returns Array of matching keys
3906
+ */
3907
+ async keys(pattern) {
3908
+ return this.storage.keys(pattern);
3909
+ }
3910
+ /**
3911
+ * Count keys matching a pattern.
3912
+ *
3913
+ * @param pattern - Glob pattern (default: '*' for all keys)
3914
+ * @returns Number of matching keys
3915
+ */
3916
+ async count(pattern) {
3917
+ return this.storage.count(pattern);
3918
+ }
3919
+ /**
3920
+ * Get the underlying storage adapter.
3921
+ * Use with caution - operations bypass type safety.
3922
+ */
3923
+ get raw() {
3924
+ return this.storage;
3925
+ }
3926
+ /**
3927
+ * Parse and validate a raw value.
3928
+ */
3929
+ parseValue(raw, key) {
3930
+ try {
3931
+ const parsed = this.deserialize(raw);
3932
+ if (this.schema) {
3933
+ const result = this.schema.safeParse(parsed);
3934
+ if (!result.success) {
3935
+ if (this.throwOnInvalid) {
3936
+ throw new Error(`TypedStorage validation failed for key "${key}": ${result.error.message}`);
3937
+ }
3938
+ return null;
3939
+ }
3940
+ return result.data;
3941
+ }
3942
+ return parsed;
3943
+ } catch (error) {
3944
+ if (this.throwOnInvalid) {
3945
+ throw error;
3946
+ }
3947
+ return null;
3948
+ }
3949
+ }
3950
+ };
3951
+
3952
+ // libs/utils/src/storage/encrypted-typed-storage.ts
3953
+ init_errors();
3954
+ var textEncoder2 = new TextEncoder();
3955
+ var textDecoder2 = new TextDecoder();
3956
+ var EncryptedTypedStorage = class {
3957
+ storage;
3958
+ activeKey;
3959
+ keyMap;
3960
+ schema;
3961
+ throwOnError;
3962
+ onKeyRotationNeeded;
3963
+ clientBinding;
3964
+ constructor(storage, options) {
3965
+ if (!options.keys || options.keys.length === 0) {
3966
+ throw new EncryptedStorageError("At least one encryption key is required");
3967
+ }
3968
+ for (const k of options.keys) {
3969
+ if (k.key.length !== 32) {
3970
+ throw new EncryptedStorageError(
3971
+ `Encryption key "${k.kid}" must be 32 bytes (AES-256), got ${k.key.length} bytes`
3972
+ );
3973
+ }
3974
+ }
3975
+ this.storage = storage;
3976
+ this.activeKey = options.keys[0];
3977
+ this.keyMap = new Map(options.keys.map((k) => [k.kid, k.key]));
3978
+ this.schema = options.schema;
3979
+ this.throwOnError = options.throwOnError ?? false;
3980
+ this.onKeyRotationNeeded = options.onKeyRotationNeeded;
3981
+ this.clientBinding = options.clientBinding;
3982
+ }
3983
+ /**
3984
+ * Get a decrypted value by key.
3985
+ *
3986
+ * @param key - Storage key
3987
+ * @returns The decrypted value, or null if not found or decryption fails
3988
+ */
3989
+ async get(key) {
3990
+ const raw = await this.storage.get(key);
3991
+ if (raw === null) {
3992
+ return null;
3993
+ }
3994
+ return this.decryptAndParse(raw, key);
3995
+ }
3996
+ /**
3997
+ * Encrypt and store a value.
3998
+ *
3999
+ * @param key - Storage key
4000
+ * @param value - Value to encrypt and store
4001
+ * @param options - Optional TTL and conditional flags
4002
+ */
4003
+ async set(key, value, options) {
4004
+ const encrypted = this.encryptValue(value);
4005
+ const serialized = JSON.stringify(encrypted);
4006
+ await this.storage.set(key, serialized, options);
4007
+ }
4008
+ /**
4009
+ * Delete a key.
4010
+ *
4011
+ * @param key - Storage key
4012
+ * @returns true if key existed and was deleted
4013
+ */
4014
+ async delete(key) {
4015
+ return this.storage.delete(key);
4016
+ }
4017
+ /**
4018
+ * Check if a key exists.
4019
+ *
4020
+ * @param key - Storage key
4021
+ * @returns true if key exists
4022
+ */
4023
+ async exists(key) {
4024
+ return this.storage.exists(key);
4025
+ }
4026
+ /**
4027
+ * Get multiple decrypted values.
4028
+ *
4029
+ * @param keys - Array of storage keys
4030
+ * @returns Array of decrypted values (null for missing/invalid keys)
4031
+ */
4032
+ async mget(keys) {
4033
+ if (keys.length === 0) {
4034
+ return [];
4035
+ }
4036
+ const rawValues = await this.storage.mget(keys);
4037
+ return rawValues.map((raw, index) => {
4038
+ if (raw === null) {
4039
+ return null;
4040
+ }
4041
+ return this.decryptAndParse(raw, keys[index]);
4042
+ });
4043
+ }
4044
+ /**
4045
+ * Encrypt and store multiple values.
4046
+ *
4047
+ * @param entries - Array of key-value-options entries
4048
+ */
4049
+ async mset(entries) {
4050
+ if (entries.length === 0) {
4051
+ return;
4052
+ }
4053
+ const rawEntries = entries.map((entry) => ({
4054
+ key: entry.key,
4055
+ value: JSON.stringify(this.encryptValue(entry.value)),
4056
+ options: entry.options
4057
+ }));
4058
+ await this.storage.mset(rawEntries);
4059
+ }
4060
+ /**
4061
+ * Delete multiple keys.
4062
+ *
4063
+ * @param keys - Array of storage keys
4064
+ * @returns Number of keys actually deleted
4065
+ */
4066
+ async mdelete(keys) {
4067
+ return this.storage.mdelete(keys);
4068
+ }
4069
+ /**
4070
+ * Update TTL on an existing key.
4071
+ *
4072
+ * @param key - Storage key
4073
+ * @param ttlSeconds - New TTL in seconds
4074
+ * @returns true if key exists and TTL was set
4075
+ */
4076
+ async expire(key, ttlSeconds) {
4077
+ return this.storage.expire(key, ttlSeconds);
4078
+ }
4079
+ /**
4080
+ * Get remaining TTL for a key.
4081
+ *
4082
+ * @param key - Storage key
4083
+ * @returns TTL in seconds, -1 if no TTL, or null if key doesn't exist
4084
+ */
4085
+ async ttl(key) {
4086
+ return this.storage.ttl(key);
4087
+ }
4088
+ /**
4089
+ * List keys matching a pattern.
4090
+ *
4091
+ * @param pattern - Glob pattern (default: '*' for all keys)
4092
+ * @returns Array of matching keys
4093
+ */
4094
+ async keys(pattern) {
4095
+ return this.storage.keys(pattern);
4096
+ }
4097
+ /**
4098
+ * Count keys matching a pattern.
4099
+ *
4100
+ * @param pattern - Glob pattern (default: '*' for all keys)
4101
+ * @returns Number of matching keys
4102
+ */
4103
+ async count(pattern) {
4104
+ return this.storage.count(pattern);
4105
+ }
4106
+ /**
4107
+ * Re-encrypt a value with the active key.
4108
+ * Useful for key rotation - reads with old key, writes with new key.
4109
+ *
4110
+ * @param key - Storage key to re-encrypt
4111
+ * @param options - Optional TTL for the re-encrypted value
4112
+ * @returns true if value was re-encrypted, false if not found
4113
+ */
4114
+ async reencrypt(key, options) {
4115
+ const value = await this.get(key);
4116
+ if (value === null) {
4117
+ return false;
4118
+ }
4119
+ await this.set(key, value, options);
4120
+ return true;
4121
+ }
4122
+ /**
4123
+ * Rotate the active encryption key.
4124
+ * New encryptions will use the new key; old keys remain for decryption.
4125
+ *
4126
+ * @param newKey - New encryption key to use for writes
4127
+ */
4128
+ rotateKey(newKey) {
4129
+ if (newKey.key.length !== 32) {
4130
+ throw new EncryptedStorageError(`New encryption key must be 32 bytes (AES-256), got ${newKey.key.length} bytes`);
4131
+ }
4132
+ if (!this.keyMap.has(newKey.kid)) {
4133
+ this.keyMap.set(newKey.kid, newKey.key);
4134
+ }
4135
+ this.activeKey = newKey;
4136
+ }
4137
+ /**
4138
+ * Get the active key ID.
4139
+ */
4140
+ get activeKeyId() {
4141
+ return this.activeKey.kid;
4142
+ }
4143
+ /**
4144
+ * Get all known key IDs.
4145
+ */
4146
+ get keyIds() {
4147
+ return Array.from(this.keyMap.keys());
4148
+ }
4149
+ /**
4150
+ * Get the underlying storage adapter.
4151
+ * Use with caution - operations bypass encryption.
4152
+ */
4153
+ get raw() {
4154
+ return this.storage;
4155
+ }
4156
+ /**
4157
+ * Check if client-side key binding is enabled.
4158
+ * When true, encryption keys are derived from both server key and client secret.
4159
+ */
4160
+ get hasClientBinding() {
4161
+ return this.clientBinding !== void 0;
4162
+ }
4163
+ /**
4164
+ * Derive the actual encryption key.
4165
+ *
4166
+ * If clientBinding is configured, combines server key with client secret
4167
+ * using HKDF-SHA256 (RFC 5869) to produce the actual encryption key.
4168
+ * Otherwise, returns the server key as-is.
4169
+ *
4170
+ * This provides zero-knowledge encryption where:
4171
+ * - Server cannot decrypt without client secret (sessionId)
4172
+ * - Client cannot decrypt without server key
4173
+ * - Key derivation is deterministic (same inputs -> same derived key)
4174
+ *
4175
+ * @param serverKey - The server-side encryption key
4176
+ * @returns The key to use for actual encryption/decryption
4177
+ */
4178
+ deriveKey(serverKey) {
4179
+ if (!this.clientBinding) {
4180
+ return serverKey;
4181
+ }
4182
+ const clientSecret = typeof this.clientBinding.secret === "string" ? textEncoder2.encode(this.clientBinding.secret) : this.clientBinding.secret;
4183
+ const ikm = new Uint8Array(serverKey.length + clientSecret.length);
4184
+ ikm.set(serverKey, 0);
4185
+ ikm.set(clientSecret, serverKey.length);
4186
+ const salt = this.clientBinding.salt ?? new Uint8Array(0);
4187
+ const info = textEncoder2.encode(this.clientBinding.info ?? "frontmcp-client-bound-v1");
4188
+ return hkdfSha256(ikm, salt, info, 32);
4189
+ }
4190
+ /**
4191
+ * Encrypt a value using the active key.
4192
+ * If client binding is configured, uses derived key from server key + client secret.
4193
+ */
4194
+ encryptValue(value) {
4195
+ let jsonString;
4196
+ try {
4197
+ jsonString = JSON.stringify(value);
4198
+ } catch (error) {
4199
+ throw new EncryptedStorageError(`Failed to serialize value: ${error.message}`);
4200
+ }
4201
+ const plaintext = textEncoder2.encode(jsonString);
4202
+ const iv = randomBytes(12);
4203
+ const encryptionKey = this.deriveKey(this.activeKey.key);
4204
+ const { ciphertext, tag } = encryptAesGcm(encryptionKey, plaintext, iv);
4205
+ return {
4206
+ alg: "A256GCM",
4207
+ kid: this.activeKey.kid,
4208
+ iv: base64urlEncode(iv),
4209
+ tag: base64urlEncode(tag),
4210
+ data: base64urlEncode(ciphertext)
4211
+ };
4212
+ }
4213
+ /**
4214
+ * Decrypt and parse a stored value.
4215
+ */
4216
+ decryptAndParse(raw, storageKey) {
4217
+ let blob;
4218
+ try {
4219
+ blob = JSON.parse(raw);
4220
+ } catch (_error) {
4221
+ if (this.throwOnError) {
4222
+ throw new EncryptedStorageError(`Failed to parse stored blob for key "${storageKey}"`);
4223
+ }
4224
+ return null;
4225
+ }
4226
+ if (!this.isValidBlob(blob)) {
4227
+ if (this.throwOnError) {
4228
+ throw new EncryptedStorageError(`Invalid encrypted blob structure for key "${storageKey}"`);
4229
+ }
4230
+ return null;
4231
+ }
4232
+ const serverKey = this.keyMap.get(blob.kid);
4233
+ if (!serverKey) {
4234
+ if (this.throwOnError) {
4235
+ throw new EncryptedStorageError(
4236
+ `Unknown encryption key "${blob.kid}" for key "${storageKey}". Known keys: ${Array.from(this.keyMap.keys()).join(", ")}`
4237
+ );
4238
+ }
4239
+ return null;
4240
+ }
4241
+ const decryptionKey = this.deriveKey(serverKey);
4242
+ let decrypted;
4243
+ try {
4244
+ const iv = base64urlDecode(blob.iv);
4245
+ const tag = base64urlDecode(blob.tag);
4246
+ const ciphertext = base64urlDecode(blob.data);
4247
+ const plaintext = decryptAesGcm(decryptionKey, ciphertext, iv, tag);
4248
+ decrypted = JSON.parse(textDecoder2.decode(plaintext));
4249
+ } catch (error) {
4250
+ if (this.throwOnError) {
4251
+ throw new EncryptedStorageError(`Decryption failed for key "${storageKey}": ${error.message}`);
4252
+ }
4253
+ return null;
4254
+ }
4255
+ if (this.schema) {
4256
+ const result = this.schema.safeParse(decrypted);
4257
+ if (!result.success) {
4258
+ if (this.throwOnError) {
4259
+ throw new EncryptedStorageError(`Schema validation failed for key "${storageKey}": ${result.error.message}`);
4260
+ }
4261
+ return null;
4262
+ }
4263
+ decrypted = result.data;
4264
+ }
4265
+ if (blob.kid !== this.activeKey.kid && this.onKeyRotationNeeded) {
4266
+ this.onKeyRotationNeeded(storageKey, blob.kid, this.activeKey.kid);
4267
+ }
4268
+ return decrypted;
4269
+ }
4270
+ /**
4271
+ * Validate blob structure.
4272
+ */
4273
+ isValidBlob(blob) {
4274
+ return typeof blob === "object" && blob !== null && "alg" in blob && "kid" in blob && "iv" in blob && "tag" in blob && "data" in blob && blob.alg === "A256GCM" && typeof blob.kid === "string" && typeof blob.iv === "string" && typeof blob.tag === "string" && typeof blob.data === "string";
4275
+ }
4276
+ };
4277
+
3106
4278
  // libs/utils/src/storage/adapters/index.ts
3107
4279
  init_base();
3108
4280
  init_redis();
@@ -3110,12 +4282,17 @@ init_vercel_kv();
3110
4282
  init_upstash();
3111
4283
 
3112
4284
  // libs/utils/src/storage/index.ts
4285
+ init_pattern();
3113
4286
  init_ttl();
3114
4287
  export {
3115
4288
  BaseStorageAdapter,
3116
4289
  DEFAULT_CODE_VERIFIER_LENGTH,
3117
4290
  DEFAULT_MAX_INPUT_LENGTH,
3118
4291
  EncryptedBlobError,
4292
+ EncryptedStorageError,
4293
+ EncryptedTypedStorage,
4294
+ FileSystemStorageAdapter,
4295
+ KeyPersistence,
3119
4296
  MAX_CODE_VERIFIER_LENGTH,
3120
4297
  MAX_TTL_SECONDS,
3121
4298
  MIN_CODE_VERIFIER_LENGTH,
@@ -3133,11 +4310,17 @@ export {
3133
4310
  StorageOperationError,
3134
4311
  StoragePatternError,
3135
4312
  StorageTTLError,
4313
+ TypedStorage,
3136
4314
  UpstashStorageAdapter,
3137
4315
  VercelKvStorageAdapter,
3138
4316
  access,
3139
4317
  analyzePattern,
4318
+ anyKeyDataSchema,
3140
4319
  assertNode,
4320
+ asymmetricAlgSchema,
4321
+ asymmetricKeyDataSchema,
4322
+ base64Decode,
4323
+ base64Encode,
3141
4324
  base64urlDecode,
3142
4325
  base64urlEncode,
3143
4326
  buildPrefix,
@@ -3147,6 +4330,8 @@ export {
3147
4330
  collapseWhitespace,
3148
4331
  copyFile,
3149
4332
  cp,
4333
+ createKeyPersistence,
4334
+ createKeyPersistenceWithStorage,
3150
4335
  createMemoryStorage,
3151
4336
  createNamespacedStorage,
3152
4337
  createRootStorage,
@@ -3188,6 +4373,7 @@ export {
3188
4373
  hmacSha256,
3189
4374
  idFromString,
3190
4375
  inferMimeType,
4376
+ isAsymmetricKeyData,
3191
4377
  isBrowser,
3192
4378
  isDirEmpty,
3193
4379
  isExpired,
@@ -3196,7 +4382,9 @@ export {
3196
4382
  isPatternSafe,
3197
4383
  isRsaPssAlg,
3198
4384
  isSecretCached,
4385
+ isSecretKeyData,
3199
4386
  isSecretPersistenceEnabled,
4387
+ isSignedData,
3200
4388
  isUriTemplate,
3201
4389
  isValidCodeChallenge,
3202
4390
  isValidCodeVerifier,
@@ -3211,12 +4399,14 @@ export {
3211
4399
  mkdir,
3212
4400
  mkdtemp,
3213
4401
  normalizeTTL,
4402
+ parseKeyData,
3214
4403
  parseSecretData,
3215
4404
  parseUriTemplate,
3216
4405
  randomBytes,
3217
4406
  randomUUID,
3218
4407
  readFile,
3219
4408
  readFileBuffer,
4409
+ readFileSync,
3220
4410
  readJSON,
3221
4411
  readdir,
3222
4412
  rename,
@@ -3233,12 +4423,14 @@ export {
3233
4423
  sanitizeToJson,
3234
4424
  saveSecret,
3235
4425
  secretDataSchema,
4426
+ secretKeyDataSchema,
3236
4427
  sepFor,
3237
4428
  serializeBlob,
3238
4429
  sha256,
3239
4430
  sha256Base64url,
3240
4431
  sha256Hex,
3241
4432
  shortHash,
4433
+ signData,
3242
4434
  splitWords,
3243
4435
  stat,
3244
4436
  timingSafeEqual,
@@ -3254,11 +4446,14 @@ export {
3254
4446
  ttlToExpiresAt,
3255
4447
  unlink,
3256
4448
  validateBaseUrl,
4449
+ validateKeyData,
3257
4450
  validateOptionalTTL,
3258
4451
  validatePattern,
3259
4452
  validateSecretData,
3260
4453
  validateTTL,
3261
4454
  verifyCodeChallenge,
4455
+ verifyData,
4456
+ verifyOrParseData,
3262
4457
  writeFile,
3263
4458
  writeJSON
3264
4459
  };