veryfront 0.0.81 → 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 (157) 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 +22 -0
  34. package/esm/src/cache/backend.d.ts.map +1 -1
  35. package/esm/src/cache/backend.js +59 -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/hash.d.ts +107 -0
  40. package/esm/src/cache/hash.d.ts.map +1 -0
  41. package/esm/src/cache/hash.js +166 -0
  42. package/esm/src/cache/index.d.ts +3 -0
  43. package/esm/src/cache/index.d.ts.map +1 -1
  44. package/esm/src/cache/index.js +3 -0
  45. package/esm/src/cache/module-cache.d.ts +82 -0
  46. package/esm/src/cache/module-cache.d.ts.map +1 -0
  47. package/esm/src/cache/module-cache.js +214 -0
  48. package/esm/src/cache/multi-tier.d.ts +148 -0
  49. package/esm/src/cache/multi-tier.d.ts.map +1 -0
  50. package/esm/src/cache/multi-tier.js +326 -0
  51. package/esm/src/cli/app/actions.d.ts +26 -0
  52. package/esm/src/cli/app/actions.d.ts.map +1 -0
  53. package/esm/src/cli/app/actions.js +152 -0
  54. package/esm/src/cli/app/components/inline-input.d.ts +35 -0
  55. package/esm/src/cli/app/components/inline-input.d.ts.map +1 -0
  56. package/esm/src/cli/app/components/inline-input.js +220 -0
  57. package/esm/src/cli/app/components/list-select.d.ts +69 -0
  58. package/esm/src/cli/app/components/list-select.d.ts.map +1 -0
  59. package/esm/src/cli/app/components/list-select.js +137 -0
  60. package/esm/src/cli/app/index.d.ts +45 -0
  61. package/esm/src/cli/app/index.d.ts.map +1 -0
  62. package/esm/src/cli/app/index.js +1252 -0
  63. package/esm/src/cli/app/state.d.ts +122 -0
  64. package/esm/src/cli/app/state.d.ts.map +1 -0
  65. package/esm/src/cli/app/state.js +232 -0
  66. package/esm/src/cli/app/views/dashboard.d.ts +19 -0
  67. package/esm/src/cli/app/views/dashboard.d.ts.map +1 -0
  68. package/esm/src/cli/app/views/dashboard.js +178 -0
  69. package/esm/src/cli/index/command-router.d.ts.map +1 -1
  70. package/esm/src/cli/index/command-router.js +9 -39
  71. package/esm/src/cli/index/start-handler.d.ts +3 -0
  72. package/esm/src/cli/index/start-handler.d.ts.map +1 -0
  73. package/esm/src/cli/index/start-handler.js +145 -0
  74. package/esm/src/cli/mcp/index.d.ts +11 -0
  75. package/esm/src/cli/mcp/index.d.ts.map +1 -0
  76. package/esm/src/cli/mcp/index.js +10 -0
  77. package/esm/src/cli/templates/integration-loader.d.ts.map +1 -1
  78. package/esm/src/cli/templates/integration-loader.js +2 -4
  79. package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
  80. package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
  81. package/esm/src/middleware/builtin/security/redis-rate-limit.js +23 -9
  82. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +10 -0
  83. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -1
  84. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +30 -42
  85. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  86. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +148 -20
  87. package/esm/src/observability/tracing/span-names.d.ts +2 -0
  88. package/esm/src/observability/tracing/span-names.d.ts.map +1 -1
  89. package/esm/src/observability/tracing/span-names.js +2 -0
  90. package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -1
  91. package/esm/src/platform/adapters/fs/cache/file-cache.js +9 -3
  92. package/esm/src/rendering/orchestrator/module-loader/cache.d.ts +10 -2
  93. package/esm/src/rendering/orchestrator/module-loader/cache.d.ts.map +1 -1
  94. package/esm/src/rendering/orchestrator/module-loader/cache.js +11 -6
  95. package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -1
  96. package/esm/src/rendering/orchestrator/module-loader/index.js +72 -77
  97. package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
  98. package/esm/src/server/context/cache-invalidation.js +4 -0
  99. package/esm/src/server/handlers/dev/dashboard/api.js +4 -0
  100. package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -1
  101. package/esm/src/server/handlers/dev/projects/ui-handler.js +6 -0
  102. package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
  103. package/esm/src/transforms/esm/http-cache.js +145 -93
  104. package/esm/src/transforms/esm/transform-cache.d.ts +25 -0
  105. package/esm/src/transforms/esm/transform-cache.d.ts.map +1 -1
  106. package/esm/src/transforms/esm/transform-cache.js +45 -0
  107. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts.map +1 -1
  108. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.js +2 -36
  109. package/esm/src/utils/constants/cache.d.ts +4 -0
  110. package/esm/src/utils/constants/cache.d.ts.map +1 -1
  111. package/esm/src/utils/constants/cache.js +14 -1
  112. package/esm/src/utils/index.d.ts +1 -1
  113. package/esm/src/utils/index.d.ts.map +1 -1
  114. package/esm/src/utils/index.js +1 -1
  115. package/package.json +2 -1
  116. package/src/deno.js +1 -1
  117. package/src/proxy/cache/index.ts +93 -0
  118. package/src/proxy/cache/memory-cache.ts +120 -0
  119. package/src/proxy/cache/redis-cache.ts +203 -0
  120. package/src/proxy/cache/resilient-cache.ts +205 -0
  121. package/src/proxy/cache/types.ts +72 -0
  122. package/src/proxy/handler.ts +593 -0
  123. package/src/proxy/logger.ts +329 -0
  124. package/src/proxy/oauth-client.ts +91 -0
  125. package/src/proxy/token-manager.ts +174 -0
  126. package/src/proxy/tracing.ts +237 -0
  127. package/src/src/cache/backend.ts +65 -0
  128. package/src/src/cache/cache-key-builder.ts +0 -9
  129. package/src/src/cache/hash.ts +205 -0
  130. package/src/src/cache/index.ts +3 -0
  131. package/src/src/cache/module-cache.ts +252 -0
  132. package/src/src/cache/multi-tier.ts +462 -0
  133. package/src/src/cli/app/actions.ts +190 -0
  134. package/src/src/cli/app/components/inline-input.ts +255 -0
  135. package/src/src/cli/app/components/list-select.ts +215 -0
  136. package/src/src/cli/app/index.ts +1471 -0
  137. package/src/src/cli/app/state.ts +385 -0
  138. package/src/src/cli/app/views/dashboard.ts +212 -0
  139. package/src/src/cli/index/command-router.ts +9 -40
  140. package/src/src/cli/index/start-handler.ts +195 -0
  141. package/src/src/cli/mcp/index.ts +11 -0
  142. package/src/src/cli/templates/integration-loader.ts +2 -8
  143. package/src/src/middleware/builtin/security/redis-rate-limit.ts +24 -11
  144. package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +36 -50
  145. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +168 -25
  146. package/src/src/observability/tracing/span-names.ts +2 -0
  147. package/src/src/platform/adapters/fs/cache/file-cache.ts +9 -3
  148. package/src/src/rendering/orchestrator/module-loader/cache.ts +14 -8
  149. package/src/src/rendering/orchestrator/module-loader/index.ts +94 -89
  150. package/src/src/server/context/cache-invalidation.ts +4 -0
  151. package/src/src/server/handlers/dev/dashboard/api.ts +2 -0
  152. package/src/src/server/handlers/dev/projects/ui-handler.ts +6 -0
  153. package/src/src/transforms/esm/http-cache.ts +160 -105
  154. package/src/src/transforms/esm/transform-cache.ts +53 -0
  155. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts +2 -40
  156. package/src/src/utils/constants/cache.ts +21 -1
  157. package/src/src/utils/index.ts +0 -1
