veryfront 0.0.82 → 0.0.83

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.
Files changed (120) hide show
  1. package/README.md +15 -1
  2. package/esm/deno.js +1 -1
  3. package/esm/proxy/cache/index.d.ts +41 -0
  4. package/esm/proxy/cache/index.d.ts.map +1 -0
  5. package/esm/proxy/cache/index.js +75 -0
  6. package/esm/proxy/cache/memory-cache.d.ts +18 -0
  7. package/esm/proxy/cache/memory-cache.d.ts.map +1 -0
  8. package/esm/proxy/cache/memory-cache.js +100 -0
  9. package/esm/proxy/cache/redis-cache.d.ts +27 -0
  10. package/esm/proxy/cache/redis-cache.d.ts.map +1 -0
  11. package/esm/proxy/cache/redis-cache.js +183 -0
  12. package/esm/proxy/cache/resilient-cache.d.ts +44 -0
  13. package/esm/proxy/cache/resilient-cache.d.ts.map +1 -0
  14. package/esm/proxy/cache/resilient-cache.js +178 -0
  15. package/esm/proxy/cache/types.d.ts +65 -0
  16. package/esm/proxy/cache/types.d.ts.map +1 -0
  17. package/esm/proxy/cache/types.js +7 -0
  18. package/esm/proxy/handler.d.ts +81 -0
  19. package/esm/proxy/handler.d.ts.map +1 -0
  20. package/esm/proxy/handler.js +417 -0
  21. package/esm/proxy/logger.d.ts +29 -0
  22. package/esm/proxy/logger.d.ts.map +1 -0
  23. package/esm/proxy/logger.js +258 -0
  24. package/esm/proxy/oauth-client.d.ts +15 -0
  25. package/esm/proxy/oauth-client.d.ts.map +1 -0
  26. package/esm/proxy/oauth-client.js +52 -0
  27. package/esm/proxy/token-manager.d.ts +59 -0
  28. package/esm/proxy/token-manager.d.ts.map +1 -0
  29. package/esm/proxy/token-manager.js +125 -0
  30. package/esm/proxy/tracing.d.ts +39 -0
  31. package/esm/proxy/tracing.d.ts.map +1 -0
  32. package/esm/proxy/tracing.js +194 -0
  33. package/esm/src/cache/backend.d.ts +2 -0
  34. package/esm/src/cache/backend.d.ts.map +1 -1
  35. package/esm/src/cache/backend.js +2 -0
  36. package/esm/src/cache/cache-key-builder.d.ts +0 -4
  37. package/esm/src/cache/cache-key-builder.d.ts.map +1 -1
  38. package/esm/src/cache/cache-key-builder.js +0 -6
  39. package/esm/src/cache/multi-tier.d.ts +0 -29
  40. package/esm/src/cache/multi-tier.d.ts.map +1 -1
  41. package/esm/src/cache/multi-tier.js +0 -26
  42. package/esm/src/cli/app/actions.d.ts +26 -0
  43. package/esm/src/cli/app/actions.d.ts.map +1 -0
  44. package/esm/src/cli/app/actions.js +152 -0
  45. package/esm/src/cli/app/components/inline-input.d.ts +35 -0
  46. package/esm/src/cli/app/components/inline-input.d.ts.map +1 -0
  47. package/esm/src/cli/app/components/inline-input.js +220 -0
  48. package/esm/src/cli/app/components/list-select.d.ts +69 -0
  49. package/esm/src/cli/app/components/list-select.d.ts.map +1 -0
  50. package/esm/src/cli/app/components/list-select.js +137 -0
  51. package/esm/src/cli/app/index.d.ts +45 -0
  52. package/esm/src/cli/app/index.d.ts.map +1 -0
  53. package/esm/src/cli/app/index.js +1252 -0
  54. package/esm/src/cli/app/state.d.ts +122 -0
  55. package/esm/src/cli/app/state.d.ts.map +1 -0
  56. package/esm/src/cli/app/state.js +232 -0
  57. package/esm/src/cli/app/views/dashboard.d.ts +19 -0
  58. package/esm/src/cli/app/views/dashboard.d.ts.map +1 -0
  59. package/esm/src/cli/app/views/dashboard.js +178 -0
  60. package/esm/src/cli/index/command-router.d.ts.map +1 -1
  61. package/esm/src/cli/index/command-router.js +9 -39
  62. package/esm/src/cli/index/start-handler.d.ts +3 -0
  63. package/esm/src/cli/index/start-handler.d.ts.map +1 -0
  64. package/esm/src/cli/index/start-handler.js +145 -0
  65. package/esm/src/cli/mcp/index.d.ts +11 -0
  66. package/esm/src/cli/mcp/index.d.ts.map +1 -0
  67. package/esm/src/cli/mcp/index.js +10 -0
  68. package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
  69. package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
  70. package/esm/src/middleware/builtin/security/redis-rate-limit.js +23 -9
  71. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +10 -0
  72. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -1
  73. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +30 -42
  74. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  75. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +34 -13
  76. package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -1
  77. package/esm/src/platform/adapters/fs/cache/file-cache.js +9 -3
  78. package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
  79. package/esm/src/server/context/cache-invalidation.js +4 -0
  80. package/esm/src/server/handlers/dev/dashboard/api.js +4 -0
  81. package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -1
  82. package/esm/src/server/handlers/dev/projects/ui-handler.js +6 -0
  83. package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
  84. package/esm/src/transforms/esm/http-cache.js +139 -64
  85. package/esm/src/utils/index.d.ts +1 -1
  86. package/esm/src/utils/index.d.ts.map +1 -1
  87. package/esm/src/utils/index.js +1 -1
  88. package/package.json +2 -1
  89. package/src/deno.js +1 -1
  90. package/src/proxy/cache/index.ts +93 -0
  91. package/src/proxy/cache/memory-cache.ts +120 -0
  92. package/src/proxy/cache/redis-cache.ts +203 -0
  93. package/src/proxy/cache/resilient-cache.ts +205 -0
  94. package/src/proxy/cache/types.ts +72 -0
  95. package/src/proxy/handler.ts +593 -0
  96. package/src/proxy/logger.ts +329 -0
  97. package/src/proxy/oauth-client.ts +91 -0
  98. package/src/proxy/token-manager.ts +174 -0
  99. package/src/proxy/tracing.ts +237 -0
  100. package/src/src/cache/backend.ts +3 -0
  101. package/src/src/cache/cache-key-builder.ts +0 -9
  102. package/src/src/cache/multi-tier.ts +0 -41
  103. package/src/src/cli/app/actions.ts +190 -0
  104. package/src/src/cli/app/components/inline-input.ts +255 -0
  105. package/src/src/cli/app/components/list-select.ts +215 -0
  106. package/src/src/cli/app/index.ts +1471 -0
  107. package/src/src/cli/app/state.ts +385 -0
  108. package/src/src/cli/app/views/dashboard.ts +212 -0
  109. package/src/src/cli/index/command-router.ts +9 -40
  110. package/src/src/cli/index/start-handler.ts +195 -0
  111. package/src/src/cli/mcp/index.ts +11 -0
  112. package/src/src/middleware/builtin/security/redis-rate-limit.ts +24 -11
  113. package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +36 -50
  114. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +38 -14
  115. package/src/src/platform/adapters/fs/cache/file-cache.ts +9 -3
  116. package/src/src/server/context/cache-invalidation.ts +4 -0
  117. package/src/src/server/handlers/dev/dashboard/api.ts +2 -0
  118. package/src/src/server/handlers/dev/projects/ui-handler.ts +6 -0
  119. package/src/src/transforms/esm/http-cache.ts +148 -73
  120. package/src/src/utils/index.ts +0 -1
