@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/index.js CHANGED
@@ -57,219 +57,8 @@ var init_jwt_alg = __esm({
57
57
  }
58
58
  });
59
59
 
60
- // libs/utils/src/crypto/node.ts
61
- var node_exports = {};
62
- __export(node_exports, {
63
- createSignedJwt: () => createSignedJwt,
64
- generateRsaKeyPair: () => generateRsaKeyPair,
65
- isRsaPssAlg: () => isRsaPssAlg,
66
- jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
67
- nodeCrypto: () => nodeCrypto,
68
- rsaSign: () => rsaSign,
69
- rsaVerify: () => rsaVerify
70
- });
71
- function toUint8Array(buf) {
72
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
73
- }
74
- function toBuffer(data) {
75
- if (typeof data === "string") {
76
- return Buffer.from(data, "utf8");
77
- }
78
- return Buffer.from(data);
79
- }
80
- function generateRsaKeyPair(modulusLength = 2048, alg = "RS256") {
81
- const kid = `rsa-key-${Date.now()}-${import_node_crypto.default.randomBytes(8).toString("hex")}`;
82
- const { privateKey, publicKey } = import_node_crypto.default.generateKeyPairSync("rsa", {
83
- modulusLength
84
- });
85
- const exported = publicKey.export({ format: "jwk" });
86
- const publicJwk = {
87
- ...exported,
88
- kid,
89
- alg,
90
- use: "sig",
91
- kty: "RSA"
92
- };
93
- return { privateKey, publicKey, publicJwk };
94
- }
95
- function rsaSign(algorithm, data, privateKey, options) {
96
- const signingKey = options ? { key: privateKey, ...options } : privateKey;
97
- return import_node_crypto.default.sign(algorithm, data, signingKey);
98
- }
99
- function rsaVerify(jwtAlg, data, publicJwk, signature) {
100
- const publicKey = import_node_crypto.default.createPublicKey({ key: publicJwk, format: "jwk" });
101
- const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
102
- const verifyKey = isRsaPssAlg(jwtAlg) ? {
103
- key: publicKey,
104
- padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
105
- saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
106
- } : publicKey;
107
- return import_node_crypto.default.verify(nodeAlgorithm, data, verifyKey, signature);
108
- }
109
- function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
110
- const header = { alg, typ: "JWT", kid };
111
- const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
112
- const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
113
- const signatureInput = `${headerB64}.${payloadB64}`;
114
- const nodeAlgorithm = jwtAlgToNodeAlg(alg);
115
- const signature = rsaSign(
116
- nodeAlgorithm,
117
- Buffer.from(signatureInput),
118
- privateKey,
119
- isRsaPssAlg(alg) ? {
120
- padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
121
- saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
122
- } : void 0
123
- );
124
- const signatureB64 = signature.toString("base64url");
125
- return `${headerB64}.${payloadB64}.${signatureB64}`;
126
- }
127
- var import_node_crypto, nodeCrypto;
128
- var init_node = __esm({
129
- "libs/utils/src/crypto/node.ts"() {
130
- "use strict";
131
- import_node_crypto = __toESM(require("node:crypto"));
132
- init_jwt_alg();
133
- init_jwt_alg();
134
- nodeCrypto = {
135
- randomUUID() {
136
- return import_node_crypto.default.randomUUID();
137
- },
138
- randomBytes(length) {
139
- return toUint8Array(import_node_crypto.default.randomBytes(length));
140
- },
141
- sha256(data) {
142
- const hash = import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest();
143
- return toUint8Array(hash);
144
- },
145
- sha256Hex(data) {
146
- return import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest("hex");
147
- },
148
- hmacSha256(key, data) {
149
- const hmac2 = import_node_crypto.default.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
150
- return toUint8Array(hmac2);
151
- },
152
- hkdfSha256(ikm, salt, info, length) {
153
- const ikmBuf = Buffer.from(ikm);
154
- const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
155
- const prk = import_node_crypto.default.createHmac("sha256", saltBuf).update(ikmBuf).digest();
156
- const hashLen = 32;
157
- const n = Math.ceil(length / hashLen);
158
- const chunks = [];
159
- let prev = Buffer.alloc(0);
160
- for (let i = 1; i <= n; i++) {
161
- prev = import_node_crypto.default.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
162
- chunks.push(prev);
163
- }
164
- return toUint8Array(Buffer.concat(chunks).subarray(0, length));
165
- },
166
- encryptAesGcm(key, plaintext, iv) {
167
- const cipher = import_node_crypto.default.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
168
- const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
169
- const tag = cipher.getAuthTag();
170
- return {
171
- ciphertext: toUint8Array(encrypted),
172
- tag: toUint8Array(tag)
173
- };
174
- },
175
- decryptAesGcm(key, ciphertext, iv, tag) {
176
- const decipher = import_node_crypto.default.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
177
- decipher.setAuthTag(Buffer.from(tag));
178
- const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
179
- return toUint8Array(decrypted);
180
- },
181
- timingSafeEqual(a, b) {
182
- if (a.length !== b.length) return false;
183
- return import_node_crypto.default.timingSafeEqual(Buffer.from(a), Buffer.from(b));
184
- }
185
- };
186
- }
187
- });
188
-
189
- // libs/utils/src/crypto/browser.ts
190
- var browser_exports = {};
191
- __export(browser_exports, {
192
- browserCrypto: () => browserCrypto
193
- });
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 = (0, import_utils.randomBytes)(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 import_sha2, import_hmac, import_hkdf, import_utils, import_aes, browserCrypto;
222
- var init_browser = __esm({
223
- "libs/utils/src/crypto/browser.ts"() {
224
- "use strict";
225
- import_sha2 = require("@noble/hashes/sha2.js");
226
- import_hmac = require("@noble/hashes/hmac.js");
227
- import_hkdf = require("@noble/hashes/hkdf.js");
228
- import_utils = require("@noble/hashes/utils.js");
229
- import_aes = require("@noble/ciphers/aes.js");
230
- browserCrypto = {
231
- randomUUID() {
232
- return generateUUID();
233
- },
234
- randomBytes(length) {
235
- return (0, import_utils.randomBytes)(length);
236
- },
237
- sha256(data) {
238
- return (0, import_sha2.sha256)(toBytes(data));
239
- },
240
- sha256Hex(data) {
241
- return toHex((0, import_sha2.sha256)(toBytes(data)));
242
- },
243
- hmacSha256(key, data) {
244
- return (0, import_hmac.hmac)(import_sha2.sha256, key, data);
245
- },
246
- hkdfSha256(ikm, salt, info, length) {
247
- const effectiveSalt = salt.length > 0 ? salt : new Uint8Array(32);
248
- return (0, import_hkdf.hkdf)(import_sha2.sha256, ikm, effectiveSalt, info, length);
249
- },
250
- encryptAesGcm(key, plaintext, iv) {
251
- const cipher = (0, import_aes.gcm)(key, iv);
252
- const sealed = cipher.encrypt(plaintext);
253
- const ciphertext = sealed.slice(0, -16);
254
- const tag = sealed.slice(-16);
255
- return { ciphertext, tag };
256
- },
257
- decryptAesGcm(key, ciphertext, iv, tag) {
258
- const cipher = (0, import_aes.gcm)(key, iv);
259
- const sealed = new Uint8Array(ciphertext.length + tag.length);
260
- sealed.set(ciphertext);
261
- sealed.set(tag, ciphertext.length);
262
- return cipher.decrypt(sealed);
263
- },
264
- timingSafeEqual(a, b) {
265
- return constantTimeEqual(a, b);
266
- }
267
- };
268
- }
269
- });
270
-
271
60
  // libs/utils/src/storage/errors.ts
272
- var StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError;
61
+ var StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError;
273
62
  var init_errors = __esm({
274
63
  "libs/utils/src/storage/errors.ts"() {
275
64
  "use strict";
@@ -287,7 +76,8 @@ var init_errors = __esm({
287
76
  };
288
77
  StorageConnectionError = class extends StorageError {
289
78
  constructor(message, cause, backend) {
290
- super(message, cause);
79
+ const fullMessage = cause ? `${message}: ${cause.message}` : message;
80
+ super(fullMessage, cause);
291
81
  this.backend = backend;
292
82
  this.name = "StorageConnectionError";
293
83
  }
@@ -337,6 +127,12 @@ var init_errors = __esm({
337
127
  this.name = "StorageNotConnectedError";
338
128
  }
339
129
  };
130
+ EncryptedStorageError = class extends StorageError {
131
+ constructor(message) {
132
+ super(message);
133
+ this.name = "EncryptedStorageError";
134
+ }
135
+ };
340
136
  }
341
137
  });
342
138
 
@@ -502,15 +298,309 @@ var init_base = __esm({
502
298
  }
503
299
  });
504
300
 
505
- // libs/utils/src/storage/adapters/redis.ts
506
- var redis_exports = {};
507
- __export(redis_exports, {
508
- RedisStorageAdapter: () => RedisStorageAdapter
509
- });
510
- function getRedisClass() {
301
+ // libs/utils/src/storage/utils/pattern.ts
302
+ function globToRegex(pattern) {
303
+ if (pattern.length > MAX_PATTERN_LENGTH) {
304
+ throw new StoragePatternError(
305
+ pattern.substring(0, 50) + "...",
306
+ `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
307
+ );
308
+ }
309
+ const wildcardCount = (pattern.match(/[*?]/g) || []).length;
310
+ if (wildcardCount > MAX_WILDCARDS) {
311
+ throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
312
+ }
313
+ if (pattern === "" || pattern === "*") {
314
+ return /^.*$/;
315
+ }
316
+ let regexStr = "^";
317
+ let prevChar = "";
318
+ for (let i = 0; i < pattern.length; i++) {
319
+ const char = pattern[i];
320
+ switch (char) {
321
+ case "*":
322
+ if (prevChar !== "*") {
323
+ regexStr += ".*";
324
+ }
325
+ break;
326
+ case "?":
327
+ regexStr += ".";
328
+ break;
329
+ default:
330
+ regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
331
+ }
332
+ prevChar = char;
333
+ }
334
+ regexStr += "$";
511
335
  try {
512
- return require("ioredis").default || require("ioredis");
336
+ return new RegExp(regexStr);
513
337
  } catch {
338
+ throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
339
+ }
340
+ }
341
+ function matchesPattern(key, pattern) {
342
+ const regex = globToRegex(pattern);
343
+ return regex.test(key);
344
+ }
345
+ function validatePattern(pattern) {
346
+ try {
347
+ globToRegex(pattern);
348
+ return { valid: true };
349
+ } catch (e) {
350
+ return {
351
+ valid: false,
352
+ error: e instanceof Error ? e.message : "Invalid pattern"
353
+ };
354
+ }
355
+ }
356
+ function escapeGlob(literal) {
357
+ return literal.replace(/[*?\\]/g, "\\$&");
358
+ }
359
+ var MAX_PATTERN_LENGTH, MAX_WILDCARDS, REGEX_SPECIAL_CHARS;
360
+ var init_pattern = __esm({
361
+ "libs/utils/src/storage/utils/pattern.ts"() {
362
+ "use strict";
363
+ init_errors();
364
+ MAX_PATTERN_LENGTH = 500;
365
+ MAX_WILDCARDS = 20;
366
+ REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
367
+ }
368
+ });
369
+
370
+ // libs/utils/src/crypto/node.ts
371
+ var node_exports = {};
372
+ __export(node_exports, {
373
+ createSignedJwt: () => createSignedJwt,
374
+ generateRsaKeyPair: () => generateRsaKeyPair,
375
+ isRsaPssAlg: () => isRsaPssAlg,
376
+ jwtAlgToNodeAlg: () => jwtAlgToNodeAlg,
377
+ nodeCrypto: () => nodeCrypto,
378
+ rsaSign: () => rsaSign,
379
+ rsaVerify: () => rsaVerify
380
+ });
381
+ function toUint8Array(buf) {
382
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
383
+ }
384
+ function toBuffer(data) {
385
+ if (typeof data === "string") {
386
+ return Buffer.from(data, "utf8");
387
+ }
388
+ return Buffer.from(data);
389
+ }
390
+ function generateRsaKeyPair(modulusLength = 2048, alg = "RS256") {
391
+ const kid = `rsa-key-${Date.now()}-${import_node_crypto.default.randomBytes(8).toString("hex")}`;
392
+ const { privateKey, publicKey } = import_node_crypto.default.generateKeyPairSync("rsa", {
393
+ modulusLength
394
+ });
395
+ const exported = publicKey.export({ format: "jwk" });
396
+ const publicJwk = {
397
+ ...exported,
398
+ kid,
399
+ alg,
400
+ use: "sig",
401
+ kty: "RSA"
402
+ };
403
+ return { privateKey, publicKey, publicJwk };
404
+ }
405
+ function rsaSign(algorithm, data, privateKey, options) {
406
+ const signingKey = options ? { key: privateKey, ...options } : privateKey;
407
+ return import_node_crypto.default.sign(algorithm, data, signingKey);
408
+ }
409
+ function rsaVerify(jwtAlg, data, publicJwk, signature) {
410
+ const publicKey = import_node_crypto.default.createPublicKey({ key: publicJwk, format: "jwk" });
411
+ const nodeAlgorithm = jwtAlgToNodeAlg(jwtAlg);
412
+ const verifyKey = isRsaPssAlg(jwtAlg) ? {
413
+ key: publicKey,
414
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
415
+ saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
416
+ } : publicKey;
417
+ return import_node_crypto.default.verify(nodeAlgorithm, data, verifyKey, signature);
418
+ }
419
+ function createSignedJwt(payload, privateKey, kid, alg = "RS256") {
420
+ const header = { alg, typ: "JWT", kid };
421
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
422
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
423
+ const signatureInput = `${headerB64}.${payloadB64}`;
424
+ const nodeAlgorithm = jwtAlgToNodeAlg(alg);
425
+ const signature = rsaSign(
426
+ nodeAlgorithm,
427
+ Buffer.from(signatureInput),
428
+ privateKey,
429
+ isRsaPssAlg(alg) ? {
430
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
431
+ saltLength: import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST
432
+ } : void 0
433
+ );
434
+ const signatureB64 = signature.toString("base64url");
435
+ return `${headerB64}.${payloadB64}.${signatureB64}`;
436
+ }
437
+ var import_node_crypto, nodeCrypto;
438
+ var init_node = __esm({
439
+ "libs/utils/src/crypto/node.ts"() {
440
+ "use strict";
441
+ import_node_crypto = __toESM(require("node:crypto"));
442
+ init_jwt_alg();
443
+ init_jwt_alg();
444
+ nodeCrypto = {
445
+ randomUUID() {
446
+ return import_node_crypto.default.randomUUID();
447
+ },
448
+ randomBytes(length) {
449
+ return toUint8Array(import_node_crypto.default.randomBytes(length));
450
+ },
451
+ sha256(data) {
452
+ const hash = import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest();
453
+ return toUint8Array(hash);
454
+ },
455
+ sha256Hex(data) {
456
+ return import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest("hex");
457
+ },
458
+ hmacSha256(key, data) {
459
+ const hmac2 = import_node_crypto.default.createHmac("sha256", Buffer.from(key)).update(Buffer.from(data)).digest();
460
+ return toUint8Array(hmac2);
461
+ },
462
+ hkdfSha256(ikm, salt, info, length) {
463
+ const ikmBuf = Buffer.from(ikm);
464
+ const saltBuf = salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(32);
465
+ const prk = import_node_crypto.default.createHmac("sha256", saltBuf).update(ikmBuf).digest();
466
+ const hashLen = 32;
467
+ const n = Math.ceil(length / hashLen);
468
+ const chunks = [];
469
+ let prev = Buffer.alloc(0);
470
+ for (let i = 1; i <= n; i++) {
471
+ prev = import_node_crypto.default.createHmac("sha256", prk).update(Buffer.concat([prev, Buffer.from(info), Buffer.from([i])])).digest();
472
+ chunks.push(prev);
473
+ }
474
+ return toUint8Array(Buffer.concat(chunks).subarray(0, length));
475
+ },
476
+ encryptAesGcm(key, plaintext, iv) {
477
+ const cipher = import_node_crypto.default.createCipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
478
+ const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
479
+ const tag = cipher.getAuthTag();
480
+ return {
481
+ ciphertext: toUint8Array(encrypted),
482
+ tag: toUint8Array(tag)
483
+ };
484
+ },
485
+ decryptAesGcm(key, ciphertext, iv, tag) {
486
+ const decipher = import_node_crypto.default.createDecipheriv("aes-256-gcm", Buffer.from(key), Buffer.from(iv));
487
+ decipher.setAuthTag(Buffer.from(tag));
488
+ const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]);
489
+ return toUint8Array(decrypted);
490
+ },
491
+ timingSafeEqual(a, b) {
492
+ if (a.length !== b.length) return false;
493
+ return import_node_crypto.default.timingSafeEqual(Buffer.from(a), Buffer.from(b));
494
+ }
495
+ };
496
+ }
497
+ });
498
+
499
+ // libs/utils/src/crypto/browser.ts
500
+ var browser_exports = {};
501
+ __export(browser_exports, {
502
+ browserCrypto: () => browserCrypto
503
+ });
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 = (0, import_utils.randomBytes)(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 import_sha2, import_hmac, import_hkdf, import_utils, import_aes, browserCrypto;
532
+ var init_browser = __esm({
533
+ "libs/utils/src/crypto/browser.ts"() {
534
+ "use strict";
535
+ import_sha2 = require("@noble/hashes/sha2.js");
536
+ import_hmac = require("@noble/hashes/hmac.js");
537
+ import_hkdf = require("@noble/hashes/hkdf.js");
538
+ import_utils = require("@noble/hashes/utils.js");
539
+ import_aes = require("@noble/ciphers/aes.js");
540
+ browserCrypto = {
541
+ randomUUID() {
542
+ return generateUUID();
543
+ },
544
+ randomBytes(length) {
545
+ return (0, import_utils.randomBytes)(length);
546
+ },
547
+ sha256(data) {
548
+ return (0, import_sha2.sha256)(toBytes(data));
549
+ },
550
+ sha256Hex(data) {
551
+ return toHex((0, import_sha2.sha256)(toBytes(data)));
552
+ },
553
+ hmacSha256(key, data) {
554
+ return (0, import_hmac.hmac)(import_sha2.sha256, key, data);
555
+ },
556
+ hkdfSha256(ikm, salt, info, length) {
557
+ const effectiveSalt = salt.length > 0 ? salt : new Uint8Array(32);
558
+ return (0, import_hkdf.hkdf)(import_sha2.sha256, ikm, effectiveSalt, info, length);
559
+ },
560
+ encryptAesGcm(key, plaintext, iv) {
561
+ const cipher = (0, import_aes.gcm)(key, iv);
562
+ const sealed = cipher.encrypt(plaintext);
563
+ const ciphertext = sealed.slice(0, -16);
564
+ const tag = sealed.slice(-16);
565
+ return { ciphertext, tag };
566
+ },
567
+ decryptAesGcm(key, ciphertext, iv, tag) {
568
+ const cipher = (0, import_aes.gcm)(key, iv);
569
+ const sealed = new Uint8Array(ciphertext.length + tag.length);
570
+ sealed.set(ciphertext);
571
+ sealed.set(tag, ciphertext.length);
572
+ return cipher.decrypt(sealed);
573
+ },
574
+ timingSafeEqual(a, b) {
575
+ return constantTimeEqual(a, b);
576
+ }
577
+ };
578
+ }
579
+ });
580
+
581
+ // libs/utils/src/storage/utils/index.ts
582
+ var init_utils = __esm({
583
+ "libs/utils/src/storage/utils/index.ts"() {
584
+ "use strict";
585
+ init_ttl();
586
+ }
587
+ });
588
+
589
+ // libs/utils/src/storage/adapters/redis.ts
590
+ var redis_exports = {};
591
+ __export(redis_exports, {
592
+ RedisStorageAdapter: () => RedisStorageAdapter
593
+ });
594
+ function getRedisClass() {
595
+ try {
596
+ return require("ioredis").default || require("ioredis");
597
+ } catch (error) {
598
+ const msg = error instanceof Error ? error.message : String(error);
599
+ if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
600
+ throw new Error(
601
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`
602
+ );
603
+ }
514
604
  throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
515
605
  }
516
606
  }
@@ -520,7 +610,7 @@ var init_redis = __esm({
520
610
  "use strict";
521
611
  init_base();
522
612
  init_errors();
523
- init_ttl();
613
+ init_utils();
524
614
  RedisStorageAdapter = class extends BaseStorageAdapter {
525
615
  backendName = "redis";
526
616
  client;
@@ -1356,6 +1446,10 @@ __export(index_exports, {
1356
1446
  DEFAULT_CODE_VERIFIER_LENGTH: () => DEFAULT_CODE_VERIFIER_LENGTH,
1357
1447
  DEFAULT_MAX_INPUT_LENGTH: () => DEFAULT_MAX_INPUT_LENGTH,
1358
1448
  EncryptedBlobError: () => EncryptedBlobError,
1449
+ EncryptedStorageError: () => EncryptedStorageError,
1450
+ EncryptedTypedStorage: () => EncryptedTypedStorage,
1451
+ FileSystemStorageAdapter: () => FileSystemStorageAdapter,
1452
+ KeyPersistence: () => KeyPersistence,
1359
1453
  MAX_CODE_VERIFIER_LENGTH: () => MAX_CODE_VERIFIER_LENGTH,
1360
1454
  MAX_TTL_SECONDS: () => MAX_TTL_SECONDS,
1361
1455
  MIN_CODE_VERIFIER_LENGTH: () => MIN_CODE_VERIFIER_LENGTH,
@@ -1373,11 +1467,17 @@ __export(index_exports, {
1373
1467
  StorageOperationError: () => StorageOperationError,
1374
1468
  StoragePatternError: () => StoragePatternError,
1375
1469
  StorageTTLError: () => StorageTTLError,
1470
+ TypedStorage: () => TypedStorage,
1376
1471
  UpstashStorageAdapter: () => UpstashStorageAdapter,
1377
1472
  VercelKvStorageAdapter: () => VercelKvStorageAdapter,
1378
1473
  access: () => access,
1379
1474
  analyzePattern: () => analyzePattern,
1475
+ anyKeyDataSchema: () => anyKeyDataSchema,
1380
1476
  assertNode: () => assertNode,
1477
+ asymmetricAlgSchema: () => asymmetricAlgSchema,
1478
+ asymmetricKeyDataSchema: () => asymmetricKeyDataSchema,
1479
+ base64Decode: () => base64Decode,
1480
+ base64Encode: () => base64Encode,
1381
1481
  base64urlDecode: () => base64urlDecode,
1382
1482
  base64urlEncode: () => base64urlEncode,
1383
1483
  buildPrefix: () => buildPrefix,
@@ -1387,6 +1487,8 @@ __export(index_exports, {
1387
1487
  collapseWhitespace: () => collapseWhitespace,
1388
1488
  copyFile: () => copyFile,
1389
1489
  cp: () => cp,
1490
+ createKeyPersistence: () => createKeyPersistence,
1491
+ createKeyPersistenceWithStorage: () => createKeyPersistenceWithStorage,
1390
1492
  createMemoryStorage: () => createMemoryStorage,
1391
1493
  createNamespacedStorage: () => createNamespacedStorage,
1392
1494
  createRootStorage: () => createRootStorage,
@@ -1428,6 +1530,7 @@ __export(index_exports, {
1428
1530
  hmacSha256: () => hmacSha256,
1429
1531
  idFromString: () => idFromString,
1430
1532
  inferMimeType: () => inferMimeType,
1533
+ isAsymmetricKeyData: () => isAsymmetricKeyData,
1431
1534
  isBrowser: () => isBrowser,
1432
1535
  isDirEmpty: () => isDirEmpty,
1433
1536
  isExpired: () => isExpired,
@@ -1436,7 +1539,9 @@ __export(index_exports, {
1436
1539
  isPatternSafe: () => isPatternSafe,
1437
1540
  isRsaPssAlg: () => isRsaPssAlg,
1438
1541
  isSecretCached: () => isSecretCached,
1542
+ isSecretKeyData: () => isSecretKeyData,
1439
1543
  isSecretPersistenceEnabled: () => isSecretPersistenceEnabled,
1544
+ isSignedData: () => isSignedData,
1440
1545
  isUriTemplate: () => isUriTemplate,
1441
1546
  isValidCodeChallenge: () => isValidCodeChallenge,
1442
1547
  isValidCodeVerifier: () => isValidCodeVerifier,
@@ -1451,12 +1556,14 @@ __export(index_exports, {
1451
1556
  mkdir: () => mkdir,
1452
1557
  mkdtemp: () => mkdtemp,
1453
1558
  normalizeTTL: () => normalizeTTL,
1559
+ parseKeyData: () => parseKeyData,
1454
1560
  parseSecretData: () => parseSecretData,
1455
1561
  parseUriTemplate: () => parseUriTemplate,
1456
1562
  randomBytes: () => randomBytes,
1457
1563
  randomUUID: () => randomUUID,
1458
1564
  readFile: () => readFile,
1459
1565
  readFileBuffer: () => readFileBuffer,
1566
+ readFileSync: () => readFileSync,
1460
1567
  readJSON: () => readJSON,
1461
1568
  readdir: () => readdir,
1462
1569
  rename: () => rename,
@@ -1473,12 +1580,14 @@ __export(index_exports, {
1473
1580
  sanitizeToJson: () => sanitizeToJson,
1474
1581
  saveSecret: () => saveSecret,
1475
1582
  secretDataSchema: () => secretDataSchema,
1583
+ secretKeyDataSchema: () => secretKeyDataSchema,
1476
1584
  sepFor: () => sepFor,
1477
1585
  serializeBlob: () => serializeBlob,
1478
1586
  sha256: () => sha256,
1479
1587
  sha256Base64url: () => sha256Base64url,
1480
1588
  sha256Hex: () => sha256Hex,
1481
1589
  shortHash: () => shortHash,
1590
+ signData: () => signData,
1482
1591
  splitWords: () => splitWords,
1483
1592
  stat: () => stat,
1484
1593
  timingSafeEqual: () => timingSafeEqual,
@@ -1494,11 +1603,14 @@ __export(index_exports, {
1494
1603
  ttlToExpiresAt: () => ttlToExpiresAt,
1495
1604
  unlink: () => unlink,
1496
1605
  validateBaseUrl: () => validateBaseUrl,
1606
+ validateKeyData: () => validateKeyData,
1497
1607
  validateOptionalTTL: () => validateOptionalTTL,
1498
1608
  validatePattern: () => validatePattern,
1499
1609
  validateSecretData: () => validateSecretData,
1500
1610
  validateTTL: () => validateTTL,
1501
1611
  verifyCodeChallenge: () => verifyCodeChallenge,
1612
+ verifyData: () => verifyData,
1613
+ verifyOrParseData: () => verifyOrParseData,
1502
1614
  writeFile: () => writeFile,
1503
1615
  writeJSON: () => writeJSON
1504
1616
  });
@@ -2001,6 +2113,7 @@ function assertNode(feature) {
2001
2113
 
2002
2114
  // libs/utils/src/fs/fs.ts
2003
2115
  var _fsp = null;
2116
+ var _fs = null;
2004
2117
  var _spawn = null;
2005
2118
  function getFsp() {
2006
2119
  if (!_fsp) {
@@ -2009,8 +2122,15 @@ function getFsp() {
2009
2122
  }
2010
2123
  return _fsp;
2011
2124
  }
2012
- function getSpawn() {
2013
- if (!_spawn) {
2125
+ function getFs() {
2126
+ if (!_fs) {
2127
+ assertNode("File system operations");
2128
+ _fs = require("fs");
2129
+ }
2130
+ return _fs;
2131
+ }
2132
+ function getSpawn() {
2133
+ if (!_spawn) {
2014
2134
  assertNode("Child process operations");
2015
2135
  _spawn = require("child_process").spawn;
2016
2136
  }
@@ -2021,6 +2141,10 @@ async function readFile(p, encoding = "utf8") {
2021
2141
  const fsp = getFsp();
2022
2142
  return fsp.readFile(p, encoding);
2023
2143
  }
2144
+ function readFileSync(p, encoding = "utf8") {
2145
+ const fs = getFs();
2146
+ return fs.readFileSync(p, encoding);
2147
+ }
2024
2148
  async function readFileBuffer(p) {
2025
2149
  const fsp = getFsp();
2026
2150
  return fsp.readFile(p);
@@ -2516,379 +2640,372 @@ function isSecretCached(options) {
2516
2640
  return secretCache.has(cacheKey);
2517
2641
  }
2518
2642
 
2519
- // libs/utils/src/crypto/index.ts
2520
- var _provider = null;
2521
- function getCrypto() {
2522
- if (!_provider) {
2523
- if (isNode()) {
2524
- _provider = (init_node(), __toCommonJS(node_exports)).nodeCrypto;
2525
- } else {
2526
- _provider = (init_browser(), __toCommonJS(browser_exports)).browserCrypto;
2527
- }
2528
- }
2529
- return _provider;
2530
- }
2531
- function rsaVerify2(jwtAlg, data, publicJwk, signature) {
2532
- assertNode("rsaVerify");
2533
- return (init_node(), __toCommonJS(node_exports)).rsaVerify(jwtAlg, data, publicJwk, signature);
2534
- }
2535
- function randomUUID() {
2536
- return getCrypto().randomUUID();
2643
+ // libs/utils/src/crypto/hmac-signing.ts
2644
+ function computeSignature(data, secret) {
2645
+ const encoder = new TextEncoder();
2646
+ const keyBytes = encoder.encode(secret);
2647
+ const dataBytes = encoder.encode(data);
2648
+ const hmac2 = hmacSha256(keyBytes, dataBytes);
2649
+ return base64urlEncode(hmac2);
2650
+ }
2651
+ function signData(data, config) {
2652
+ const jsonData = JSON.stringify(data);
2653
+ const sig = computeSignature(jsonData, config.secret);
2654
+ const signed = {
2655
+ data,
2656
+ sig,
2657
+ v: 1
2658
+ };
2659
+ return JSON.stringify(signed);
2537
2660
  }
2538
- function randomBytes(length) {
2539
- if (!Number.isInteger(length) || length <= 0) {
2540
- throw new Error(`randomBytes length must be a positive integer, got ${length}`);
2661
+ function verifyData(signedJson, config) {
2662
+ try {
2663
+ const parsed = JSON.parse(signedJson);
2664
+ if (!parsed || typeof parsed !== "object" || !("sig" in parsed)) {
2665
+ return null;
2666
+ }
2667
+ const signed = parsed;
2668
+ if (signed.v !== 1) {
2669
+ return null;
2670
+ }
2671
+ const jsonData = JSON.stringify(signed.data);
2672
+ const expectedSig = computeSignature(jsonData, config.secret);
2673
+ const sigBytes = base64urlDecode(signed.sig);
2674
+ const expectedBytes = base64urlDecode(expectedSig);
2675
+ if (sigBytes.length !== expectedBytes.length) {
2676
+ return null;
2677
+ }
2678
+ if (!timingSafeEqual(sigBytes, expectedBytes)) {
2679
+ return null;
2680
+ }
2681
+ return signed.data;
2682
+ } catch {
2683
+ return null;
2541
2684
  }
2542
- return getCrypto().randomBytes(length);
2543
- }
2544
- function sha256(data) {
2545
- return getCrypto().sha256(data);
2546
- }
2547
- function sha256Hex(data) {
2548
- return getCrypto().sha256Hex(data);
2549
- }
2550
- function hmacSha256(key, data) {
2551
- return getCrypto().hmacSha256(key, data);
2552
2685
  }
2553
- var HKDF_SHA256_MAX_LENGTH = 255 * 32;
2554
- function hkdfSha256(ikm, salt, info, length) {
2555
- if (!Number.isInteger(length) || length <= 0) {
2556
- throw new Error(`HKDF length must be a positive integer, got ${length}`);
2557
- }
2558
- if (length > HKDF_SHA256_MAX_LENGTH) {
2559
- throw new Error(`HKDF-SHA256 length cannot exceed ${HKDF_SHA256_MAX_LENGTH} bytes, got ${length}`);
2686
+ function isSignedData(json) {
2687
+ try {
2688
+ const parsed = JSON.parse(json);
2689
+ return parsed && typeof parsed === "object" && "sig" in parsed && "v" in parsed;
2690
+ } catch {
2691
+ return false;
2560
2692
  }
2561
- return getCrypto().hkdfSha256(ikm, salt, info, length);
2562
2693
  }
2563
- function encryptAesGcm(key, plaintext, iv) {
2564
- if (key.length !== 32) {
2565
- throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
2694
+ function verifyOrParseData(json, config) {
2695
+ if (isSignedData(json)) {
2696
+ return verifyData(json, config);
2566
2697
  }
2567
- if (iv.length !== 12) {
2568
- throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
2698
+ try {
2699
+ return JSON.parse(json);
2700
+ } catch {
2701
+ return null;
2569
2702
  }
2570
- return getCrypto().encryptAesGcm(key, plaintext, iv);
2571
2703
  }
2572
- function decryptAesGcm(key, ciphertext, iv, tag) {
2573
- if (key.length !== 32) {
2574
- throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
2704
+
2705
+ // libs/utils/src/crypto/key-persistence/schemas.ts
2706
+ var import_zod2 = require("zod");
2707
+ var MAX_CLOCK_DRIFT_MS2 = 6e4;
2708
+ var MAX_KEY_AGE_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
2709
+ var asymmetricAlgSchema = import_zod2.z.enum(["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]);
2710
+ var baseKeyDataSchema = import_zod2.z.object({
2711
+ kid: import_zod2.z.string().min(1),
2712
+ createdAt: import_zod2.z.number().positive().int(),
2713
+ version: import_zod2.z.number().positive().int()
2714
+ });
2715
+ var jsonWebKeySchema = import_zod2.z.object({
2716
+ kty: import_zod2.z.string().optional(),
2717
+ use: import_zod2.z.string().optional(),
2718
+ alg: import_zod2.z.string().optional(),
2719
+ kid: import_zod2.z.string().optional(),
2720
+ n: import_zod2.z.string().optional(),
2721
+ e: import_zod2.z.string().optional(),
2722
+ d: import_zod2.z.string().optional(),
2723
+ p: import_zod2.z.string().optional(),
2724
+ q: import_zod2.z.string().optional(),
2725
+ dp: import_zod2.z.string().optional(),
2726
+ dq: import_zod2.z.string().optional(),
2727
+ qi: import_zod2.z.string().optional(),
2728
+ x: import_zod2.z.string().optional(),
2729
+ y: import_zod2.z.string().optional(),
2730
+ crv: import_zod2.z.string().optional()
2731
+ }).passthrough();
2732
+ var secretKeyDataSchema = baseKeyDataSchema.extend({
2733
+ type: import_zod2.z.literal("secret"),
2734
+ secret: import_zod2.z.string().min(1),
2735
+ bytes: import_zod2.z.number().positive().int()
2736
+ });
2737
+ var asymmetricKeyDataSchema = baseKeyDataSchema.extend({
2738
+ type: import_zod2.z.literal("asymmetric"),
2739
+ alg: asymmetricAlgSchema,
2740
+ privateKey: jsonWebKeySchema,
2741
+ publicJwk: import_zod2.z.object({
2742
+ keys: import_zod2.z.array(jsonWebKeySchema).min(1)
2743
+ })
2744
+ });
2745
+ var anyKeyDataSchema = import_zod2.z.discriminatedUnion("type", [secretKeyDataSchema, asymmetricKeyDataSchema]);
2746
+ function validateKeyData(data) {
2747
+ const result = anyKeyDataSchema.safeParse(data);
2748
+ if (!result.success) {
2749
+ return {
2750
+ valid: false,
2751
+ error: result.error.issues[0]?.message ?? "Invalid key structure"
2752
+ };
2575
2753
  }
2576
- if (iv.length !== 12) {
2577
- throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
2754
+ const parsed = result.data;
2755
+ const now = Date.now();
2756
+ if (parsed.createdAt > now + MAX_CLOCK_DRIFT_MS2) {
2757
+ return { valid: false, error: "createdAt is in the future" };
2578
2758
  }
2579
- if (tag.length !== 16) {
2580
- throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`);
2759
+ if (parsed.createdAt < now - MAX_KEY_AGE_MS) {
2760
+ return { valid: false, error: "createdAt is too old" };
2581
2761
  }
2582
- return getCrypto().decryptAesGcm(key, ciphertext, iv, tag);
2762
+ return { valid: true, data: parsed };
2583
2763
  }
2584
- function timingSafeEqual(a, b) {
2585
- if (a.length !== b.length) {
2586
- throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`);
2764
+ function parseKeyData(data) {
2765
+ const validation = validateKeyData(data);
2766
+ if (!validation.valid) {
2767
+ return null;
2587
2768
  }
2588
- return getCrypto().timingSafeEqual(a, b);
2769
+ return validation.data;
2589
2770
  }
2590
- function bytesToHex(data) {
2591
- return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
2771
+ function isSecretKeyData(data) {
2772
+ return data.type === "secret";
2592
2773
  }
2593
- function base64urlEncode(data) {
2594
- let base64;
2595
- if (typeof Buffer !== "undefined") {
2596
- base64 = Buffer.from(data).toString("base64");
2597
- } else {
2598
- const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
2599
- base64 = btoa(binString);
2600
- }
2601
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2774
+ function isAsymmetricKeyData(data) {
2775
+ return data.type === "asymmetric";
2602
2776
  }
2603
- function base64urlDecode(data) {
2604
- let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
2605
- const pad = base64.length % 4;
2606
- if (pad) {
2607
- base64 += "=".repeat(4 - pad);
2777
+
2778
+ // libs/utils/src/crypto/key-persistence/key-persistence.ts
2779
+ var KeyPersistence = class {
2780
+ storage;
2781
+ cache = /* @__PURE__ */ new Map();
2782
+ enableCache;
2783
+ throwOnInvalid;
2784
+ constructor(options) {
2785
+ this.storage = options.storage;
2786
+ this.enableCache = options.enableCache ?? true;
2787
+ this.throwOnInvalid = options.throwOnInvalid ?? false;
2608
2788
  }
2609
- if (typeof Buffer !== "undefined") {
2610
- return new Uint8Array(Buffer.from(base64, "base64"));
2611
- } else {
2612
- const binString = atob(base64);
2613
- return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
2789
+ /**
2790
+ * Get a key by ID.
2791
+ *
2792
+ * @param kid - Key identifier
2793
+ * @returns Key data or null if not found
2794
+ */
2795
+ async get(kid) {
2796
+ if (this.enableCache) {
2797
+ const cached = this.cache.get(kid);
2798
+ if (cached) return cached;
2799
+ }
2800
+ const raw = await this.storage.get(kid);
2801
+ if (!raw) return null;
2802
+ let parsed;
2803
+ try {
2804
+ parsed = JSON.parse(raw);
2805
+ } catch {
2806
+ if (this.throwOnInvalid) {
2807
+ throw new Error(`Invalid JSON for key "${kid}"`);
2808
+ }
2809
+ return null;
2810
+ }
2811
+ const validation = validateKeyData(parsed);
2812
+ if (!validation.valid) {
2813
+ if (this.throwOnInvalid) {
2814
+ throw new Error(`Invalid key data for "${kid}": ${validation.error}`);
2815
+ }
2816
+ return null;
2817
+ }
2818
+ const key = validation.data;
2819
+ if (this.enableCache) {
2820
+ this.cache.set(kid, key);
2821
+ }
2822
+ return key;
2614
2823
  }
2615
- }
2616
- function sha256Base64url(data) {
2617
- return base64urlEncode(sha256(data));
2618
- }
2619
-
2620
- // libs/utils/src/storage/factory.ts
2621
- init_errors();
2622
-
2623
- // libs/utils/src/storage/namespace.ts
2624
- var NAMESPACE_SEPARATOR = ":";
2625
- function buildPrefix(name, id) {
2626
- if (id) {
2627
- return `${name}${NAMESPACE_SEPARATOR}${id}${NAMESPACE_SEPARATOR}`;
2824
+ /**
2825
+ * Get a secret key by ID.
2826
+ *
2827
+ * @param kid - Key identifier
2828
+ * @returns Secret key data or null if not found or wrong type
2829
+ */
2830
+ async getSecret(kid) {
2831
+ const key = await this.get(kid);
2832
+ if (!key || !isSecretKeyData(key)) return null;
2833
+ return key;
2628
2834
  }
2629
- return `${name}${NAMESPACE_SEPARATOR}`;
2630
- }
2631
- var NamespacedStorageImpl = class _NamespacedStorageImpl {
2632
- constructor(adapter, prefix = "", root = adapter) {
2633
- this.adapter = adapter;
2634
- this.prefix = prefix;
2635
- this.root = root;
2835
+ /**
2836
+ * Get an asymmetric key by ID.
2837
+ *
2838
+ * @param kid - Key identifier
2839
+ * @returns Asymmetric key data or null if not found or wrong type
2840
+ */
2841
+ async getAsymmetric(kid) {
2842
+ const key = await this.get(kid);
2843
+ if (!key || !isAsymmetricKeyData(key)) return null;
2844
+ return key;
2636
2845
  }
2637
- // ============================================
2638
- // Key Prefixing Helpers
2639
- // ============================================
2640
2846
  /**
2641
- * Add prefix to a key.
2847
+ * Store a key.
2848
+ *
2849
+ * @param key - Key data to store
2642
2850
  */
2643
- prefixKey(key) {
2644
- return this.prefix + key;
2851
+ async set(key) {
2852
+ const validation = validateKeyData(key);
2853
+ if (!validation.valid) {
2854
+ throw new Error(`Invalid key data: ${validation.error}`);
2855
+ }
2856
+ await this.storage.set(key.kid, JSON.stringify(key, null, 2));
2857
+ if (this.enableCache) {
2858
+ this.cache.set(key.kid, key);
2859
+ }
2645
2860
  }
2646
2861
  /**
2647
- * Remove prefix from a key.
2862
+ * Delete a key.
2863
+ *
2864
+ * @param kid - Key identifier
2865
+ * @returns true if key existed and was deleted
2648
2866
  */
2649
- unprefixKey(key) {
2650
- if (this.prefix && key.startsWith(this.prefix)) {
2651
- return key.slice(this.prefix.length);
2867
+ async delete(kid) {
2868
+ this.cache.delete(kid);
2869
+ return this.storage.delete(kid);
2870
+ }
2871
+ /**
2872
+ * Check if a key exists.
2873
+ *
2874
+ * @param kid - Key identifier
2875
+ * @returns true if key exists
2876
+ */
2877
+ async has(kid) {
2878
+ if (this.enableCache && this.cache.has(kid)) {
2879
+ return true;
2652
2880
  }
2653
- return key;
2881
+ return this.storage.exists(kid);
2654
2882
  }
2655
2883
  /**
2656
- * Add prefix to a pattern (for keys() operation).
2884
+ * List all key IDs.
2885
+ *
2886
+ * @returns Array of key identifiers
2657
2887
  */
2658
- prefixPattern(pattern) {
2659
- return this.prefix + pattern;
2888
+ async list() {
2889
+ return this.storage.keys();
2660
2890
  }
2661
2891
  /**
2662
- * Add prefix to a channel (for pub/sub).
2892
+ * Get or create a secret key.
2893
+ *
2894
+ * If a key with the given ID exists and is a valid secret key,
2895
+ * it is returned. Otherwise, a new secret key is generated.
2896
+ *
2897
+ * @param kid - Key identifier
2898
+ * @param options - Options for key creation
2899
+ * @returns Secret key data
2900
+ *
2901
+ * @example
2902
+ * ```typescript
2903
+ * const secret = await keys.getOrCreateSecret('encryption-key');
2904
+ * const bytes = base64urlDecode(secret.secret);
2905
+ * ```
2663
2906
  */
2664
- prefixChannel(channel) {
2665
- return this.prefix + channel;
2907
+ async getOrCreateSecret(kid, options) {
2908
+ const existing = await this.getSecret(kid);
2909
+ if (existing) {
2910
+ return existing;
2911
+ }
2912
+ const bytes = options?.bytes ?? 32;
2913
+ const secret = {
2914
+ type: "secret",
2915
+ kid,
2916
+ secret: base64urlEncode(randomBytes(bytes)),
2917
+ bytes,
2918
+ createdAt: Date.now(),
2919
+ version: 1
2920
+ };
2921
+ await this.set(secret);
2922
+ return secret;
2666
2923
  }
2667
- // ============================================
2668
- // Namespace API
2669
- // ============================================
2670
- namespace(name, id) {
2671
- const newPrefix = this.prefix + buildPrefix(name, id);
2672
- return new _NamespacedStorageImpl(this.adapter, newPrefix, this.root);
2924
+ /**
2925
+ * Clear the in-memory cache.
2926
+ *
2927
+ * Useful when you want to force a reload from storage.
2928
+ */
2929
+ clearCache() {
2930
+ this.cache.clear();
2931
+ }
2932
+ /**
2933
+ * Clear cache for a specific key.
2934
+ *
2935
+ * @param kid - Key identifier
2936
+ */
2937
+ clearCacheFor(kid) {
2938
+ this.cache.delete(kid);
2939
+ }
2940
+ /**
2941
+ * Check if a key is in the cache.
2942
+ *
2943
+ * @param kid - Key identifier
2944
+ * @returns true if key is cached
2945
+ */
2946
+ isCached(kid) {
2947
+ return this.cache.has(kid);
2948
+ }
2949
+ /**
2950
+ * Get the underlying storage adapter.
2951
+ *
2952
+ * Use with caution - direct storage access bypasses validation.
2953
+ */
2954
+ getAdapter() {
2955
+ return this.storage;
2956
+ }
2957
+ };
2958
+
2959
+ // libs/utils/src/storage/adapters/memory.ts
2960
+ var import_events = require("events");
2961
+ init_base();
2962
+ init_errors();
2963
+ init_pattern();
2964
+ init_ttl();
2965
+ var DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
2966
+ var MAX_TIMEOUT_MS = 2147483647;
2967
+ var MemoryStorageAdapter = class extends BaseStorageAdapter {
2968
+ backendName = "memory";
2969
+ store = /* @__PURE__ */ new Map();
2970
+ emitter = new import_events.EventEmitter();
2971
+ sweepInterval;
2972
+ options;
2973
+ // LRU tracking (simple linked list via insertion order in Map)
2974
+ accessOrder = [];
2975
+ constructor(options = {}) {
2976
+ super();
2977
+ this.options = {
2978
+ enableSweeper: options.enableSweeper ?? true,
2979
+ sweepIntervalSeconds: options.sweepIntervalSeconds ?? DEFAULT_SWEEP_INTERVAL_SECONDS,
2980
+ maxEntries: options.maxEntries ?? 0
2981
+ // 0 = unlimited
2982
+ };
2983
+ this.emitter.setMaxListeners(1e3);
2673
2984
  }
2674
2985
  // ============================================
2675
- // Connection Lifecycle (delegated to root)
2986
+ // Connection Lifecycle
2676
2987
  // ============================================
2677
2988
  async connect() {
2678
- return this.adapter.connect();
2989
+ if (this.connected) return;
2990
+ this.connected = true;
2991
+ if (this.options.enableSweeper && this.options.sweepIntervalSeconds > 0) {
2992
+ this.startSweeper();
2993
+ }
2679
2994
  }
2680
2995
  async disconnect() {
2681
- return this.adapter.disconnect();
2996
+ if (!this.connected) return;
2997
+ this.stopSweeper();
2998
+ this.clearAllTimeouts();
2999
+ this.store.clear();
3000
+ this.accessOrder = [];
3001
+ this.emitter.removeAllListeners();
3002
+ this.connected = false;
2682
3003
  }
2683
3004
  async ping() {
2684
- return this.adapter.ping();
3005
+ return this.connected;
2685
3006
  }
2686
3007
  // ============================================
2687
- // Core Operations (with prefixing)
2688
- // ============================================
2689
- async get(key) {
2690
- return this.adapter.get(this.prefixKey(key));
2691
- }
2692
- async set(key, value, options) {
2693
- return this.adapter.set(this.prefixKey(key), value, options);
2694
- }
2695
- async delete(key) {
2696
- return this.adapter.delete(this.prefixKey(key));
2697
- }
2698
- async exists(key) {
2699
- return this.adapter.exists(this.prefixKey(key));
2700
- }
2701
- // ============================================
2702
- // Batch Operations (with prefixing)
2703
- // ============================================
2704
- async mget(keys) {
2705
- const prefixedKeys = keys.map((k) => this.prefixKey(k));
2706
- return this.adapter.mget(prefixedKeys);
2707
- }
2708
- async mset(entries) {
2709
- const prefixedEntries = entries.map((e) => ({
2710
- ...e,
2711
- key: this.prefixKey(e.key)
2712
- }));
2713
- return this.adapter.mset(prefixedEntries);
2714
- }
2715
- async mdelete(keys) {
2716
- const prefixedKeys = keys.map((k) => this.prefixKey(k));
2717
- return this.adapter.mdelete(prefixedKeys);
2718
- }
2719
- // ============================================
2720
- // TTL Operations (with prefixing)
2721
- // ============================================
2722
- async expire(key, ttlSeconds) {
2723
- return this.adapter.expire(this.prefixKey(key), ttlSeconds);
2724
- }
2725
- async ttl(key) {
2726
- return this.adapter.ttl(this.prefixKey(key));
2727
- }
2728
- // ============================================
2729
- // Key Enumeration (with prefixing)
2730
- // ============================================
2731
- async keys(pattern = "*") {
2732
- const prefixedPattern = this.prefixPattern(pattern);
2733
- const keys = await this.adapter.keys(prefixedPattern);
2734
- return keys.map((k) => this.unprefixKey(k));
2735
- }
2736
- async count(pattern = "*") {
2737
- const prefixedPattern = this.prefixPattern(pattern);
2738
- return this.adapter.count(prefixedPattern);
2739
- }
2740
- // ============================================
2741
- // Atomic Operations (with prefixing)
2742
- // ============================================
2743
- async incr(key) {
2744
- return this.adapter.incr(this.prefixKey(key));
2745
- }
2746
- async decr(key) {
2747
- return this.adapter.decr(this.prefixKey(key));
2748
- }
2749
- async incrBy(key, amount) {
2750
- return this.adapter.incrBy(this.prefixKey(key), amount);
2751
- }
2752
- // ============================================
2753
- // Pub/Sub (with channel prefixing)
2754
- // ============================================
2755
- async publish(channel, message) {
2756
- return this.adapter.publish(this.prefixChannel(channel), message);
2757
- }
2758
- async subscribe(channel, handler) {
2759
- const prefixedChannel = this.prefixChannel(channel);
2760
- const wrappedHandler = (message, ch) => {
2761
- const unprefixedChannel = ch.startsWith(this.prefix) ? ch.slice(this.prefix.length) : ch;
2762
- handler(message, unprefixedChannel);
2763
- };
2764
- return this.adapter.subscribe(prefixedChannel, wrappedHandler);
2765
- }
2766
- supportsPubSub() {
2767
- return this.adapter.supportsPubSub();
2768
- }
2769
- };
2770
- function createRootStorage(adapter) {
2771
- return new NamespacedStorageImpl(adapter, "", adapter);
2772
- }
2773
- function createNamespacedStorage(adapter, prefix) {
2774
- const normalizedPrefix = prefix && !prefix.endsWith(NAMESPACE_SEPARATOR) ? prefix + NAMESPACE_SEPARATOR : prefix;
2775
- return new NamespacedStorageImpl(adapter, normalizedPrefix, adapter);
2776
- }
2777
-
2778
- // libs/utils/src/storage/adapters/memory.ts
2779
- var import_events = require("events");
2780
- init_base();
2781
- init_errors();
2782
-
2783
- // libs/utils/src/storage/utils/pattern.ts
2784
- init_errors();
2785
- var MAX_PATTERN_LENGTH = 500;
2786
- var MAX_WILDCARDS = 20;
2787
- var REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
2788
- function globToRegex(pattern) {
2789
- if (pattern.length > MAX_PATTERN_LENGTH) {
2790
- throw new StoragePatternError(
2791
- pattern.substring(0, 50) + "...",
2792
- `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
2793
- );
2794
- }
2795
- const wildcardCount = (pattern.match(/[*?]/g) || []).length;
2796
- if (wildcardCount > MAX_WILDCARDS) {
2797
- throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
2798
- }
2799
- if (pattern === "" || pattern === "*") {
2800
- return /^.*$/;
2801
- }
2802
- let regexStr = "^";
2803
- let prevChar = "";
2804
- for (let i = 0; i < pattern.length; i++) {
2805
- const char = pattern[i];
2806
- switch (char) {
2807
- case "*":
2808
- if (prevChar !== "*") {
2809
- regexStr += ".*";
2810
- }
2811
- break;
2812
- case "?":
2813
- regexStr += ".";
2814
- break;
2815
- default:
2816
- regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
2817
- }
2818
- prevChar = char;
2819
- }
2820
- regexStr += "$";
2821
- try {
2822
- return new RegExp(regexStr);
2823
- } catch {
2824
- throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
2825
- }
2826
- }
2827
- function matchesPattern(key, pattern) {
2828
- const regex = globToRegex(pattern);
2829
- return regex.test(key);
2830
- }
2831
- function validatePattern(pattern) {
2832
- try {
2833
- globToRegex(pattern);
2834
- return { valid: true };
2835
- } catch (e) {
2836
- return {
2837
- valid: false,
2838
- error: e instanceof Error ? e.message : "Invalid pattern"
2839
- };
2840
- }
2841
- }
2842
- function escapeGlob(literal) {
2843
- return literal.replace(/[*?\\]/g, "\\$&");
2844
- }
2845
-
2846
- // libs/utils/src/storage/adapters/memory.ts
2847
- init_ttl();
2848
- var DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
2849
- var MAX_TIMEOUT_MS = 2147483647;
2850
- var MemoryStorageAdapter = class extends BaseStorageAdapter {
2851
- backendName = "memory";
2852
- store = /* @__PURE__ */ new Map();
2853
- emitter = new import_events.EventEmitter();
2854
- sweepInterval;
2855
- options;
2856
- // LRU tracking (simple linked list via insertion order in Map)
2857
- accessOrder = [];
2858
- constructor(options = {}) {
2859
- super();
2860
- this.options = {
2861
- enableSweeper: options.enableSweeper ?? true,
2862
- sweepIntervalSeconds: options.sweepIntervalSeconds ?? DEFAULT_SWEEP_INTERVAL_SECONDS,
2863
- maxEntries: options.maxEntries ?? 0
2864
- // 0 = unlimited
2865
- };
2866
- this.emitter.setMaxListeners(1e3);
2867
- }
2868
- // ============================================
2869
- // Connection Lifecycle
2870
- // ============================================
2871
- async connect() {
2872
- if (this.connected) return;
2873
- this.connected = true;
2874
- if (this.options.enableSweeper && this.options.sweepIntervalSeconds > 0) {
2875
- this.startSweeper();
2876
- }
2877
- }
2878
- async disconnect() {
2879
- if (!this.connected) return;
2880
- this.stopSweeper();
2881
- this.clearAllTimeouts();
2882
- this.store.clear();
2883
- this.accessOrder = [];
2884
- this.emitter.removeAllListeners();
2885
- this.connected = false;
2886
- }
2887
- async ping() {
2888
- return this.connected;
2889
- }
2890
- // ============================================
2891
- // Core Operations
3008
+ // Core Operations
2892
3009
  // ============================================
2893
3010
  async get(key) {
2894
3011
  this.ensureConnected();
@@ -3168,42 +3285,624 @@ var MemoryStorageAdapter = class extends BaseStorageAdapter {
3168
3285
  }
3169
3286
  };
3170
3287
 
3171
- // libs/utils/src/storage/factory.ts
3172
- function isProduction() {
3173
- return process.env["NODE_ENV"] === "production";
3174
- }
3175
- function detectStorageType() {
3176
- if (process.env["UPSTASH_REDIS_REST_URL"] && process.env["UPSTASH_REDIS_REST_TOKEN"]) {
3177
- return "upstash";
3288
+ // libs/utils/src/storage/adapters/filesystem.ts
3289
+ init_base();
3290
+ init_errors();
3291
+ init_pattern();
3292
+ var FileSystemStorageAdapter = class extends BaseStorageAdapter {
3293
+ backendName = "filesystem";
3294
+ baseDir;
3295
+ extension;
3296
+ dirMode;
3297
+ fileMode;
3298
+ constructor(options) {
3299
+ super();
3300
+ this.baseDir = options.baseDir;
3301
+ this.extension = options.extension ?? ".json";
3302
+ this.dirMode = options.dirMode ?? 448;
3303
+ this.fileMode = options.fileMode ?? 384;
3178
3304
  }
3179
- if (process.env["KV_REST_API_URL"] && process.env["KV_REST_API_TOKEN"]) {
3180
- return "vercel-kv";
3305
+ // ============================================
3306
+ // Connection Lifecycle
3307
+ // ============================================
3308
+ async connect() {
3309
+ if (this.connected) return;
3310
+ await ensureDir(this.baseDir);
3311
+ try {
3312
+ await mkdir(this.baseDir, { recursive: true, mode: this.dirMode });
3313
+ } catch {
3314
+ }
3315
+ this.connected = true;
3181
3316
  }
3182
- if (process.env["REDIS_URL"] || process.env["REDIS_HOST"]) {
3183
- return "redis";
3317
+ async disconnect() {
3318
+ if (!this.connected) return;
3319
+ this.connected = false;
3184
3320
  }
3185
- return "memory";
3186
- }
3187
- async function createAdapter(type, config) {
3188
- switch (type) {
3189
- case "memory": {
3190
- return new MemoryStorageAdapter(config.memory);
3321
+ async ping() {
3322
+ if (!this.connected) return false;
3323
+ try {
3324
+ return await fileExists(this.baseDir);
3325
+ } catch {
3326
+ return false;
3191
3327
  }
3192
- case "redis": {
3193
- const { RedisStorageAdapter: RedisStorageAdapter2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
3194
- return new RedisStorageAdapter2(config.redis);
3328
+ }
3329
+ // ============================================
3330
+ // Core Operations
3331
+ // ============================================
3332
+ async get(key) {
3333
+ this.ensureConnected();
3334
+ const filePath = this.keyToPath(key);
3335
+ try {
3336
+ const entry = await readJSON(filePath);
3337
+ if (!entry) return null;
3338
+ if (entry.expiresAt && Date.now() >= entry.expiresAt) {
3339
+ await this.deleteFile(filePath);
3340
+ return null;
3341
+ }
3342
+ return entry.value;
3343
+ } catch (e) {
3344
+ const error = e;
3345
+ if (error.code === "ENOENT") {
3346
+ return null;
3347
+ }
3348
+ throw e;
3195
3349
  }
3196
- case "vercel-kv": {
3197
- const { VercelKvStorageAdapter: VercelKvStorageAdapter2 } = await Promise.resolve().then(() => (init_vercel_kv(), vercel_kv_exports));
3198
- return new VercelKvStorageAdapter2(config.vercelKv);
3350
+ }
3351
+ async doSet(key, value, options) {
3352
+ const filePath = this.keyToPath(key);
3353
+ const existingEntry = await this.readEntry(filePath);
3354
+ const exists = existingEntry !== null && !this.isExpired(existingEntry);
3355
+ if (options?.ifNotExists && exists) {
3356
+ return;
3199
3357
  }
3200
- case "upstash": {
3201
- const { UpstashStorageAdapter: UpstashStorageAdapter2 } = await Promise.resolve().then(() => (init_upstash(), upstash_exports));
3202
- return new UpstashStorageAdapter2(config.upstash);
3358
+ if (options?.ifExists && !exists) {
3359
+ return;
3203
3360
  }
3204
- case "auto":
3205
- throw new StorageConfigError("auto", "Auto type should be resolved before calling createAdapter");
3206
- default:
3361
+ const entry = { value };
3362
+ if (options?.ttlSeconds) {
3363
+ entry.expiresAt = Date.now() + options.ttlSeconds * 1e3;
3364
+ }
3365
+ await this.atomicWrite(filePath, entry);
3366
+ }
3367
+ async delete(key) {
3368
+ this.ensureConnected();
3369
+ const filePath = this.keyToPath(key);
3370
+ return this.deleteFile(filePath);
3371
+ }
3372
+ async exists(key) {
3373
+ this.ensureConnected();
3374
+ const filePath = this.keyToPath(key);
3375
+ try {
3376
+ const entry = await this.readEntry(filePath);
3377
+ if (!entry) return false;
3378
+ if (this.isExpired(entry)) {
3379
+ await this.deleteFile(filePath);
3380
+ return false;
3381
+ }
3382
+ return true;
3383
+ } catch {
3384
+ return false;
3385
+ }
3386
+ }
3387
+ // ============================================
3388
+ // TTL Operations
3389
+ // ============================================
3390
+ async expire(key, ttlSeconds) {
3391
+ this.ensureConnected();
3392
+ const filePath = this.keyToPath(key);
3393
+ const entry = await this.readEntry(filePath);
3394
+ if (!entry || this.isExpired(entry)) {
3395
+ return false;
3396
+ }
3397
+ entry.expiresAt = Date.now() + ttlSeconds * 1e3;
3398
+ await this.atomicWrite(filePath, entry);
3399
+ return true;
3400
+ }
3401
+ async ttl(key) {
3402
+ this.ensureConnected();
3403
+ const filePath = this.keyToPath(key);
3404
+ const entry = await this.readEntry(filePath);
3405
+ if (!entry) return null;
3406
+ if (this.isExpired(entry)) {
3407
+ await this.deleteFile(filePath);
3408
+ return null;
3409
+ }
3410
+ if (entry.expiresAt === void 0) {
3411
+ return -1;
3412
+ }
3413
+ return Math.max(0, Math.ceil((entry.expiresAt - Date.now()) / 1e3));
3414
+ }
3415
+ // ============================================
3416
+ // Key Enumeration
3417
+ // ============================================
3418
+ async keys(pattern = "*") {
3419
+ this.ensureConnected();
3420
+ const regex = globToRegex(pattern);
3421
+ const result = [];
3422
+ try {
3423
+ const files = await readdir(this.baseDir);
3424
+ for (const file of files) {
3425
+ if (!file.endsWith(this.extension)) continue;
3426
+ const key = this.pathToKey(file);
3427
+ if (!regex.test(key)) continue;
3428
+ const filePath = this.keyToPath(key);
3429
+ const entry = await this.readEntry(filePath);
3430
+ if (entry && !this.isExpired(entry)) {
3431
+ result.push(key);
3432
+ } else if (entry && this.isExpired(entry)) {
3433
+ await this.deleteFile(filePath);
3434
+ }
3435
+ }
3436
+ } catch (e) {
3437
+ const error = e;
3438
+ if (error.code === "ENOENT") {
3439
+ return [];
3440
+ }
3441
+ throw e;
3442
+ }
3443
+ return result;
3444
+ }
3445
+ // ============================================
3446
+ // Atomic Operations
3447
+ // ============================================
3448
+ async incr(key) {
3449
+ return this.incrBy(key, 1);
3450
+ }
3451
+ async decr(key) {
3452
+ return this.incrBy(key, -1);
3453
+ }
3454
+ async incrBy(key, amount) {
3455
+ this.ensureConnected();
3456
+ const filePath = this.keyToPath(key);
3457
+ const entry = await this.readEntry(filePath);
3458
+ let currentValue = 0;
3459
+ if (entry && !this.isExpired(entry)) {
3460
+ const parsed = parseInt(entry.value, 10);
3461
+ if (isNaN(parsed)) {
3462
+ throw new StorageOperationError("incrBy", key, "Value is not an integer");
3463
+ }
3464
+ currentValue = parsed;
3465
+ }
3466
+ const newValue = currentValue + amount;
3467
+ const newEntry = { value: String(newValue) };
3468
+ if (entry?.expiresAt && !this.isExpired(entry)) {
3469
+ newEntry.expiresAt = entry.expiresAt;
3470
+ }
3471
+ await this.atomicWrite(filePath, newEntry);
3472
+ return newValue;
3473
+ }
3474
+ // ============================================
3475
+ // Internal Helpers
3476
+ // ============================================
3477
+ /**
3478
+ * Convert a storage key to a file path.
3479
+ * Sanitizes the key to be filesystem-safe.
3480
+ */
3481
+ keyToPath(key) {
3482
+ const safeKey = this.encodeKey(key);
3483
+ return `${this.baseDir}/${safeKey}${this.extension}`;
3484
+ }
3485
+ /**
3486
+ * Convert a filename back to a storage key.
3487
+ */
3488
+ pathToKey(filename) {
3489
+ const safeKey = filename.slice(0, -this.extension.length);
3490
+ return this.decodeKey(safeKey);
3491
+ }
3492
+ /**
3493
+ * Encode a key to be filesystem-safe.
3494
+ * Uses URL encoding to handle special characters.
3495
+ */
3496
+ encodeKey(key) {
3497
+ return encodeURIComponent(key).replace(/\./g, "%2E");
3498
+ }
3499
+ /**
3500
+ * Decode a filesystem-safe key back to original.
3501
+ */
3502
+ decodeKey(encoded) {
3503
+ return decodeURIComponent(encoded);
3504
+ }
3505
+ /**
3506
+ * Read an entry from a file.
3507
+ */
3508
+ async readEntry(filePath) {
3509
+ try {
3510
+ return await readJSON(filePath);
3511
+ } catch {
3512
+ return null;
3513
+ }
3514
+ }
3515
+ /**
3516
+ * Check if an entry is expired.
3517
+ */
3518
+ isExpired(entry) {
3519
+ return entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt;
3520
+ }
3521
+ /**
3522
+ * Delete a file, returning true if it existed.
3523
+ */
3524
+ async deleteFile(filePath) {
3525
+ try {
3526
+ await unlink(filePath);
3527
+ return true;
3528
+ } catch (e) {
3529
+ const error = e;
3530
+ if (error.code === "ENOENT") {
3531
+ return false;
3532
+ }
3533
+ throw e;
3534
+ }
3535
+ }
3536
+ /**
3537
+ * Atomic write: write to temp file then rename.
3538
+ * This ensures we don't corrupt the file if write is interrupted.
3539
+ */
3540
+ async atomicWrite(filePath, entry) {
3541
+ const tempSuffix = bytesToHex(randomBytes(8));
3542
+ const tempPath = `${filePath}.${tempSuffix}.tmp`;
3543
+ try {
3544
+ const content = JSON.stringify(entry, null, 2);
3545
+ await writeFile(tempPath, content, { mode: this.fileMode });
3546
+ await rename(tempPath, filePath);
3547
+ } catch (e) {
3548
+ try {
3549
+ await unlink(tempPath);
3550
+ } catch {
3551
+ }
3552
+ throw e;
3553
+ }
3554
+ }
3555
+ /**
3556
+ * Get suggestion message for pub/sub not supported error.
3557
+ */
3558
+ getPubSubSuggestion() {
3559
+ return "FileSystem adapter does not support pub/sub. Use Redis or Memory adapter for pub/sub support.";
3560
+ }
3561
+ };
3562
+
3563
+ // libs/utils/src/crypto/key-persistence/factory.ts
3564
+ var DEFAULT_BASE_DIR = ".frontmcp/keys";
3565
+ async function createKeyPersistence(options) {
3566
+ const type = options?.type ?? "auto";
3567
+ const baseDir = options?.baseDir ?? DEFAULT_BASE_DIR;
3568
+ let adapter;
3569
+ if (type === "memory") {
3570
+ adapter = new MemoryStorageAdapter();
3571
+ } else if (type === "filesystem") {
3572
+ adapter = new FileSystemStorageAdapter({ baseDir });
3573
+ } else {
3574
+ if (isNode()) {
3575
+ adapter = new FileSystemStorageAdapter({ baseDir });
3576
+ } else {
3577
+ adapter = new MemoryStorageAdapter();
3578
+ }
3579
+ }
3580
+ await adapter.connect();
3581
+ return new KeyPersistence({
3582
+ storage: adapter,
3583
+ throwOnInvalid: options?.throwOnInvalid ?? false,
3584
+ enableCache: options?.enableCache ?? true
3585
+ });
3586
+ }
3587
+ function createKeyPersistenceWithStorage(storage, options) {
3588
+ return new KeyPersistence({
3589
+ storage,
3590
+ throwOnInvalid: options?.throwOnInvalid ?? false,
3591
+ enableCache: options?.enableCache ?? true
3592
+ });
3593
+ }
3594
+
3595
+ // libs/utils/src/crypto/index.ts
3596
+ var _provider = null;
3597
+ function getCrypto() {
3598
+ if (!_provider) {
3599
+ if (isNode()) {
3600
+ _provider = (init_node(), __toCommonJS(node_exports)).nodeCrypto;
3601
+ } else {
3602
+ _provider = (init_browser(), __toCommonJS(browser_exports)).browserCrypto;
3603
+ }
3604
+ }
3605
+ return _provider;
3606
+ }
3607
+ function rsaVerify2(jwtAlg, data, publicJwk, signature) {
3608
+ assertNode("rsaVerify");
3609
+ return (init_node(), __toCommonJS(node_exports)).rsaVerify(jwtAlg, data, publicJwk, signature);
3610
+ }
3611
+ function randomUUID() {
3612
+ return getCrypto().randomUUID();
3613
+ }
3614
+ function randomBytes(length) {
3615
+ if (!Number.isInteger(length) || length <= 0) {
3616
+ throw new Error(`randomBytes length must be a positive integer, got ${length}`);
3617
+ }
3618
+ return getCrypto().randomBytes(length);
3619
+ }
3620
+ function sha256(data) {
3621
+ return getCrypto().sha256(data);
3622
+ }
3623
+ function sha256Hex(data) {
3624
+ return getCrypto().sha256Hex(data);
3625
+ }
3626
+ function hmacSha256(key, data) {
3627
+ return getCrypto().hmacSha256(key, data);
3628
+ }
3629
+ var HKDF_SHA256_MAX_LENGTH = 255 * 32;
3630
+ function hkdfSha256(ikm, salt, info, length) {
3631
+ if (!Number.isInteger(length) || length <= 0) {
3632
+ throw new Error(`HKDF length must be a positive integer, got ${length}`);
3633
+ }
3634
+ if (length > HKDF_SHA256_MAX_LENGTH) {
3635
+ throw new Error(`HKDF-SHA256 length cannot exceed ${HKDF_SHA256_MAX_LENGTH} bytes, got ${length}`);
3636
+ }
3637
+ return getCrypto().hkdfSha256(ikm, salt, info, length);
3638
+ }
3639
+ function encryptAesGcm(key, plaintext, iv) {
3640
+ if (key.length !== 32) {
3641
+ throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
3642
+ }
3643
+ if (iv.length !== 12) {
3644
+ throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
3645
+ }
3646
+ return getCrypto().encryptAesGcm(key, plaintext, iv);
3647
+ }
3648
+ function decryptAesGcm(key, ciphertext, iv, tag) {
3649
+ if (key.length !== 32) {
3650
+ throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
3651
+ }
3652
+ if (iv.length !== 12) {
3653
+ throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
3654
+ }
3655
+ if (tag.length !== 16) {
3656
+ throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`);
3657
+ }
3658
+ return getCrypto().decryptAesGcm(key, ciphertext, iv, tag);
3659
+ }
3660
+ function timingSafeEqual(a, b) {
3661
+ if (a.length !== b.length) {
3662
+ throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`);
3663
+ }
3664
+ return getCrypto().timingSafeEqual(a, b);
3665
+ }
3666
+ function bytesToHex(data) {
3667
+ return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
3668
+ }
3669
+ function base64urlEncode(data) {
3670
+ let base64;
3671
+ if (typeof Buffer !== "undefined") {
3672
+ base64 = Buffer.from(data).toString("base64");
3673
+ } else {
3674
+ const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
3675
+ base64 = btoa(binString);
3676
+ }
3677
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3678
+ }
3679
+ function base64urlDecode(data) {
3680
+ let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
3681
+ const pad = base64.length % 4;
3682
+ if (pad) {
3683
+ base64 += "=".repeat(4 - pad);
3684
+ }
3685
+ if (typeof Buffer !== "undefined") {
3686
+ return new Uint8Array(Buffer.from(base64, "base64"));
3687
+ } else {
3688
+ const binString = atob(base64);
3689
+ return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
3690
+ }
3691
+ }
3692
+ function base64Encode(data) {
3693
+ if (typeof Buffer !== "undefined") {
3694
+ return Buffer.from(data).toString("base64");
3695
+ } else {
3696
+ const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
3697
+ return btoa(binString);
3698
+ }
3699
+ }
3700
+ function base64Decode(data) {
3701
+ if (typeof Buffer !== "undefined") {
3702
+ return new Uint8Array(Buffer.from(data, "base64"));
3703
+ } else {
3704
+ const binString = atob(data);
3705
+ return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
3706
+ }
3707
+ }
3708
+ function sha256Base64url(data) {
3709
+ return base64urlEncode(sha256(data));
3710
+ }
3711
+
3712
+ // libs/utils/src/storage/factory.ts
3713
+ init_errors();
3714
+
3715
+ // libs/utils/src/storage/namespace.ts
3716
+ var NAMESPACE_SEPARATOR = ":";
3717
+ function buildPrefix(name, id) {
3718
+ if (id) {
3719
+ return `${name}${NAMESPACE_SEPARATOR}${id}${NAMESPACE_SEPARATOR}`;
3720
+ }
3721
+ return `${name}${NAMESPACE_SEPARATOR}`;
3722
+ }
3723
+ var NamespacedStorageImpl = class _NamespacedStorageImpl {
3724
+ constructor(adapter, prefix = "", root = adapter) {
3725
+ this.adapter = adapter;
3726
+ this.prefix = prefix;
3727
+ this.root = root;
3728
+ }
3729
+ // ============================================
3730
+ // Key Prefixing Helpers
3731
+ // ============================================
3732
+ /**
3733
+ * Add prefix to a key.
3734
+ */
3735
+ prefixKey(key) {
3736
+ return this.prefix + key;
3737
+ }
3738
+ /**
3739
+ * Remove prefix from a key.
3740
+ */
3741
+ unprefixKey(key) {
3742
+ if (this.prefix && key.startsWith(this.prefix)) {
3743
+ return key.slice(this.prefix.length);
3744
+ }
3745
+ return key;
3746
+ }
3747
+ /**
3748
+ * Add prefix to a pattern (for keys() operation).
3749
+ */
3750
+ prefixPattern(pattern) {
3751
+ return this.prefix + pattern;
3752
+ }
3753
+ /**
3754
+ * Add prefix to a channel (for pub/sub).
3755
+ */
3756
+ prefixChannel(channel) {
3757
+ return this.prefix + channel;
3758
+ }
3759
+ // ============================================
3760
+ // Namespace API
3761
+ // ============================================
3762
+ namespace(name, id) {
3763
+ const newPrefix = this.prefix + buildPrefix(name, id);
3764
+ return new _NamespacedStorageImpl(this.adapter, newPrefix, this.root);
3765
+ }
3766
+ // ============================================
3767
+ // Connection Lifecycle (delegated to root)
3768
+ // ============================================
3769
+ async connect() {
3770
+ return this.adapter.connect();
3771
+ }
3772
+ async disconnect() {
3773
+ return this.adapter.disconnect();
3774
+ }
3775
+ async ping() {
3776
+ return this.adapter.ping();
3777
+ }
3778
+ // ============================================
3779
+ // Core Operations (with prefixing)
3780
+ // ============================================
3781
+ async get(key) {
3782
+ return this.adapter.get(this.prefixKey(key));
3783
+ }
3784
+ async set(key, value, options) {
3785
+ return this.adapter.set(this.prefixKey(key), value, options);
3786
+ }
3787
+ async delete(key) {
3788
+ return this.adapter.delete(this.prefixKey(key));
3789
+ }
3790
+ async exists(key) {
3791
+ return this.adapter.exists(this.prefixKey(key));
3792
+ }
3793
+ // ============================================
3794
+ // Batch Operations (with prefixing)
3795
+ // ============================================
3796
+ async mget(keys) {
3797
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
3798
+ return this.adapter.mget(prefixedKeys);
3799
+ }
3800
+ async mset(entries) {
3801
+ const prefixedEntries = entries.map((e) => ({
3802
+ ...e,
3803
+ key: this.prefixKey(e.key)
3804
+ }));
3805
+ return this.adapter.mset(prefixedEntries);
3806
+ }
3807
+ async mdelete(keys) {
3808
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
3809
+ return this.adapter.mdelete(prefixedKeys);
3810
+ }
3811
+ // ============================================
3812
+ // TTL Operations (with prefixing)
3813
+ // ============================================
3814
+ async expire(key, ttlSeconds) {
3815
+ return this.adapter.expire(this.prefixKey(key), ttlSeconds);
3816
+ }
3817
+ async ttl(key) {
3818
+ return this.adapter.ttl(this.prefixKey(key));
3819
+ }
3820
+ // ============================================
3821
+ // Key Enumeration (with prefixing)
3822
+ // ============================================
3823
+ async keys(pattern = "*") {
3824
+ const prefixedPattern = this.prefixPattern(pattern);
3825
+ const keys = await this.adapter.keys(prefixedPattern);
3826
+ return keys.map((k) => this.unprefixKey(k));
3827
+ }
3828
+ async count(pattern = "*") {
3829
+ const prefixedPattern = this.prefixPattern(pattern);
3830
+ return this.adapter.count(prefixedPattern);
3831
+ }
3832
+ // ============================================
3833
+ // Atomic Operations (with prefixing)
3834
+ // ============================================
3835
+ async incr(key) {
3836
+ return this.adapter.incr(this.prefixKey(key));
3837
+ }
3838
+ async decr(key) {
3839
+ return this.adapter.decr(this.prefixKey(key));
3840
+ }
3841
+ async incrBy(key, amount) {
3842
+ return this.adapter.incrBy(this.prefixKey(key), amount);
3843
+ }
3844
+ // ============================================
3845
+ // Pub/Sub (with channel prefixing)
3846
+ // ============================================
3847
+ async publish(channel, message) {
3848
+ return this.adapter.publish(this.prefixChannel(channel), message);
3849
+ }
3850
+ async subscribe(channel, handler) {
3851
+ const prefixedChannel = this.prefixChannel(channel);
3852
+ const wrappedHandler = (message, ch) => {
3853
+ const unprefixedChannel = ch.startsWith(this.prefix) ? ch.slice(this.prefix.length) : ch;
3854
+ handler(message, unprefixedChannel);
3855
+ };
3856
+ return this.adapter.subscribe(prefixedChannel, wrappedHandler);
3857
+ }
3858
+ supportsPubSub() {
3859
+ return this.adapter.supportsPubSub();
3860
+ }
3861
+ };
3862
+ function createRootStorage(adapter) {
3863
+ return new NamespacedStorageImpl(adapter, "", adapter);
3864
+ }
3865
+ function createNamespacedStorage(adapter, prefix) {
3866
+ const normalizedPrefix = prefix && !prefix.endsWith(NAMESPACE_SEPARATOR) ? prefix + NAMESPACE_SEPARATOR : prefix;
3867
+ return new NamespacedStorageImpl(adapter, normalizedPrefix, adapter);
3868
+ }
3869
+
3870
+ // libs/utils/src/storage/factory.ts
3871
+ function isProduction() {
3872
+ return process.env["NODE_ENV"] === "production";
3873
+ }
3874
+ function detectStorageType() {
3875
+ if (process.env["UPSTASH_REDIS_REST_URL"] && process.env["UPSTASH_REDIS_REST_TOKEN"]) {
3876
+ return "upstash";
3877
+ }
3878
+ if (process.env["KV_REST_API_URL"] && process.env["KV_REST_API_TOKEN"]) {
3879
+ return "vercel-kv";
3880
+ }
3881
+ if (process.env["REDIS_URL"] || process.env["REDIS_HOST"]) {
3882
+ return "redis";
3883
+ }
3884
+ return "memory";
3885
+ }
3886
+ async function createAdapter(type, config) {
3887
+ switch (type) {
3888
+ case "memory": {
3889
+ return new MemoryStorageAdapter(config.memory);
3890
+ }
3891
+ case "redis": {
3892
+ const { RedisStorageAdapter: RedisStorageAdapter2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
3893
+ return new RedisStorageAdapter2(config.redis);
3894
+ }
3895
+ case "vercel-kv": {
3896
+ const { VercelKvStorageAdapter: VercelKvStorageAdapter2 } = await Promise.resolve().then(() => (init_vercel_kv(), vercel_kv_exports));
3897
+ return new VercelKvStorageAdapter2(config.vercelKv);
3898
+ }
3899
+ case "upstash": {
3900
+ const { UpstashStorageAdapter: UpstashStorageAdapter2 } = await Promise.resolve().then(() => (init_upstash(), upstash_exports));
3901
+ return new UpstashStorageAdapter2(config.upstash);
3902
+ }
3903
+ case "auto":
3904
+ throw new StorageConfigError("auto", "Auto type should be resolved before calling createAdapter");
3905
+ default:
3207
3906
  throw new StorageConfigError("unknown", `Unknown storage type: ${type}`);
3208
3907
  }
3209
3908
  }
@@ -3263,6 +3962,501 @@ function getDetectedStorageType() {
3263
3962
  // libs/utils/src/storage/index.ts
3264
3963
  init_errors();
3265
3964
 
3965
+ // libs/utils/src/storage/typed-storage.ts
3966
+ var TypedStorage = class {
3967
+ storage;
3968
+ serialize;
3969
+ deserialize;
3970
+ schema;
3971
+ throwOnInvalid;
3972
+ constructor(storage, options = {}) {
3973
+ this.storage = storage;
3974
+ this.serialize = options.serialize ?? JSON.stringify;
3975
+ this.deserialize = options.deserialize ?? JSON.parse;
3976
+ this.schema = options.schema;
3977
+ this.throwOnInvalid = options.throwOnInvalid ?? false;
3978
+ }
3979
+ /**
3980
+ * Get a typed value by key.
3981
+ *
3982
+ * @param key - Storage key
3983
+ * @returns The typed value, or null if not found or invalid
3984
+ */
3985
+ async get(key) {
3986
+ const raw = await this.storage.get(key);
3987
+ if (raw === null) {
3988
+ return null;
3989
+ }
3990
+ return this.parseValue(raw, key);
3991
+ }
3992
+ /**
3993
+ * Set a typed value with optional TTL.
3994
+ *
3995
+ * @param key - Storage key
3996
+ * @param value - Typed value to store
3997
+ * @param options - Optional TTL and conditional flags
3998
+ */
3999
+ async set(key, value, options) {
4000
+ const serialized = this.serialize(value);
4001
+ await this.storage.set(key, serialized, options);
4002
+ }
4003
+ /**
4004
+ * Delete a key.
4005
+ *
4006
+ * @param key - Storage key
4007
+ * @returns true if key existed and was deleted
4008
+ */
4009
+ async delete(key) {
4010
+ return this.storage.delete(key);
4011
+ }
4012
+ /**
4013
+ * Check if a key exists.
4014
+ *
4015
+ * @param key - Storage key
4016
+ * @returns true if key exists
4017
+ */
4018
+ async exists(key) {
4019
+ return this.storage.exists(key);
4020
+ }
4021
+ /**
4022
+ * Get multiple typed values.
4023
+ *
4024
+ * @param keys - Array of storage keys
4025
+ * @returns Array of typed values (null for missing/invalid keys)
4026
+ */
4027
+ async mget(keys) {
4028
+ if (keys.length === 0) {
4029
+ return [];
4030
+ }
4031
+ const rawValues = await this.storage.mget(keys);
4032
+ return rawValues.map((raw, index) => {
4033
+ if (raw === null) {
4034
+ return null;
4035
+ }
4036
+ return this.parseValue(raw, keys[index]);
4037
+ });
4038
+ }
4039
+ /**
4040
+ * Set multiple typed values.
4041
+ *
4042
+ * @param entries - Array of key-value-options entries
4043
+ */
4044
+ async mset(entries) {
4045
+ if (entries.length === 0) {
4046
+ return;
4047
+ }
4048
+ const rawEntries = entries.map((entry) => ({
4049
+ key: entry.key,
4050
+ value: this.serialize(entry.value),
4051
+ options: entry.options
4052
+ }));
4053
+ await this.storage.mset(rawEntries);
4054
+ }
4055
+ /**
4056
+ * Delete multiple keys.
4057
+ *
4058
+ * @param keys - Array of storage keys
4059
+ * @returns Number of keys actually deleted
4060
+ */
4061
+ async mdelete(keys) {
4062
+ return this.storage.mdelete(keys);
4063
+ }
4064
+ /**
4065
+ * Update TTL on an existing key.
4066
+ *
4067
+ * @param key - Storage key
4068
+ * @param ttlSeconds - New TTL in seconds
4069
+ * @returns true if key exists and TTL was set
4070
+ */
4071
+ async expire(key, ttlSeconds) {
4072
+ return this.storage.expire(key, ttlSeconds);
4073
+ }
4074
+ /**
4075
+ * Get remaining TTL for a key.
4076
+ *
4077
+ * @param key - Storage key
4078
+ * @returns TTL in seconds, -1 if no TTL, or null if key doesn't exist
4079
+ */
4080
+ async ttl(key) {
4081
+ return this.storage.ttl(key);
4082
+ }
4083
+ /**
4084
+ * List keys matching a pattern.
4085
+ *
4086
+ * @param pattern - Glob pattern (default: '*' for all keys)
4087
+ * @returns Array of matching keys
4088
+ */
4089
+ async keys(pattern) {
4090
+ return this.storage.keys(pattern);
4091
+ }
4092
+ /**
4093
+ * Count keys matching a pattern.
4094
+ *
4095
+ * @param pattern - Glob pattern (default: '*' for all keys)
4096
+ * @returns Number of matching keys
4097
+ */
4098
+ async count(pattern) {
4099
+ return this.storage.count(pattern);
4100
+ }
4101
+ /**
4102
+ * Get the underlying storage adapter.
4103
+ * Use with caution - operations bypass type safety.
4104
+ */
4105
+ get raw() {
4106
+ return this.storage;
4107
+ }
4108
+ /**
4109
+ * Parse and validate a raw value.
4110
+ */
4111
+ parseValue(raw, key) {
4112
+ try {
4113
+ const parsed = this.deserialize(raw);
4114
+ if (this.schema) {
4115
+ const result = this.schema.safeParse(parsed);
4116
+ if (!result.success) {
4117
+ if (this.throwOnInvalid) {
4118
+ throw new Error(`TypedStorage validation failed for key "${key}": ${result.error.message}`);
4119
+ }
4120
+ return null;
4121
+ }
4122
+ return result.data;
4123
+ }
4124
+ return parsed;
4125
+ } catch (error) {
4126
+ if (this.throwOnInvalid) {
4127
+ throw error;
4128
+ }
4129
+ return null;
4130
+ }
4131
+ }
4132
+ };
4133
+
4134
+ // libs/utils/src/storage/encrypted-typed-storage.ts
4135
+ init_errors();
4136
+ var textEncoder2 = new TextEncoder();
4137
+ var textDecoder2 = new TextDecoder();
4138
+ var EncryptedTypedStorage = class {
4139
+ storage;
4140
+ activeKey;
4141
+ keyMap;
4142
+ schema;
4143
+ throwOnError;
4144
+ onKeyRotationNeeded;
4145
+ clientBinding;
4146
+ constructor(storage, options) {
4147
+ if (!options.keys || options.keys.length === 0) {
4148
+ throw new EncryptedStorageError("At least one encryption key is required");
4149
+ }
4150
+ for (const k of options.keys) {
4151
+ if (k.key.length !== 32) {
4152
+ throw new EncryptedStorageError(
4153
+ `Encryption key "${k.kid}" must be 32 bytes (AES-256), got ${k.key.length} bytes`
4154
+ );
4155
+ }
4156
+ }
4157
+ this.storage = storage;
4158
+ this.activeKey = options.keys[0];
4159
+ this.keyMap = new Map(options.keys.map((k) => [k.kid, k.key]));
4160
+ this.schema = options.schema;
4161
+ this.throwOnError = options.throwOnError ?? false;
4162
+ this.onKeyRotationNeeded = options.onKeyRotationNeeded;
4163
+ this.clientBinding = options.clientBinding;
4164
+ }
4165
+ /**
4166
+ * Get a decrypted value by key.
4167
+ *
4168
+ * @param key - Storage key
4169
+ * @returns The decrypted value, or null if not found or decryption fails
4170
+ */
4171
+ async get(key) {
4172
+ const raw = await this.storage.get(key);
4173
+ if (raw === null) {
4174
+ return null;
4175
+ }
4176
+ return this.decryptAndParse(raw, key);
4177
+ }
4178
+ /**
4179
+ * Encrypt and store a value.
4180
+ *
4181
+ * @param key - Storage key
4182
+ * @param value - Value to encrypt and store
4183
+ * @param options - Optional TTL and conditional flags
4184
+ */
4185
+ async set(key, value, options) {
4186
+ const encrypted = this.encryptValue(value);
4187
+ const serialized = JSON.stringify(encrypted);
4188
+ await this.storage.set(key, serialized, options);
4189
+ }
4190
+ /**
4191
+ * Delete a key.
4192
+ *
4193
+ * @param key - Storage key
4194
+ * @returns true if key existed and was deleted
4195
+ */
4196
+ async delete(key) {
4197
+ return this.storage.delete(key);
4198
+ }
4199
+ /**
4200
+ * Check if a key exists.
4201
+ *
4202
+ * @param key - Storage key
4203
+ * @returns true if key exists
4204
+ */
4205
+ async exists(key) {
4206
+ return this.storage.exists(key);
4207
+ }
4208
+ /**
4209
+ * Get multiple decrypted values.
4210
+ *
4211
+ * @param keys - Array of storage keys
4212
+ * @returns Array of decrypted values (null for missing/invalid keys)
4213
+ */
4214
+ async mget(keys) {
4215
+ if (keys.length === 0) {
4216
+ return [];
4217
+ }
4218
+ const rawValues = await this.storage.mget(keys);
4219
+ return rawValues.map((raw, index) => {
4220
+ if (raw === null) {
4221
+ return null;
4222
+ }
4223
+ return this.decryptAndParse(raw, keys[index]);
4224
+ });
4225
+ }
4226
+ /**
4227
+ * Encrypt and store multiple values.
4228
+ *
4229
+ * @param entries - Array of key-value-options entries
4230
+ */
4231
+ async mset(entries) {
4232
+ if (entries.length === 0) {
4233
+ return;
4234
+ }
4235
+ const rawEntries = entries.map((entry) => ({
4236
+ key: entry.key,
4237
+ value: JSON.stringify(this.encryptValue(entry.value)),
4238
+ options: entry.options
4239
+ }));
4240
+ await this.storage.mset(rawEntries);
4241
+ }
4242
+ /**
4243
+ * Delete multiple keys.
4244
+ *
4245
+ * @param keys - Array of storage keys
4246
+ * @returns Number of keys actually deleted
4247
+ */
4248
+ async mdelete(keys) {
4249
+ return this.storage.mdelete(keys);
4250
+ }
4251
+ /**
4252
+ * Update TTL on an existing key.
4253
+ *
4254
+ * @param key - Storage key
4255
+ * @param ttlSeconds - New TTL in seconds
4256
+ * @returns true if key exists and TTL was set
4257
+ */
4258
+ async expire(key, ttlSeconds) {
4259
+ return this.storage.expire(key, ttlSeconds);
4260
+ }
4261
+ /**
4262
+ * Get remaining TTL for a key.
4263
+ *
4264
+ * @param key - Storage key
4265
+ * @returns TTL in seconds, -1 if no TTL, or null if key doesn't exist
4266
+ */
4267
+ async ttl(key) {
4268
+ return this.storage.ttl(key);
4269
+ }
4270
+ /**
4271
+ * List keys matching a pattern.
4272
+ *
4273
+ * @param pattern - Glob pattern (default: '*' for all keys)
4274
+ * @returns Array of matching keys
4275
+ */
4276
+ async keys(pattern) {
4277
+ return this.storage.keys(pattern);
4278
+ }
4279
+ /**
4280
+ * Count keys matching a pattern.
4281
+ *
4282
+ * @param pattern - Glob pattern (default: '*' for all keys)
4283
+ * @returns Number of matching keys
4284
+ */
4285
+ async count(pattern) {
4286
+ return this.storage.count(pattern);
4287
+ }
4288
+ /**
4289
+ * Re-encrypt a value with the active key.
4290
+ * Useful for key rotation - reads with old key, writes with new key.
4291
+ *
4292
+ * @param key - Storage key to re-encrypt
4293
+ * @param options - Optional TTL for the re-encrypted value
4294
+ * @returns true if value was re-encrypted, false if not found
4295
+ */
4296
+ async reencrypt(key, options) {
4297
+ const value = await this.get(key);
4298
+ if (value === null) {
4299
+ return false;
4300
+ }
4301
+ await this.set(key, value, options);
4302
+ return true;
4303
+ }
4304
+ /**
4305
+ * Rotate the active encryption key.
4306
+ * New encryptions will use the new key; old keys remain for decryption.
4307
+ *
4308
+ * @param newKey - New encryption key to use for writes
4309
+ */
4310
+ rotateKey(newKey) {
4311
+ if (newKey.key.length !== 32) {
4312
+ throw new EncryptedStorageError(`New encryption key must be 32 bytes (AES-256), got ${newKey.key.length} bytes`);
4313
+ }
4314
+ if (!this.keyMap.has(newKey.kid)) {
4315
+ this.keyMap.set(newKey.kid, newKey.key);
4316
+ }
4317
+ this.activeKey = newKey;
4318
+ }
4319
+ /**
4320
+ * Get the active key ID.
4321
+ */
4322
+ get activeKeyId() {
4323
+ return this.activeKey.kid;
4324
+ }
4325
+ /**
4326
+ * Get all known key IDs.
4327
+ */
4328
+ get keyIds() {
4329
+ return Array.from(this.keyMap.keys());
4330
+ }
4331
+ /**
4332
+ * Get the underlying storage adapter.
4333
+ * Use with caution - operations bypass encryption.
4334
+ */
4335
+ get raw() {
4336
+ return this.storage;
4337
+ }
4338
+ /**
4339
+ * Check if client-side key binding is enabled.
4340
+ * When true, encryption keys are derived from both server key and client secret.
4341
+ */
4342
+ get hasClientBinding() {
4343
+ return this.clientBinding !== void 0;
4344
+ }
4345
+ /**
4346
+ * Derive the actual encryption key.
4347
+ *
4348
+ * If clientBinding is configured, combines server key with client secret
4349
+ * using HKDF-SHA256 (RFC 5869) to produce the actual encryption key.
4350
+ * Otherwise, returns the server key as-is.
4351
+ *
4352
+ * This provides zero-knowledge encryption where:
4353
+ * - Server cannot decrypt without client secret (sessionId)
4354
+ * - Client cannot decrypt without server key
4355
+ * - Key derivation is deterministic (same inputs -> same derived key)
4356
+ *
4357
+ * @param serverKey - The server-side encryption key
4358
+ * @returns The key to use for actual encryption/decryption
4359
+ */
4360
+ deriveKey(serverKey) {
4361
+ if (!this.clientBinding) {
4362
+ return serverKey;
4363
+ }
4364
+ const clientSecret = typeof this.clientBinding.secret === "string" ? textEncoder2.encode(this.clientBinding.secret) : this.clientBinding.secret;
4365
+ const ikm = new Uint8Array(serverKey.length + clientSecret.length);
4366
+ ikm.set(serverKey, 0);
4367
+ ikm.set(clientSecret, serverKey.length);
4368
+ const salt = this.clientBinding.salt ?? new Uint8Array(0);
4369
+ const info = textEncoder2.encode(this.clientBinding.info ?? "frontmcp-client-bound-v1");
4370
+ return hkdfSha256(ikm, salt, info, 32);
4371
+ }
4372
+ /**
4373
+ * Encrypt a value using the active key.
4374
+ * If client binding is configured, uses derived key from server key + client secret.
4375
+ */
4376
+ encryptValue(value) {
4377
+ let jsonString;
4378
+ try {
4379
+ jsonString = JSON.stringify(value);
4380
+ } catch (error) {
4381
+ throw new EncryptedStorageError(`Failed to serialize value: ${error.message}`);
4382
+ }
4383
+ const plaintext = textEncoder2.encode(jsonString);
4384
+ const iv = randomBytes(12);
4385
+ const encryptionKey = this.deriveKey(this.activeKey.key);
4386
+ const { ciphertext, tag } = encryptAesGcm(encryptionKey, plaintext, iv);
4387
+ return {
4388
+ alg: "A256GCM",
4389
+ kid: this.activeKey.kid,
4390
+ iv: base64urlEncode(iv),
4391
+ tag: base64urlEncode(tag),
4392
+ data: base64urlEncode(ciphertext)
4393
+ };
4394
+ }
4395
+ /**
4396
+ * Decrypt and parse a stored value.
4397
+ */
4398
+ decryptAndParse(raw, storageKey) {
4399
+ let blob;
4400
+ try {
4401
+ blob = JSON.parse(raw);
4402
+ } catch (_error) {
4403
+ if (this.throwOnError) {
4404
+ throw new EncryptedStorageError(`Failed to parse stored blob for key "${storageKey}"`);
4405
+ }
4406
+ return null;
4407
+ }
4408
+ if (!this.isValidBlob(blob)) {
4409
+ if (this.throwOnError) {
4410
+ throw new EncryptedStorageError(`Invalid encrypted blob structure for key "${storageKey}"`);
4411
+ }
4412
+ return null;
4413
+ }
4414
+ const serverKey = this.keyMap.get(blob.kid);
4415
+ if (!serverKey) {
4416
+ if (this.throwOnError) {
4417
+ throw new EncryptedStorageError(
4418
+ `Unknown encryption key "${blob.kid}" for key "${storageKey}". Known keys: ${Array.from(this.keyMap.keys()).join(", ")}`
4419
+ );
4420
+ }
4421
+ return null;
4422
+ }
4423
+ const decryptionKey = this.deriveKey(serverKey);
4424
+ let decrypted;
4425
+ try {
4426
+ const iv = base64urlDecode(blob.iv);
4427
+ const tag = base64urlDecode(blob.tag);
4428
+ const ciphertext = base64urlDecode(blob.data);
4429
+ const plaintext = decryptAesGcm(decryptionKey, ciphertext, iv, tag);
4430
+ decrypted = JSON.parse(textDecoder2.decode(plaintext));
4431
+ } catch (error) {
4432
+ if (this.throwOnError) {
4433
+ throw new EncryptedStorageError(`Decryption failed for key "${storageKey}": ${error.message}`);
4434
+ }
4435
+ return null;
4436
+ }
4437
+ if (this.schema) {
4438
+ const result = this.schema.safeParse(decrypted);
4439
+ if (!result.success) {
4440
+ if (this.throwOnError) {
4441
+ throw new EncryptedStorageError(`Schema validation failed for key "${storageKey}": ${result.error.message}`);
4442
+ }
4443
+ return null;
4444
+ }
4445
+ decrypted = result.data;
4446
+ }
4447
+ if (blob.kid !== this.activeKey.kid && this.onKeyRotationNeeded) {
4448
+ this.onKeyRotationNeeded(storageKey, blob.kid, this.activeKey.kid);
4449
+ }
4450
+ return decrypted;
4451
+ }
4452
+ /**
4453
+ * Validate blob structure.
4454
+ */
4455
+ isValidBlob(blob) {
4456
+ 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";
4457
+ }
4458
+ };
4459
+
3266
4460
  // libs/utils/src/storage/adapters/index.ts
3267
4461
  init_base();
3268
4462
  init_redis();
@@ -3270,6 +4464,7 @@ init_vercel_kv();
3270
4464
  init_upstash();
3271
4465
 
3272
4466
  // libs/utils/src/storage/index.ts
4467
+ init_pattern();
3273
4468
  init_ttl();
3274
4469
  // Annotate the CommonJS export names for ESM import in node:
3275
4470
  0 && (module.exports = {
@@ -3277,6 +4472,10 @@ init_ttl();
3277
4472
  DEFAULT_CODE_VERIFIER_LENGTH,
3278
4473
  DEFAULT_MAX_INPUT_LENGTH,
3279
4474
  EncryptedBlobError,
4475
+ EncryptedStorageError,
4476
+ EncryptedTypedStorage,
4477
+ FileSystemStorageAdapter,
4478
+ KeyPersistence,
3280
4479
  MAX_CODE_VERIFIER_LENGTH,
3281
4480
  MAX_TTL_SECONDS,
3282
4481
  MIN_CODE_VERIFIER_LENGTH,
@@ -3294,11 +4493,17 @@ init_ttl();
3294
4493
  StorageOperationError,
3295
4494
  StoragePatternError,
3296
4495
  StorageTTLError,
4496
+ TypedStorage,
3297
4497
  UpstashStorageAdapter,
3298
4498
  VercelKvStorageAdapter,
3299
4499
  access,
3300
4500
  analyzePattern,
4501
+ anyKeyDataSchema,
3301
4502
  assertNode,
4503
+ asymmetricAlgSchema,
4504
+ asymmetricKeyDataSchema,
4505
+ base64Decode,
4506
+ base64Encode,
3302
4507
  base64urlDecode,
3303
4508
  base64urlEncode,
3304
4509
  buildPrefix,
@@ -3308,6 +4513,8 @@ init_ttl();
3308
4513
  collapseWhitespace,
3309
4514
  copyFile,
3310
4515
  cp,
4516
+ createKeyPersistence,
4517
+ createKeyPersistenceWithStorage,
3311
4518
  createMemoryStorage,
3312
4519
  createNamespacedStorage,
3313
4520
  createRootStorage,
@@ -3349,6 +4556,7 @@ init_ttl();
3349
4556
  hmacSha256,
3350
4557
  idFromString,
3351
4558
  inferMimeType,
4559
+ isAsymmetricKeyData,
3352
4560
  isBrowser,
3353
4561
  isDirEmpty,
3354
4562
  isExpired,
@@ -3357,7 +4565,9 @@ init_ttl();
3357
4565
  isPatternSafe,
3358
4566
  isRsaPssAlg,
3359
4567
  isSecretCached,
4568
+ isSecretKeyData,
3360
4569
  isSecretPersistenceEnabled,
4570
+ isSignedData,
3361
4571
  isUriTemplate,
3362
4572
  isValidCodeChallenge,
3363
4573
  isValidCodeVerifier,
@@ -3372,12 +4582,14 @@ init_ttl();
3372
4582
  mkdir,
3373
4583
  mkdtemp,
3374
4584
  normalizeTTL,
4585
+ parseKeyData,
3375
4586
  parseSecretData,
3376
4587
  parseUriTemplate,
3377
4588
  randomBytes,
3378
4589
  randomUUID,
3379
4590
  readFile,
3380
4591
  readFileBuffer,
4592
+ readFileSync,
3381
4593
  readJSON,
3382
4594
  readdir,
3383
4595
  rename,
@@ -3394,12 +4606,14 @@ init_ttl();
3394
4606
  sanitizeToJson,
3395
4607
  saveSecret,
3396
4608
  secretDataSchema,
4609
+ secretKeyDataSchema,
3397
4610
  sepFor,
3398
4611
  serializeBlob,
3399
4612
  sha256,
3400
4613
  sha256Base64url,
3401
4614
  sha256Hex,
3402
4615
  shortHash,
4616
+ signData,
3403
4617
  splitWords,
3404
4618
  stat,
3405
4619
  timingSafeEqual,
@@ -3415,11 +4629,14 @@ init_ttl();
3415
4629
  ttlToExpiresAt,
3416
4630
  unlink,
3417
4631
  validateBaseUrl,
4632
+ validateKeyData,
3418
4633
  validateOptionalTTL,
3419
4634
  validatePattern,
3420
4635
  validateSecretData,
3421
4636
  validateTTL,
3422
4637
  verifyCodeChallenge,
4638
+ verifyData,
4639
+ verifyOrParseData,
3423
4640
  writeFile,
3424
4641
  writeJSON
3425
4642
  });