@@ -12,7 +12,6 @@ import { isAbsolute, join } from "../../platform/compat/path/index.js";
12
12
  import { cwd } from "../../platform/compat/process.js";
13
13
  import { rendererLogger as logger } from "../../utils/index.js";
14
14
  import { simpleHash } from "../../utils/hash-utils.js";
15
- import { Singleflight } from "../../utils/singleflight.js";
16
15
  import { LRUCache } from "../../utils/lru-wrapper.js";
17
16
  import { withSpan } from "../../observability/tracing/otlp-setup.js";
18
17
  import { SpanNames } from "../../observability/tracing/span-names.js";
@@ -20,35 +19,13 @@ import { resolveImport } from "../../modules/import-map/resolver.js";
20
19
  import { isDeno } from "../../platform/compat/runtime.js";
21
20
  import { getDenoNpmReactMap, getReactImportMap, REACT_VERSION } from "./package-registry.js";
22
21
  import { parseImports, replaceSpecifiers } from "./lexer.js";
22
+ import { CacheBackends, createDistributedCacheAccessor } from "../../cache/backend.js";
23
+ import { HTTP_MODULE_CACHE_MAX_ENTRIES, HTTP_MODULE_DISTRIBUTED_TTL_SEC, } from "../../utils/constants/cache.js";
23
24
  /** Lazy-loaded distributed cache backend for cross-pod sharing */
24
- let distributedCache;
25
- const distributedCacheInit = new Singleflight();
26
- function getDistributedCache() {
27
- if (distributedCache !== undefined)
28
- return Promise.resolve(distributedCache);
29
- return distributedCacheInit.do("init", async () => {
30
- try {
31
- const { CacheBackends } = await import("../../cache/backend.js");
32
- const backend = await CacheBackends.httpModule();
33
- if (backend.type === "memory") {
34
- distributedCache = null;
35
- logger.debug("[HTTP-CACHE] No distributed cache available (memory only)");
36
- return null;
37
- }
38
- distributedCache = backend;
39
- logger.debug("[HTTP-CACHE] Distributed cache initialized", { type: backend.type });
40
- return backend;
41
- }
42
- catch (error) {
43
- logger.debug("[HTTP-CACHE] Failed to initialize distributed cache", { error });
44
- distributedCache = null;
45
- return null;
46
- }
47
- });
48
- }
49
- /** TTL for cached modules in distributed cache (24 hours) */
50
- const DISTRIBUTED_CACHE_TTL_SECONDS = 86400;
51
- const cachedPaths = new LRUCache({ maxEntries: 2000 });
25
+ const getDistributedCache = createDistributedCacheAccessor(() => CacheBackends.httpModule(), "HTTP-CACHE");
26
+ /** TTL for cached modules in distributed cache (uses centralized config) */
27
+ const DISTRIBUTED_CACHE_TTL_SECONDS = HTTP_MODULE_DISTRIBUTED_TTL_SEC;
28
+ const cachedPaths = new LRUCache({ maxEntries: HTTP_MODULE_CACHE_MAX_ENTRIES });
52
29
  const processingStack = new Set();