@@ -280,16 +280,21 @@ async function cacheHttpModule(url: string, options: CacheOptions): Promise<stri
280
280
  await fs.writeTextFile(cachePath, code);
281
281
 
282
282
  if (distributed) {
283
- // Store code by URL, by hash (for direct recovery), and URL mapping (for debugging)
284
- // Storing code by hash enables recovery without needing URL lookup
283
+ // Store code by URL, by hash (for direct recovery), and URL mapping (for debugging).
284
+ // Storing code by hash enables recovery without needing URL lookup.
285
+ // IMPORTANT: await the writes so other pods can recover this bundle immediately.
286
+ // Without await, a transform referencing this bundle could reach Redis before
287
+ // the bundle code does, causing ensureHttpBundlesExist on another pod to miss.
285
288
  const hash = simpleHash(normalizedUrl);
286
- Promise.all([
287
- distributed.set(normalizedUrl, code, DISTRIBUTED_CACHE_TTL_SECONDS),
288
- distributed.set(`code:${hash}`, code, DISTRIBUTED_CACHE_TTL_SECONDS),
289
- distributed.set(`hash:${hash}`, normalizedUrl, DISTRIBUTED_CACHE_TTL_SECONDS),
290
- ]).catch((error) => {
289
+ try {
290
+ await Promise.all([
291
+ distributed.set(normalizedUrl, code, DISTRIBUTED_CACHE_TTL_SECONDS),
292
+ distributed.set(`code:${hash}`, code, DISTRIBUTED_CACHE_TTL_SECONDS),
293
+ distributed.set(`hash:${hash}`, normalizedUrl, DISTRIBUTED_CACHE_TTL_SECONDS),
294
+ ]);
295
+ } catch (error) {
291
296
  logger.debug("[HTTP-CACHE] Distributed cache set failed", { url: normalizedUrl, error });
292
- });
297
+ }
293
298
  }