53
30
  function ensureAbsoluteDir(path) {
54
31
  return isAbsolute(path) ? path : join(cwd(), path);
@@ -247,16 +224,22 @@ async function cacheHttpModule(url, options) {
247
224
  await fs.mkdir(cacheDir, { recursive: true });
248
225
  await fs.writeTextFile(cachePath, code);
249
226
  if (distributed) {
250
- // Store code by URL, by hash (for direct recovery), and URL mapping (for debugging)
251
- // Storing code by hash enables recovery without needing URL lookup
227
+ // Store code by URL, by hash (for direct recovery), and URL mapping (for debugging).
228
+ // Storing code by hash enables recovery without needing URL lookup.
229
+ // IMPORTANT: await the writes so other pods can recover this bundle immediately.
230
+ // Without await, a transform referencing this bundle could reach Redis before
231
+ // the bundle code does, causing ensureHttpBundlesExist on another pod to miss.
252
232
  const hash = simpleHash(normalizedUrl);
253
- Promise.all([
254
- distributed.set(normalizedUrl, code, DISTRIBUTED_CACHE_TTL_SECONDS),
255
- distributed.set(`code:${hash}`, code, DISTRIBUTED_CACHE_TTL_SECONDS),
256
- distributed.set(`hash:${hash}`, normalizedUrl, DISTRIBUTED_CACHE_TTL_SECONDS),
257
- ]).catch((error) => {
233
+ try {
234
+ await Promise.all([
235
+ distributed.set(normalizedUrl, code, DISTRIBUTED_CACHE_TTL_SECONDS),
236
+ distributed.set(`code:${hash}`, code, DISTRIBUTED_CACHE_TTL_SECONDS),
237
+ distributed.set(`hash:${hash}`, normalizedUrl, DISTRIBUTED_CACHE_TTL_SECONDS),
238
+ ]);
239
+ }
240
+ catch (error) {
258
241
  logger.debug("[HTTP-CACHE] Distributed cache set failed", { url: normalizedUrl, error });
259
- });
242
+ }
260
243
  }
261
244
  cachedPaths.set(cacheKey, cachePath);
262
245
  return cachePath;
@@ -365,6 +348,26 @@ export async function recoverHttpBundleByHash(hash, cacheDir) {
365
348
  await fs.mkdir(absoluteCacheDir, { recursive: true });
366
349
  await fs.writeTextFile(cachePath, cachedCode);
367
350
  logger.info("[HTTP-CACHE] Bundle recovery successful (direct)", { hash, path: cachePath });
351
+ // Proactively recover transitive deps so the import retry doesn't
352
+ // fail again with a different missing bundle.
353
+ const BUNDLE_RE = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
354
+ const transitiveDeps = [];
355
+ let m;
356
+ while ((m = BUNDLE_RE.exec(cachedCode)) !== null) {
357
+ const tHash = m[2];
358
+ if (tHash !== hash) {
359
+ transitiveDeps.push({
360
+ path: join(absoluteCacheDir, `http-${tHash}.mjs`),
361
+ hash: tHash,
362
+ });
363
+ }
364
+ }
365
+ if (transitiveDeps.length > 0) {
366
+ logger.info("[HTTP-CACHE] Recovering transitive deps from last-resort recovery", {
367
+ count: transitiveDeps.length,
368
+ });
369
+ await ensureHttpBundlesExist(transitiveDeps, cacheDir);
370
+ }
368
371
  return true;
369
372
  }
370
373
  // Strategy 2: URL lookup then re-fetch (fallback for bundles cached before code:{hash} was added)
@@ -403,69 +406,118 @@ export async function ensureHttpBundlesExist(bundlePaths, cacheDir) {
403
406
  if (bundlePaths.length === 0)
404
407
  return [];
405
408
  const fs = createFileSystem();
406
- const _absoluteCacheDir = ensureAbsoluteDir(cacheDir);
407
- // Check which bundles exist locally
408
- const existenceChecks = await Promise.all(bundlePaths.map(async ({ path, hash }) => ({
409
- path,
410
- hash,
411
- exists: await exists(path),
412
- })));
413
- const missing = existenceChecks.filter((b) => !b.exists);
414
- if (missing.length === 0) {
415
- logger.debug("[HTTP-CACHE] All bundles exist locally", { count: bundlePaths.length });
416
- return [];
417
- }
418
- logger.info("[HTTP-CACHE] Fetching missing bundles from distributed cache", {
419
- missing: missing.length,
420
- total: bundlePaths.length,
421
- });
422
- const distributed = await getDistributedCache();
423
- if (!distributed) {
424
- logger.error("[HTTP-CACHE] No distributed cache available for bundle recovery");
425
- return missing.map((m) => m.hash);
426
- }
427
- // Batch fetch from distributed cache
428
- const codeKeys = missing.map((m) => `code:${m.hash}`);
429
- let codes;
430
- try {
431
- if (distributed.getBatch) {
432
- codes = await distributed.getBatch(codeKeys);
409
+ const absoluteCacheDir = ensureAbsoluteDir(cacheDir);
410
+ // Use [a-f0-9]+ to match both hex and decimal hashes consistently
411
+ const BUNDLE_RE = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
412
+ const extractBundleRefs = (code) => {
413
+ const refs = [];
414
+ const dedup = new Set();
415
+ let match;
416
+ while ((match = BUNDLE_RE.exec(code)) !== null) {
417
+ const hash = match[2];
418
+ if (!dedup.has(hash)) {
419
+ dedup.add(hash);
420
+ refs.push({ hash });
421
+ }
433
422
  }
434
- else {
435
- // Fallback to individual gets
436
- const results = await Promise.all(codeKeys.map(async (key) => [key, await distributed.get(key)]));
437
- codes = new Map(results);
423
+ BUNDLE_RE.lastIndex = 0;
424
+ return refs;
425
+ };
426
+ const pending = bundlePaths.map((b) => ({ hash: b.hash }));
427
+ const seen = new Set();
428
+ const failed = new Set();
429
+ while (pending.length > 0) {
430
+ const batch = pending.splice(0, pending.length).filter((b) => !seen.has(b.hash));
431
+ if (batch.length === 0)
432
+ break;
433
+ for (const item of batch) {
434
+ seen.add(item.hash);
438
435
  }
439
- }
440
- catch (error) {
441
- logger.error("[HTTP-CACHE] Batch fetch from distributed cache failed", { error });
442
- return missing.map((m) => m.hash);
443
- }
444
- // Write fetched bundles to disk
445
- const failed = [];
446
- await Promise.all(missing.map(async ({ path, hash }) => {
447
- const code = codes.get(`code:${hash}`);
448
- if (!code) {
449
- logger.warn("[HTTP-CACHE] Bundle not found in distributed cache", { hash });
450
- failed.push(hash);
451
- return;
436
+ // Check which bundles exist locally using canonical paths derived from
437
+ // cacheDir + hash. Don't trust caller-provided paths — they may reference
438
+ // a different pod's absolute directory.
439
+ const existenceChecks = await Promise.all(batch.map(async ({ hash }) => ({
440
+ hash,
441
+ canonicalPath: join(absoluteCacheDir, `http-${hash}.mjs`),
442
+ exists: await exists(join(absoluteCacheDir, `http-${hash}.mjs`)),
443
+ })));
444
+ const missing = existenceChecks.filter((b) => !b.exists);
445
+ if (missing.length === 0)
446
+ continue;
447
+ logger.info("[HTTP-CACHE] Fetching missing bundles from distributed cache", {
448
+ missing: missing.length,
449
+ total: batch.length,
450
+ });
451
+ const distributed = await getDistributedCache();
452
+ if (!distributed) {
453
+ logger.error("[HTTP-CACHE] No distributed cache available for bundle recovery");
454
+ for (const m of missing)
455
+ failed.add(m.hash);
456
+ continue;
452
457
  }
458
+ // Batch fetch from distributed cache
459
+ const codeKeys = missing.map((m) => `code:${m.hash}`);
460
+ let codes;
453
461
  try {
454
- const dir = path.substring(0, path.lastIndexOf("/"));
455
- await fs.mkdir(dir, { recursive: true });
456
- await fs.writeTextFile(path, code);
457
- logger.debug("[HTTP-CACHE] Wrote bundle to disk", { hash, path });
462
+ if (distributed.getBatch) {
463
+ codes = await distributed.getBatch(codeKeys);
464
+ }
465
+ else {
466
+ const results = await Promise.all(codeKeys.map(async (key) => [key, await distributed.get(key)]));
467
+ codes = new Map(results);
468
+ }
458
469
  }
459
470
  catch (error) {
460
- logger.error("[HTTP-CACHE] Failed to write bundle to disk", { hash, error });
461
- failed.push(hash);
471
+ logger.error("[HTTP-CACHE] Batch fetch from distributed cache failed", { error });
472
+ for (const m of missing)
473
+ failed.add(m.hash);
474
+ continue;
462
475
  }
463
- }));
464
- if (failed.length > 0) {
465
- logger.warn("[HTTP-CACHE] Some bundles could not be recovered", { failed });
476
+ // Write fetched bundles to disk using canonical paths and scan for transitive deps
477
+ await Promise.all(missing.map(async ({ hash, canonicalPath }) => {
478
+ const code = codes.get(`code:${hash}`);
479
+ if (!code) {
480
+ // Try single-bundle recovery as last resort
481
+ const recovered = await recoverHttpBundleByHash(hash, absoluteCacheDir);
482
+ if (!recovered) {
483
+ failed.add(hash);
484
+ }
485
+ else {
486
+ // Read the recovered bundle to scan for transitive deps
487
+ try {
488
+ const recoveredCode = await fs.readTextFile(canonicalPath);
489
+ for (const ref of extractBundleRefs(recoveredCode)) {
490
+ if (!seen.has(ref.hash))
491
+ pending.push(ref);
492
+ }
493
+ }
494
+ catch { /* ignore read errors for dep scanning */ }
495
+ }
496
+ return;
497
+ }
498
+ try {
499
+ await fs.mkdir(absoluteCacheDir, { recursive: true });
500
+ await fs.writeTextFile(canonicalPath, code);
501
+ logger.debug("[HTTP-CACHE] Wrote bundle to disk", { hash, path: canonicalPath });
502
+ // Scan recovered code for transitive HTTP bundle dependencies.
503
+ // HTTP bundles import other bundles (e.g., esm.sh packages depending
504
+ // on other packages). Without this, Pod B recovers only the top-level
505
+ // bundle and gets "Module not found" for transitive deps at import time.
506
+ for (const ref of extractBundleRefs(code)) {
507
+ if (!seen.has(ref.hash))
508
+ pending.push(ref);
509
+ }
510
+ }
511
+ catch (error) {
512
+ logger.error("[HTTP-CACHE] Failed to write bundle to disk", { hash, error });
513
+ failed.add(hash);
514
+ }
515
+ }));
466
516
  }
467
- else {
468
- logger.info("[HTTP-CACHE] All missing bundles recovered", { count: missing.length });
517
+ if (failed.size > 0) {
518
+ logger.warn("[HTTP-CACHE] Some bundles could not be recovered", {
519
+ failed: Array.from(failed),
520
+ });
469
521
  }
470
- return failed;
522
+ return Array.from(failed);
471
523
  }
@@ -1,3 +1,4 @@
1
+ import { type CacheBackend } from "../../cache/backend.js";
1
2
  export interface TransformCacheEntry {
2
3
  code: string;
3
4
  hash: string;
@@ -15,6 +16,30 @@ export declare function getCachedTransform(key: string): TransformCacheEntry | u
15
16
  export declare function setCachedTransformAsync(key: string, code: string, hash: string, ttlSeconds?: number): Promise<void>;
16
17
  export declare function setCachedTransform(key: string, code: string, hash: string, ttlSeconds?: number): void;
17
18
  export declare function destroyTransformCache(): void;
19
+ /**
20
+ * Get the underlying distributed cache backend.
21
+ *
22
+ * This is exposed for callers that need direct access to the distributed
23
+ * cache (e.g., MDX module-fetcher that stores raw code strings instead of
24
+ * TransformCacheEntry JSON). Ensures initialization happens only once.
25
+ *
26
+ * Returns null if distributed cache is not available (memory-only mode).
27
+ */
28
+ export declare function getDistributedTransformBackend(): Promise<CacheBackend | null>;
29
+ /**
30
+ * Get a cached transform or compute it if not found.
31
+ *
32
+ * This is the preferred way to use the transform cache - it handles:
33
+ * - Cache lookup (distributed first, then local fallback)
34
+ * - Compute on miss
35
+ * - Cache storage on compute
36
+ *
37
+ * @param key - Cache key (use generateCacheKey to build it)
38
+ * @param computeFn - Function to compute the transform if not cached
39
+ * @param ttlSeconds - TTL for the cached entry
40
+ * @returns The cached or computed code
41
+ */
42
+ export declare function getOrComputeTransform(key: string, computeFn: () => Promise<string>, ttlSeconds?: number): Promise<string>;
18
43
  export declare function getTransformCacheStats(): {
19
44
  fallbackEntries: number;
20
45
  maxFallbackEntries: number;
@@ -1 +1 @@
1
- {"version":3,"file":"transform-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/transform-cache.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAeD,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBjE;AAED,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,iCAA2B,CAAC;AAE7D,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,kCAA4B,CAAC;AAE7D,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,OAAe,EACpB,WAAW,GAAE,OAAe,GAC3B,MAAM,CAER;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAW1C;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAG/E;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAA4B,GACvC,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAA4B,GACvC,IAAI,CAeN;AAuBD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,sBAAsB,IAAI;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,CAMA"}
1
+ {"version":3,"file":"transform-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/transform-cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAqC,MAAM,wBAAwB,CAAC;AAK9F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAeD,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBjE;AAED,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,iCAA2B,CAAC;AAE7D,wDAAwD;AACxD,eAAO,MAAM,mBAAmB,kCAA4B,CAAC;AAE7D,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,OAAe,EACpB,WAAW,GAAE,OAAe,GAC3B,MAAM,CAER;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAW1C;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAG/E;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAA4B,GACvC,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAA4B,GACvC,IAAI,CAeN;AAuBD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;;;;;GAQG;AACH,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAInF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,UAAU,GAAE,MAA4B,GACvC,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,wBAAgB,sBAAsB,IAAI;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,CAMA"}
@@ -110,6 +110,51 @@ function pruneLocalFallback() {
110
110
  export function destroyTransformCache() {
111
111
  localFallback.clear();
112
112
  }
113
+ /**
114
+ * Get the underlying distributed cache backend.
115
+ *
116
+ * This is exposed for callers that need direct access to the distributed
117
+ * cache (e.g., MDX module-fetcher that stores raw code strings instead of
118
+ * TransformCacheEntry JSON). Ensures initialization happens only once.
119
+ *
120
+ * Returns null if distributed cache is not available (memory-only mode).
121
+ */
122
+ export async function getDistributedTransformBackend() {
123
+ await initializeTransformCache();
124
+ if (!cacheBackend || cacheBackend.type === "memory")
125
+ return null;
126
+ return cacheBackend;
127
+ }
128
+ /**
129
+ * Get a cached transform or compute it if not found.
130
+ *
131
+ * This is the preferred way to use the transform cache - it handles:
132
+ * - Cache lookup (distributed first, then local fallback)
133
+ * - Compute on miss
134
+ * - Cache storage on compute
135
+ *
136
+ * @param key - Cache key (use generateCacheKey to build it)
137
+ * @param computeFn - Function to compute the transform if not cached
138
+ * @param ttlSeconds - TTL for the cached entry
139
+ * @returns The cached or computed code
140
+ */
141
+ export async function getOrComputeTransform(key, computeFn, ttlSeconds = DEFAULT_TTL_SECONDS) {
142
+ // Try to get from cache first
143
+ const cached = await getCachedTransformAsync(key);
144
+ if (cached) {
145
+ logger.debug("[TransformCache] Cache hit", { key });
146
+ return cached.code;
147
+ }
148
+ // Compute on miss
149
+ logger.debug("[TransformCache] Cache miss, computing", { key });
150
+ const code = await computeFn();
151
+ // Store in cache (fire-and-forget for performance)
152
+ const hash = String(Date.now()); // Simple hash for now
153
+ setCachedTransformAsync(key, code, hash, ttlSeconds).catch((error) => {
154
+ logger.debug("[TransformCache] Failed to cache computed transform", { key, error });
155
+ });
156
+ return code;
157
+ }
113
158
  export function getTransformCacheStats() {
114
159
  return {
115
160
  fallbackEntries: localFallback.size,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAY5E,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,aAAa,CAAC;AAgI5E;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,IAAI,CAON;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiCxD;AAsND;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,EAC7B,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6CxB;AA4OD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD,oBAAoB,CAUtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAW5E,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,aAAa,CAAC;AA4F5E;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,IAAI,CAON;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiCxD;AAsND;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,EAC7B,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6CxB;AA4OD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD,oBAAoB,CAUtB"}
@@ -14,7 +14,6 @@
14
14
  import * as dntShim from "../../../../../_dnt.shims.js";
15
15
  import { join, posix } from "../../../../../deps/deno.land/std@0.220.0/path/mod.js";
16
16
  import { rendererLogger as logger } from "../../../../utils/index.js";
17
- import { Singleflight } from "../../../../utils/singleflight.js";
18
17
  import { withSpan } from "../../../../observability/tracing/otlp-setup.js";
19
18
  import { SpanNames } from "../../../../observability/tracing/span-names.js";
20
19
  import { transformToESM } from "../../../esm-transform.js";
@@ -27,43 +26,10 @@ import { hashString } from "../utils/hash.js";
27
26
  import { createStubModule } from "../utils/stub-module.js";
28
27
  import { resolveModuleFile } from "../resolution/file-finder.js";
29
28
  import { recordSSRModules } from "../../../../modules/manifest/route-module-manifest.js";
29
+ import { getDistributedTransformBackend } from "../../../esm/transform-cache.js";
30
30
  import { TRANSFORM_DISTRIBUTED_TTL_SEC } from "../../../../utils/constants/cache.js";
31
- /**
32
- * Distributed transform cache for cross-pod sharing.
33
- * Caches transformed module code in Redis/API so other pods don't need to re-transform.
34
- */
35
- let distributedTransformCache;
36
- const distributedCacheInit = new Singleflight();
37
31
  /** TTL for cached transforms (uses centralized config) */
38
32
  const TRANSFORM_CACHE_TTL_SECONDS = TRANSFORM_DISTRIBUTED_TTL_SEC;
39
- function getDistributedTransformCache() {
40
- if (distributedTransformCache !== undefined)
41
- return Promise.resolve(distributedTransformCache);
42
- return distributedCacheInit.do("init", async () => {
43
- try {
44
- const { CacheBackends } = await import("../../../../cache/backend.js");
45
- const backend = await CacheBackends.transform();
46
- // Only use distributed cache if API or Redis (not memory - that's per-process)
47
- if (backend.type === "memory") {
48
- distributedTransformCache = null;
49
- logger.debug(`${LOG_PREFIX_MDX_LOADER} No distributed transform cache (memory only)`);
50
- return null;
51
- }
52
- distributedTransformCache = backend;
53
- logger.debug(`${LOG_PREFIX_MDX_LOADER} Distributed transform cache initialized`, {
54
- type: backend.type,
55
- });
56
- return backend;
57
- }
58
- catch (error) {
59
- logger.debug(`${LOG_PREFIX_MDX_LOADER} Failed to init distributed transform cache`, {
60
- error,
61
- });
62
- distributedTransformCache = null;
63
- return null;
64
- }
65
- });
66
- }
67
33
  /**
68
34
  * Build cache key for transformed module.
69
35
  * Includes content hash so cache invalidates when source changes.
@@ -415,7 +381,7 @@ async function doFetchAndCacheModule(normalizedPath, context, fetchAndCacheModul
415
381
  const contentHash = hashString(sourceCode);
416
382
  const transformCacheKey = getTransformCacheKey(projectId, normalizedPath, contentHash);
417
383
  let moduleCode = null;
418
- const distributedCache = await getDistributedTransformCache();
384
+ const distributedCache = await getDistributedTransformBackend();
419
385
  if (distributedCache) {
420
386
  try {
421
387
  const cached = await distributedCache.get(transformCacheKey);
@@ -51,4 +51,8 @@ export declare const REVALIDATION_TIMEOUT_MS: number;
51
51
  export declare const HTTP_MODULE_CACHE_MAX_ENTRIES: number;
52
52
  export declare const HTTP_MODULE_DISTRIBUTED_TTL_SEC: number;
53
53
  export declare const TRANSFORM_DISTRIBUTED_TTL_SEC: number;
54
+ export declare const MODULE_CACHE_MAX_ENTRIES: number;
55
+ export declare const MODULE_CACHE_TTL_MS: number;
56
+ export declare const ESM_CACHE_MAX_ENTRIES: number;
57
+ export declare const ESM_CACHE_TTL_MS: number;
54
58
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/constants/cache.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,eAAO,MAAM,aAAa,QAAqC,CAAC;AAChE,eAAO,MAAM,WAAW,QAAmC,CAAC;AAC5D,eAAO,MAAM,UAAU,QAA8B,CAAC;AAyBtD,eAAO,MAAM,uBAAuB,QAA+C,CAAC;AAEpF,eAAO,MAAM,4BAA4B,QAAoD,CAAC;AAC9F,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,QAAgD,CAAC;AACtF,eAAO,MAAM,mBAAmB,QAAqB,CAAC;AAEtD,eAAO,MAAM,yBAAyB,QAAiD,CAAC;AACxF,eAAO,MAAM,oBAAoB,QAAoB,CAAC;AAEtD,eAAO,MAAM,sBAAsB,QAA8C,CAAC;AAClF,eAAO,MAAM,iBAAiB,QAAqB,CAAC;AAEpD,eAAO,MAAM,yBAAyB,QAAiD,CAAC;AACxF,eAAO,MAAM,oBAAoB,QAAqB,CAAC;AAEvD,eAAO,MAAM,2BAA2B,QAAa,CAAC;AACtD,eAAO,MAAM,4BAA4B,QAAoB,CAAC;AAE9D,eAAO,MAAM,8BAA8B,QAAa,CAAC;AACzD,eAAO,MAAM,+BAA+B,QAAoB,CAAC;AAEjE,eAAO,MAAM,2BAA2B,QAAiB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,QAAc,CAAC;AAEtD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,6BAA6B,QAAwC,CAAC;AAMnF,eAAO,MAAM,yCAAyC,QAGrD,CAAC;AACF,eAAO,MAAM,sCAAsC,QAGlD,CAAC;AAEF,eAAO,MAAM,wCAAwC,QAGpD,CAAC;AACF,eAAO,MAAM,qCAAqC,QAGjD,CAAC;AAEF,eAAO,MAAM,mCAAmC,QAG/C,CAAC;AACF,eAAO,MAAM,gCAAgC,QAG5C,CAAC;AAEF,eAAO,MAAM,kCAAkC,QAG9C,CAAC;AACF,eAAO,MAAM,+BAA+B,QAG3C,CAAC;AAEF,6DAA6D;AAC7D,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,KAAK,EACtD,YAAY,GAAE,OAA4B,GACzC,MAAM,CAeR;AAGD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,0BAA0B,QAAwC,CAAC;AAChF,eAAO,MAAM,0BAA0B,QAAqD,CAAC;AAC7F,eAAO,MAAM,wBAAwB,QAAiD,CAAC;AACvF,eAAO,MAAM,sBAAsB,QAA+C,CAAC;AACnF,eAAO,MAAM,sBAAsB,QAA8C,CAAC;AAGlF,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAClD,eAAO,MAAM,2BAA2B,WAAW,CAAC;AAGpD,eAAO,MAAM,yBAAyB,QAAQ,CAAC;AAC/C,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAG7C,eAAO,MAAM,4BAA4B,QAAmD,CAAC;AAC7F,eAAO,MAAM,2BAA2B,QAAkD,CAAC;AAC3F,eAAO,MAAM,uBAAuB,QAAiD,CAAC;AAItF,eAAO,MAAM,6BAA6B,QAAsD,CAAC;AACjG,eAAO,MAAM,+BAA+B,QAG3C,CAAC;AAIF,eAAO,MAAM,6BAA6B,QAGzC,CAAC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/constants/cache.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,eAAO,MAAM,aAAa,QAAqC,CAAC;AAChE,eAAO,MAAM,WAAW,QAAmC,CAAC;AAC5D,eAAO,MAAM,UAAU,QAA8B,CAAC;AA8BtD,eAAO,MAAM,uBAAuB,QAA+C,CAAC;AAEpF,eAAO,MAAM,4BAA4B,QAAoD,CAAC;AAC9F,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,QAAgD,CAAC;AACtF,eAAO,MAAM,mBAAmB,QAAqB,CAAC;AAEtD,eAAO,MAAM,yBAAyB,QAAiD,CAAC;AACxF,eAAO,MAAM,oBAAoB,QAAoB,CAAC;AAEtD,eAAO,MAAM,sBAAsB,QAA8C,CAAC;AAClF,eAAO,MAAM,iBAAiB,QAAqB,CAAC;AAEpD,eAAO,MAAM,yBAAyB,QAAiD,CAAC;AACxF,eAAO,MAAM,oBAAoB,QAAqB,CAAC;AAEvD,eAAO,MAAM,2BAA2B,QAAa,CAAC;AACtD,eAAO,MAAM,4BAA4B,QAAoB,CAAC;AAE9D,eAAO,MAAM,8BAA8B,QAAa,CAAC;AACzD,eAAO,MAAM,+BAA+B,QAAoB,CAAC;AAEjE,eAAO,MAAM,2BAA2B,QAAiB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,QAAc,CAAC;AAEtD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,6BAA6B,QAAwC,CAAC;AAMnF,eAAO,MAAM,yCAAyC,QAGrD,CAAC;AACF,eAAO,MAAM,sCAAsC,QAGlD,CAAC;AAEF,eAAO,MAAM,wCAAwC,QAGpD,CAAC;AACF,eAAO,MAAM,qCAAqC,QAGjD,CAAC;AAEF,eAAO,MAAM,mCAAmC,QAG/C,CAAC;AACF,eAAO,MAAM,gCAAgC,QAG5C,CAAC;AAEF,eAAO,MAAM,kCAAkC,QAG9C,CAAC;AACF,eAAO,MAAM,+BAA+B,QAG3C,CAAC;AAEF,6DAA6D;AAC7D,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,KAAK,EACtD,YAAY,GAAE,OAA4B,GACzC,MAAM,CAeR;AAGD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,0BAA0B,QAAwC,CAAC;AAChF,eAAO,MAAM,0BAA0B,QAAqD,CAAC;AAC7F,eAAO,MAAM,wBAAwB,QAAiD,CAAC;AACvF,eAAO,MAAM,sBAAsB,QAA+C,CAAC;AACnF,eAAO,MAAM,sBAAsB,QAA8C,CAAC;AAGlF,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAClD,eAAO,MAAM,2BAA2B,WAAW,CAAC;AAGpD,eAAO,MAAM,yBAAyB,QAAQ,CAAC;AAC/C,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAG7C,eAAO,MAAM,4BAA4B,QAAmD,CAAC;AAC7F,eAAO,MAAM,2BAA2B,QAAkD,CAAC;AAC3F,eAAO,MAAM,uBAAuB,QAAiD,CAAC;AAItF,eAAO,MAAM,6BAA6B,QAAsD,CAAC;AACjG,eAAO,MAAM,+BAA+B,QAG3C,CAAC;AAIF,eAAO,MAAM,6BAA6B,QAGzC,CAAC;AAIF,eAAO,MAAM,wBAAwB,QAAkD,CAAC;AACxF,eAAO,MAAM,mBAAmB,QAG/B,CAAC;AAGF,eAAO,MAAM,qBAAqB,QAA8C,CAAC;AACjF,eAAO,MAAM,gBAAgB,QAG5B,CAAC"}
@@ -8,7 +8,13 @@ export const MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;
8
8
  export const ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;
9
9
  function getEnvString(key) {
10
10
  const g = dntShim.dntGlobalThis;
11
- return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];
11
+ try {
12
+ return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];
13
+ }
14
+ catch {
15
+ // Gracefully handle missing --allow-env permission in Deno
16
+ return undefined;
17
+ }
12
18
  }
13
19
  function getEnvNumber(key, fallback) {
14
20
  const value = getEnvString(key);
@@ -95,3 +101,10 @@ export const HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvNumber("HTTP_MODULE_DISTRIB
95
101
  // Transform cache for module compilation
96
102
  // Same TTL as HTTP module cache since transforms are tied to content hashes
97
103
  export const TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvNumber("TRANSFORM_DISTRIBUTED_TTL_SEC", HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE);
104
+ // Pod-level module cache (shared across all RenderPipeline instances)
105
+ // These caches map module paths to transformed temp file paths
106
+ export const MODULE_CACHE_MAX_ENTRIES = getEnvNumber("MODULE_CACHE_MAX_ENTRIES", 10000);
107
+ export const MODULE_CACHE_TTL_MS = getEnvNumber("MODULE_CACHE_TTL_MS", 5 * MS_PER_MINUTE);
108
+ // ESM cache for external module mappings
109
+ export const ESM_CACHE_MAX_ENTRIES = getEnvNumber("ESM_CACHE_MAX_ENTRIES", 5000);
110
+ export const ESM_CACHE_TTL_MS = getEnvNumber("ESM_CACHE_TTL_MS", 10 * MS_PER_MINUTE);
@@ -3,7 +3,7 @@ export * from "./logger/index.js";
3
3
  export * from "./constants/index.js";
4
4
  export { VERSION } from "./version.js";
5
5
  export * from "./paths.js";
6
- export { type BundleCode as HashBundleCode, computeCodeHash, computeContentHash, computeHash, getContentHash, shortHash, simpleHash, simpleHash as numericHash, } from "./hash-utils.js";
6
+ export { type BundleCode as HashBundleCode, computeCodeHash, computeContentHash, computeHash, getContentHash, shortHash, simpleHash, } from "./hash-utils.js";
7
7
  export { MemoCache, memoize, memoizeAsync, simpleHash as memoizeHash } from "./memoize.js";
8
8
  export * from "./path-utils.js";
9
9
  export * from "./format-utils.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,cAAc,YAAY,CAAC;AAE3B,OAAO,EACL,KAAK,UAAU,IAAI,cAAc,EACjC,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,IAAI,WAAW,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,IAAI,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3F,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,cAAc,YAAY,CAAC;AAE3B,OAAO,EACL,KAAK,UAAU,IAAI,cAAc,EACjC,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,SAAS,EACT,UAAU,GACX,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,IAAI,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3F,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
@@ -3,7 +3,7 @@ export * from "./logger/index.js";
3
3
  export * from "./constants/index.js";
4
4
  export { VERSION } from "./version.js";
5
5
  export * from "./paths.js";
6
- export { computeCodeHash, computeContentHash, computeHash, getContentHash, shortHash, simpleHash, simpleHash as numericHash, } from "./hash-utils.js";
6
+ export { computeCodeHash, computeContentHash, computeHash, getContentHash, shortHash, simpleHash, } from "./hash-utils.js";
7
7
  export { MemoCache, memoize, memoizeAsync, simpleHash as memoizeHash } from "./memoize.js";
8
8
  export * from "./path-utils.js";
9
9
  export * from "./format-utils.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.0.81",
3
+ "version": "0.0.83",
4
4
  "description": "Zero-config React meta-framework for building agentic AI applications",
5
5
  "keywords": [
6
6
  "react",
@@ -195,6 +195,7 @@
195
195
  "picocolors": "^1.1.0",
196
196
  "react": "19.1.1",
197
197
  "react-dom": "19.1.1",
198
+ "redis": "*",
198
199
  "rehype-highlight": "7.0.2",
199
200
  "rehype-slug": "6.0.0",
200
201
  "rehype-starry-night": "2.2.0",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.0.81",
3
+ "version": "0.0.83",
4
4
  "nodeModulesDir": "auto",
5
5
  "exclude": [
6
6
  "npm/",
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Token Cache Module
3
+ *
4
+ * Provides configurable caching for OAuth tokens.
5
+ * Supports in-memory (single instance) and Redis (distributed) backends.
6
+ * Redis cache includes automatic fallback to memory when Redis is unavailable.
7
+ */
8
+
9
+ export type {
10
+ CacheOptions,
11
+ CacheStats,
12
+ MemoryCacheOptions,
13
+ RedisCacheOptions,
14
+ TokenCache,
15
+ TokenCacheEntry,
16
+ } from "./types.js";
17
+ export { MemoryCache } from "./memory-cache.js";
18
+ export { RedisCache } from "./redis-cache.js";
19
+ export { ResilientCache } from "./resilient-cache.js";
20
+
21
+ import type { CacheOptions, TokenCache } from "./types.js";
22
+ import { MemoryCache } from "./memory-cache.js";
23
+ import { RedisCache } from "./redis-cache.js";
24
+ import { ResilientCache } from "./resilient-cache.js";
25
+ import { getEnv } from "../../src/platform/compat/process.js";
26
+ import { proxyLogger } from "../logger.js";
27
+ import { withSpan } from "../tracing.js";
28
+
29
+ const logger = proxyLogger.child({ module: "cache" });
30
+
31
+ /**
32
+ * Create a token cache based on configuration.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // In-memory cache (default)
37
+ * const cache = createCache({ type: "memory" });
38
+ *
39
+ * // Redis cache for distributed deployments
40
+ * const cache = createCache({
41
+ * type: "redis",
42
+ * options: { url: "redis://localhost:6379" }
43
+ * });
44
+ * ```
45
+ */
46
+ export async function createCache(options: CacheOptions): Promise<TokenCache> {
47
+ return withSpan("cache.create", async () => {
48
+ switch (options.type) {
49
+ case "redis":
50
+ return new RedisCache(options.options);
51
+ case "memory":
52
+ default:
53
+ return new MemoryCache(options.options);
54
+ }
55
+ }, { "cache.type": options.type });
56
+ }
57
+
58
+ /**
59
+ * Create cache from environment variables.
60
+ *
61
+ * Environment variables:
62
+ * - CACHE_TYPE: "memory" or "redis" (default: "memory")
63
+ * - REDIS_URL: Redis connection URL (required if CACHE_TYPE=redis)
64
+ * - REDIS_PREFIX: Key prefix (default: "vf:token:")
65
+ *
66
+ * When CACHE_TYPE=redis, automatically wraps with ResilientCache for
67
+ * graceful fallback to memory when Redis is unavailable.
68
+ */
69
+ export async function createCacheFromEnv(): Promise<TokenCache> {
70
+ return withSpan("cache.createFromEnv", async () => {
71
+ const cacheType = getEnv("CACHE_TYPE") || "memory";
72
+
73
+ if (cacheType === "redis") {
74
+ const url = getEnv("REDIS_URL");
75
+ if (!url) {
76
+ logger.warn("[Cache] CACHE_TYPE=redis but REDIS_URL not set, falling back to memory");
77
+ return new MemoryCache();
78
+ }
79
+
80
+ const redisCache = new RedisCache({
81
+ url,
82
+ prefix: getEnv("REDIS_PREFIX") || "vf:token:",
83
+ });
84
+
85
+ // Wrap Redis with resilient fallback to memory cache
86
+ // This ensures the proxy continues to function when Redis is unavailable
87
+ logger.info("[Cache] Using Redis with memory fallback (ResilientCache)");
88
+ return new ResilientCache(redisCache, new MemoryCache());
89
+ }
90
+
91
+ return new MemoryCache();
92
+ }, { "cache.type": getEnv("CACHE_TYPE") || "memory" });
93
+ }