294
299
 
295
300
  cachedPaths.set(cacheKey, cachePath);
@@ -431,6 +436,28 @@ export async function recoverHttpBundleByHash(hash: string, cacheDir: string): P
431
436
  await fs.mkdir(absoluteCacheDir, { recursive: true });
432
437
  await fs.writeTextFile(cachePath, cachedCode);
433
438
  logger.info("[HTTP-CACHE] Bundle recovery successful (direct)", { hash, path: cachePath });
439
+
440
+ // Proactively recover transitive deps so the import retry doesn't
441
+ // fail again with a different missing bundle.
442
+ const BUNDLE_RE = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
443
+ const transitiveDeps: Array<{ path: string; hash: string }> = [];
444
+ let m;
445
+ while ((m = BUNDLE_RE.exec(cachedCode)) !== null) {
446
+ const tHash = m[2]!;
447
+ if (tHash !== hash) {
448
+ transitiveDeps.push({
449
+ path: join(absoluteCacheDir, `http-${tHash}.mjs`),
450
+ hash: tHash,
451
+ });
452
+ }
453
+ }
454
+ if (transitiveDeps.length > 0) {
455
+ logger.info("[HTTP-CACHE] Recovering transitive deps from last-resort recovery", {
456
+ count: transitiveDeps.length,
457
+ });
458
+ await ensureHttpBundlesExist(transitiveDeps, cacheDir);
459
+ }
460
+
434
461
  return true;
435
462
  }
436
463
 
@@ -474,81 +501,129 @@ export async function ensureHttpBundlesExist(
474
501
  if (bundlePaths.length === 0) return [];
475
502
 
476
503
  const fs = createFileSystem();
477
- const _absoluteCacheDir = ensureAbsoluteDir(cacheDir);
478
-
479
- // Check which bundles exist locally
480
- const existenceChecks = await Promise.all(
481
- bundlePaths.map(async ({ path, hash }) => ({
482
- path,
483
- hash,
484
- exists: await exists(path),
485
- })),
486
- );
504
+ const absoluteCacheDir = ensureAbsoluteDir(cacheDir);
487
505
 
488
- const missing = existenceChecks.filter((b) => !b.exists);
489
- if (missing.length === 0) {
490
- logger.debug("[HTTP-CACHE] All bundles exist locally", { count: bundlePaths.length });
491
- return [];
492
- }
506
+ // Use [a-f0-9]+ to match both hex and decimal hashes consistently
507
+ const BUNDLE_RE = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
508
+
509
+ const extractBundleRefs = (code: string): Array<{ hash: string }> => {
510
+ const refs: Array<{ hash: string }> = [];
511
+ const dedup = new Set<string>();
512
+ let match;
513
+ while ((match = BUNDLE_RE.exec(code)) !== null) {
514
+ const hash = match[2] as string;
515
+ if (!dedup.has(hash)) {
516
+ dedup.add(hash);
517
+ refs.push({ hash });
518
+ }
519
+ }
520
+ BUNDLE_RE.lastIndex = 0;
521
+ return refs;
522
+ };
493
523
 
494
- logger.info("[HTTP-CACHE] Fetching missing bundles from distributed cache", {
495
- missing: missing.length,
496
- total: bundlePaths.length,
497
- });
524
+ const pending: Array<{ hash: string }> = bundlePaths.map((b) => ({ hash: b.hash }));
525
+ const seen = new Set<string>();
526
+ const failed = new Set<string>();
498
527
 
499
- const distributed = await getDistributedCache();
500
- if (!distributed) {
501
- logger.error("[HTTP-CACHE] No distributed cache available for bundle recovery");
502
- return missing.map((m) => m.hash);
503
- }
528
+ while (pending.length > 0) {
529
+ const batch = pending.splice(0, pending.length).filter((b) => !seen.has(b.hash));
530
+ if (batch.length === 0) break;
504
531
 
505
- // Batch fetch from distributed cache
506
- const codeKeys = missing.map((m) => `code:${m.hash}`);
507
- let codes: Map<string, string | null>;
532
+ for (const item of batch) {
533
+ seen.add(item.hash);
534
+ }
508
535
 
509
- try {
510
- if (distributed.getBatch) {
511
- codes = await distributed.getBatch(codeKeys);
512
- } else {
513
- // Fallback to individual gets
514
- const results = await Promise.all(
515
- codeKeys.map(async (key) => [key, await distributed.get(key)] as const),
516
- );
517
- codes = new Map(results);
536
+ // Check which bundles exist locally using canonical paths derived from
537
+ // cacheDir + hash. Don't trust caller-provided paths — they may reference
538
+ // a different pod's absolute directory.
539
+ const existenceChecks = await Promise.all(
540
+ batch.map(async ({ hash }) => ({
541
+ hash,
542
+ canonicalPath: join(absoluteCacheDir, `http-${hash}.mjs`),
543
+ exists: await exists(join(absoluteCacheDir, `http-${hash}.mjs`)),
544
+ })),
545
+ );
546
+
547
+ const missing = existenceChecks.filter((b) => !b.exists);
548
+ if (missing.length === 0) continue;
549
+
550
+ logger.info("[HTTP-CACHE] Fetching missing bundles from distributed cache", {
551
+ missing: missing.length,
552
+ total: batch.length,
553
+ });
554
+
555
+ const distributed = await getDistributedCache();
556
+ if (!distributed) {
557
+ logger.error("[HTTP-CACHE] No distributed cache available for bundle recovery");
558
+ for (const m of missing) failed.add(m.hash);
559
+ continue;
518
560
  }
519
- } catch (error) {
520
- logger.error("[HTTP-CACHE] Batch fetch from distributed cache failed", { error });
521
- return missing.map((m) => m.hash);
522
- }
523
561
 
524
- // Write fetched bundles to disk
525
- const failed: string[] = [];
526
- await Promise.all(
527
- missing.map(async ({ path, hash }) => {
528
- const code = codes.get(`code:${hash}`);
529
- if (!code) {
530
- logger.warn("[HTTP-CACHE] Bundle not found in distributed cache", { hash });
531
- failed.push(hash);
532
- return;
533
- }
562
+ // Batch fetch from distributed cache
563
+ const codeKeys = missing.map((m) => `code:${m.hash}`);
564
+ let codes: Map<string, string | null>;
534
565
 
535
- try {
536
- const dir = path.substring(0, path.lastIndexOf("/"));
537
- await fs.mkdir(dir, { recursive: true });
538
- await fs.writeTextFile(path, code);
539
- logger.debug("[HTTP-CACHE] Wrote bundle to disk", { hash, path });
540
- } catch (error) {
541
- logger.error("[HTTP-CACHE] Failed to write bundle to disk", { hash, error });
542
- failed.push(hash);
566
+ try {
567
+ if (distributed.getBatch) {
568
+ codes = await distributed.getBatch(codeKeys);
569
+ } else {
570
+ const results = await Promise.all(
571
+ codeKeys.map(async (key) => [key, await distributed.get(key)] as const),
572
+ );
573
+ codes = new Map(results);
543
574
  }
544
- }),
545
- );
575
+ } catch (error) {
576
+ logger.error("[HTTP-CACHE] Batch fetch from distributed cache failed", { error });
577
+ for (const m of missing) failed.add(m.hash);
578
+ continue;
579
+ }
546
580
 
547
- if (failed.length > 0) {
548
- logger.warn("[HTTP-CACHE] Some bundles could not be recovered", { failed });
549
- } else {
550
- logger.info("[HTTP-CACHE] All missing bundles recovered", { count: missing.length });
581
+ // Write fetched bundles to disk using canonical paths and scan for transitive deps
582
+ await Promise.all(
583
+ missing.map(async ({ hash, canonicalPath }) => {
584
+ const code = codes.get(`code:${hash}`);
585
+ if (!code) {
586
+ // Try single-bundle recovery as last resort
587
+ const recovered = await recoverHttpBundleByHash(hash, absoluteCacheDir);
588
+ if (!recovered) {
589
+ failed.add(hash);
590
+ } else {
591
+ // Read the recovered bundle to scan for transitive deps
592
+ try {
593
+ const recoveredCode = await fs.readTextFile(canonicalPath);
594
+ for (const ref of extractBundleRefs(recoveredCode)) {
595
+ if (!seen.has(ref.hash)) pending.push(ref);
596
+ }
597
+ } catch { /* ignore read errors for dep scanning */ }
598
+ }
599
+ return;
600
+ }
601
+
602
+ try {
603
+ await fs.mkdir(absoluteCacheDir, { recursive: true });
604
+ await fs.writeTextFile(canonicalPath, code);
605
+ logger.debug("[HTTP-CACHE] Wrote bundle to disk", { hash, path: canonicalPath });
606
+
607
+ // Scan recovered code for transitive HTTP bundle dependencies.
608
+ // HTTP bundles import other bundles (e.g., esm.sh packages depending
609
+ // on other packages). Without this, Pod B recovers only the top-level
610
+ // bundle and gets "Module not found" for transitive deps at import time.
611
+ for (const ref of extractBundleRefs(code)) {
612
+ if (!seen.has(ref.hash)) pending.push(ref);
613
+ }
614
+ } catch (error) {
615
+ logger.error("[HTTP-CACHE] Failed to write bundle to disk", { hash, error });
616
+ failed.add(hash);
617
+ }
618
+ }),
619
+ );
620
+ }
621
+
622
+ if (failed.size > 0) {
623
+ logger.warn("[HTTP-CACHE] Some bundles could not be recovered", {
624
+ failed: Array.from(failed),
625
+ });
551
626
  }
552
627
 
553
- return failed;
628
+ return Array.from(failed);
554
629
  }
@@ -12,7 +12,6 @@ export {
12
12
  getContentHash,
13
13
  shortHash,
14
14
  simpleHash,
15
- simpleHash as numericHash,
16
15
  } from "./hash-utils.js";
17
16
 
18
17
  export { MemoCache, memoize, memoizeAsync, simpleHash as memoizeHash } from "./memoize.js";