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
@@ -0,0 +1,145 @@
1
+ import { cwd, getEnv } from "../../platform/compat/process.js";
2
+ import { createFileSystem } from "../../platform/compat/fs.js";
3
+ import { isAbsolute, join, resolve } from "../../platform/compat/path/index.js";
4
+ import { cliLogger } from "../../utils/index.js";
5
+ import { exitProcess, registerTerminationSignals } from "../utils/index.js";
6
+ const DEFAULT_START_PORT = 8080;
7
+ const DEFAULT_MCP_PORT = 9999;
8
+ function getProjectSlug(path) {
9
+ return path.replace(/\/+$/, "").split("/").pop() || "";
10
+ }
11
+ async function isVeryFrontProject(projectPath) {
12
+ const fs = createFileSystem();
13
+ const markers = ["app", "pages", "components"];
14
+ const checks = await Promise.all(markers.map((m) => fs.exists(join(projectPath, m))));
15
+ return checks.some(Boolean);
16
+ }
17
+ async function findProjectsInDirs(baseDirs) {
18
+ const projects = new Map();
19
+ const fs = createFileSystem();
20
+ for (const baseDir of baseDirs) {
21
+ const absoluteBase = isAbsolute(baseDir) ? baseDir : join(cwd(), baseDir);
22
+ if (!(await fs.exists(absoluteBase)))
23
+ continue;
24
+ try {
25
+ for await (const entry of fs.readDir(absoluteBase)) {
26
+ if (!entry.isDirectory || entry.name.startsWith("."))
27
+ continue;
28
+ const projectPath = join(absoluteBase, entry.name);
29
+ if (await isVeryFrontProject(projectPath)) {
30
+ projects.set(entry.name, resolve(projectPath));
31
+ }
32
+ }
33
+ }
34
+ catch {
35
+ // Directory not readable - skip
36
+ }
37
+ }
38
+ return projects;
39
+ }
40
+ async function discoverProjects(explicitPath) {
41
+ const [projects, examples] = await Promise.all([
42
+ findProjectsInDirs(["data/projects", "projects"]),
43
+ findProjectsInDirs(["examples"]),
44
+ ]);
45
+ const fs = createFileSystem();
46
+ let defaultProject = null;
47
+ // Add explicit project path if provided
48
+ if (explicitPath) {
49
+ const absolutePath = isAbsolute(explicitPath) ? explicitPath : join(cwd(), explicitPath);
50
+ if (await fs.exists(absolutePath)) {
51
+ const slug = getProjectSlug(absolutePath);
52
+ projects.set(slug, resolve(absolutePath));
53
+ defaultProject = slug;
54
+ }
55
+ }
56
+ // Fall back to current directory if no projects found
57
+ if (projects.size === 0 && !defaultProject) {
58
+ const currentDir = cwd();
59
+ if (await isVeryFrontProject(currentDir)) {
60
+ const slug = getProjectSlug(currentDir);
61
+ projects.set(slug, resolve(currentDir));
62
+ defaultProject = slug;
63
+ }
64
+ }
65
+ return { projects, examples, defaultProject };
66
+ }
67
+ async function trySetupProxy(localProjects) {
68
+ try {
69
+ // Proxy is only available in local dev, not in the npm package
70
+ const { createProxyHandler, injectContextHeaders } = await import("../../../proxy/handler.js");
71
+ const { createCacheFromEnv } = await import("../../../proxy/cache/index.js");
72
+ const proxyConfig = {
73
+ apiBaseUrl: getEnv("VERYFRONT_API_BASE_URL") || "http://api.lvh.me:4000",
74
+ clientId: getEnv("OAUTH_CLIENT_ID") || "",
75
+ clientSecret: getEnv("OAUTH_CLIENT_SECRET") || "",
76
+ previewClientId: getEnv("OAUTH_PREVIEW_CLIENT_ID") || "",
77
+ previewClientSecret: getEnv("OAUTH_PREVIEW_CLIENT_SECRET") || "",
78
+ apiToken: getEnv("VERYFRONT_API_TOKEN") || "",
79
+ localProjects: Object.fromEntries(localProjects),
80
+ };
81
+ const cache = await createCacheFromEnv();
82
+ const handler = createProxyHandler({ config: proxyConfig, cache });
83
+ return {
84
+ interceptor: async (req) => injectContextHeaders(req, await handler.processRequest(req)),
85
+ close: () => handler.close(),
86
+ };
87
+ }
88
+ catch {
89
+ return { interceptor: undefined, close: async () => { } };
90
+ }
91
+ }
92
+ export async function handleStartCommand(args) {
93
+ const port = typeof args.port === "number" ? args.port : DEFAULT_START_PORT;
94
+ const mcpPort = typeof args["mcp-port"] === "number" ? args["mcp-port"] : DEFAULT_MCP_PORT;
95
+ const projectPath = args.project ? String(args.project) : null;
96
+ const headless = Boolean(args.headless || args["no-tui"]);
97
+ const { createApp, showStartup } = await import("../app/index.js");
98
+ const discovered = await discoverProjects(projectPath);
99
+ const app = createApp({
100
+ port,
101
+ mcpPort,
102
+ headless,
103
+ projects: discovered.projects,
104
+ examples: discovered.examples,
105
+ defaultProject: discovered.defaultProject ?? undefined,
106
+ });
107
+ const restoreConsole = app.interceptConsole();
108
+ if (!headless) {
109
+ await showStartup(["Loading configuration", "Discovering projects", "Starting server"]);
110
+ }
111
+ const allProjects = new Map([...discovered.projects, ...discovered.examples]);
112
+ const proxy = await trySetupProxy(allProjects);
113
+ const { createDevServer } = await import("../../server/dev-server.js");
114
+ const shutdownController = new AbortController();
115
+ const devServer = await createDevServer({
116
+ port,
117
+ projectDir: cwd(),
118
+ hmrPort: port + 1,
119
+ enableHMR: true,
120
+ enableFastRefresh: true,
121
+ signal: shutdownController.signal,
122
+ requestInterceptor: proxy.interceptor,
123
+ });
124
+ await devServer.ready;
125
+ const { createMCPServer } = await import("../mcp/index.js");
126
+ const mcpServer = await createMCPServer({ httpPort: mcpPort });
127
+ app.setServerReady();
128
+ let shuttingDown = false;
129
+ async function shutdown() {
130
+ if (shuttingDown)
131
+ return;
132
+ shuttingDown = true;
133
+ restoreConsole();
134
+ cliLogger.info("Shutting down...");
135
+ app.stop();
136
+ await mcpServer.stop();
137
+ shutdownController.abort();
138
+ await devServer.stop();
139
+ await proxy.close();
140
+ exitProcess(0);
141
+ }
142
+ registerTerminationSignals(() => void shutdown());
143
+ app.start();
144
+ await new Promise(() => { });
145
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP Module for Dev Server
3
+ *
4
+ * Exposes dev server functionality via MCP (Model Context Protocol)
5
+ * for coding agents like Claude Code and Cursor.
6
+ */
7
+ export * from "./server.js";
8
+ export * from "./error-collector.js";
9
+ export * from "./log-buffer.js";
10
+ export * from "./tools.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP Module for Dev Server
3
+ *
4
+ * Exposes dev server functionality via MCP (Model Context Protocol)
5
+ * for coding agents like Claude Code and Cursor.
6
+ */
7
+ export * from "./server.js";
8
+ export * from "./error-collector.js";
9
+ export * from "./log-buffer.js";
10
+ export * from "./tools.js";
@@ -1 +1 @@
1
- {"version":3,"file":"integration-loader.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/templates/integration-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAAe,EAqDnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,EAM3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,WAAW,EAAE,aAAa,CA8C/D,CAAC;AAqBF;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAanC;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAarC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG;IACrE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAYA;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,eAAe,EAAE,GAClC,OAAO,CAAC;IACT,YAAY,EAAE,mBAAmB,EAAE,CAAC;IACpC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CAyBD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,aAAa,CAExE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,gBAAgB,EAAE,eAAe,EAAE,GAClC,OAAO,CACR,KAAK,CAAC;IACJ,WAAW,EAAE,eAAe,CAAC;IAC7B,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC,CACH,CAcA;AAED;;;GAGG;AACH,wBAAgB,qCAAqC,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAG/E;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAE7E;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,YAAY,EAAE,CAiZxD"}
1
+ {"version":3,"file":"integration-loader.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/templates/integration-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAAe,EAqDnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,EAM3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,WAAW,EAAE,aAAa,CA8C/D,CAAC;AAqBF;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAanC;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAQrC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG;IACrE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAYA;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,eAAe,EAAE,GAClC,OAAO,CAAC;IACT,YAAY,EAAE,mBAAmB,EAAE,CAAC;IACpC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CAyBD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,aAAa,CAExE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,gBAAgB,EAAE,eAAe,EAAE,GAClC,OAAO,CACR,KAAK,CAAC;IACJ,WAAW,EAAE,eAAe,CAAC;IAC7B,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC,CACH,CAcA;AAED;;;GAGG;AACH,wBAAgB,qCAAqC,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAE/E;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAE7E;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,YAAY,EAAE,CAiZxD"}
@@ -167,10 +167,9 @@ export async function loadIntegration(integrationName) {
167
167
  const config = await loadIntegrationConfig(integrationName);
168
168
  if (!config)
169
169
  return null;
170
- const filesDir = pathHelper.join(getIntegrationDirectory(integrationName), "files");
171
170
  return {
172
171
  config,
173
- files: await loadTemplateFromDirectory(filesDir),
172
+ files: await loadTemplateFromDirectory(`integration:${integrationName}`),
174
173
  };
175
174
  }
176
175
  /**
@@ -248,8 +247,7 @@ export async function getAvailablePrompts(integrationNames) {
248
247
  * These include setup guide page and status API
249
248
  */
250
249
  export function loadIntegrationBaseFilesFromDirectory() {
251
- const filesDir = pathHelper.join(getIntegrationDirectory("_base"), "files");
252
- return loadTemplateFromDirectory(filesDir);
250
+ return loadTemplateFromDirectory("integration:_base");
253
251
  }
254
252
  /**
255
253
  * Load the _base integration config to get shared env vars like APP_URL
@@ -5,10 +5,12 @@ export interface RedisRateLimitOptions {
5
5
  }
6
6
  export declare class RedisRateLimitStore implements RateLimitStore {
7
7
  private client;
8
+ private clientPromise;
8
9
  private readonly url?;
9
10
  private readonly keyPrefix;
10
11
  constructor(options?: RedisRateLimitOptions);
11
12
  private ensureClient;
13
+ private connectClient;
12
14
  private storageKey;
13
15
  increment(key: string, windowMs: number): Promise<RateLimitEntry>;
14
16
  reset(key: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjE,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;YAKjC,YAAY;IA8B1B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAqBjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
1
+ {"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjE,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;IAK/C,OAAO,CAAC,YAAY;YAQN,aAAa;IAkC3B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAqBjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
@@ -2,15 +2,22 @@ import { createError, toError } from "../../../errors/veryfront-error.js";
2
2
  import { serverLogger as logger } from "../../../utils/index.js";
3
3
  export class RedisRateLimitStore {
4
4
  client = null;
5
+ clientPromise = null;
5
6
  url;
6
7
  keyPrefix;
7
8
  constructor(options = {}) {
8
9
  this.url = options.url;
9
10
  this.keyPrefix = options.keyPrefix ?? "veryfront:ratelimit:";
10
11
  }
11
- async ensureClient() {
12
+ ensureClient() {
12
13
  if (this.client)
13
- return this.client;
14
+ return Promise.resolve(this.client);
15
+ if (this.clientPromise)
16
+ return this.clientPromise;
17
+ this.clientPromise = this.connectClient();
18
+ return this.clientPromise;
19
+ }
20
+ async connectClient() {
14
21
  let createClient;
15
22
  try {
16
23
  const redisClientModule = ["npm:@redis/client", "@1.5.8"].join("");
@@ -18,18 +25,25 @@ export class RedisRateLimitStore {
18
25
  createClient = mod.createClient;
19
26
  }
20
27
  catch {
28
+ this.clientPromise = null;
21
29
  throw toError(createError({
22
30
  type: "config",
23
31
  message: "Redis rate limit store requires npm:@redis/client. Install dependencies or use MemoryRateLimitStore.",
24
32
  }));
25
33
  }
26
- const client = createClient({ url: this.url });
27
- client.on?.("error", (err) => {
28
- logger.error("[redis-ratelimit] client error", err);
29
- });
30
- await client.connect();
31
- this.client = client;
32
- return client;
34
+ try {
35
+ const client = createClient({ url: this.url });
36
+ client.on?.("error", (err) => {
37
+ logger.error("[redis-ratelimit] client error", err);
38
+ });
39
+ await client.connect();
40
+ this.client = client;
41
+ return client;
42
+ }
43
+ catch (error) {
44
+ this.clientPromise = null;
45
+ throw error;
46
+ }
33
47
  }
34
48
  storageKey(key) {
35
49
  return `${this.keyPrefix}${key}`;
@@ -1,14 +1,24 @@
1
1
  /** Redis caching for cross-pod SSR module sharing */
2
2
  import { type RedisClient } from "../../../../utils/redis-client.js";
3
+ /**
4
+ * @deprecated Legacy key builder. CacheBackend handles prefixing internally.
5
+ * Used only for backward compatibility if needed.
6
+ */
3
7
  export declare function redisKey(key: string): string;
4
8
  /** Initialize distributed caching for SSR modules */
5
9
  export declare function initializeSSRDistributedCache(): Promise<boolean>;
10
+ /** Check if distributed caching is enabled for SSR modules */
6
11
  export declare function isSSRDistributedCacheEnabled(): boolean;
7
12
  /** @deprecated Use initializeSSRDistributedCache instead */
8
13
  export declare const initializeSSRRedisCache: typeof initializeSSRDistributedCache;
9
14
  /** @deprecated Use isSSRDistributedCacheEnabled instead */
10
15
  export declare const isSSRRedisCacheEnabled: typeof isSSRDistributedCacheEnabled;
16
+ /** @deprecated Use isSSRDistributedCacheEnabled instead */
11
17
  export declare function getRedisEnabled(): boolean;
18
+ /**
19
+ * @deprecated Direct Redis client access is deprecated. Use CacheBackend abstraction.
20
+ * Returns null to force use of CacheBackend path in updated consumers.
21
+ */
12
22
  export declare function getRedisClientInstance(): RedisClient | null;
13
23
  export declare function getFromRedis(cacheKey: string): Promise<string | null>;
14
24
  /** Store transformed code in Redis with environment-aware TTL */
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../../../../src/src/modules/react-loader/ssr-module-loader/cache/redis.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAS3C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,qDAAqD;AACrD,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,OAAO,CAAC,CA+BtE;AAED,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD;AAED,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,sCAAgC,CAAC;AAErE,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,qCAA+B,CAAC;AAEnE,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,wBAAgB,sBAAsB,IAAI,WAAW,GAAG,IAAI,CAE3D;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS3E;AAED,iEAAiE;AACjE,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAUf"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../../../../src/src/modules/react-loader/ssr-module-loader/cache/redis.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAWrE;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,qDAAqD;AACrD,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED,8DAA8D;AAC9D,wBAAgB,4BAA4B,IAAI,OAAO,CAMtD;AAED,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,sCAAgC,CAAC;AAErE,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,qCAA+B,CAAC;AAEnE,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,WAAW,GAAG,IAAI,CAE3D;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAU3E;AAED,iEAAiE;AACjE,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAWf"}
@@ -1,79 +1,67 @@
1
1
  /** Redis caching for cross-pod SSR module sharing */
2
2
  import { rendererLogger as logger } from "../../../../utils/index.js";
3
- import { getRedisClient, isRedisConfigured, } from "../../../../utils/redis-client.js";
4
3
  import { buildRedisSSRModuleKey } from "../../../../cache/index.js";
5
4
  import { getSSRModuleRedisTTL } from "../constants.js";
6
- let redisEnabled = false;
7
- let redisClient = null;
8
- let redisInitialized = false;
9
- let redisInitPromise = null;
5
+ import { CacheBackends, createDistributedCacheAccessor } from "../../../../cache/backend.js";
6
+ /** Lazy-loaded distributed cache backend for cross-pod sharing */
7
+ const getDistributedCache = createDistributedCacheAccessor(() => CacheBackends.ssrModule(), "SSR-MODULE-LOADER");
8
+ /**
9
+ * @deprecated Legacy key builder. CacheBackend handles prefixing internally.
10
+ * Used only for backward compatibility if needed.
11
+ */
10
12
  export function redisKey(key) {
11
13
  return buildRedisSSRModuleKey(key);
12
14
  }
13
15
  /** Initialize distributed caching for SSR modules */
14
16
  export async function initializeSSRDistributedCache() {
15
- if (redisInitialized)
16
- return redisEnabled;
17
- if (redisInitPromise) {
18
- await redisInitPromise;
19
- return redisEnabled;
20
- }
21
- redisInitPromise = (async () => {
22
- if (!isRedisConfigured()) {
23
- logger.debug("[SSR-MODULE-LOADER] Redis not configured, using memory cache");
24
- redisInitialized = true;
25
- return;
26
- }
27
- try {
28
- redisClient = await getRedisClient();
29
- redisEnabled = true;
30
- logger.debug("[SSR-MODULE-LOADER] Redis cache enabled");
31
- }
32
- catch (error) {
33
- logger.warn("[SSR-MODULE-LOADER] Redis unavailable, falling back to memory cache", { error });
34
- redisEnabled = false;
35
- }
36
- finally {
37
- redisInitialized = true;
38
- }
39
- })();
40
- await redisInitPromise;
41
- redisInitPromise = null;
42
- return redisEnabled;
17
+ const backend = await getDistributedCache();
18
+ return backend !== null;
43
19
  }
20
+ /** Check if distributed caching is enabled for SSR modules */
44
21
  export function isSSRDistributedCacheEnabled() {
45
- return redisEnabled && redisClient !== null;
22
+ // We can't synchronously check if backend is initialized without accessing the promise
23
+ // But we can check if we *should* be enabled based on env via CacheBackend utils
24
+ // For now, this returns true because it's used as a guard for get/set calls
25
+ // which themselves are async and handle missing backends gracefully.
26
+ return true;
46
27
  }
47
28
  /** @deprecated Use initializeSSRDistributedCache instead */
48
29
  export const initializeSSRRedisCache = initializeSSRDistributedCache;
49
30
  /** @deprecated Use isSSRDistributedCacheEnabled instead */
50
31
  export const isSSRRedisCacheEnabled = isSSRDistributedCacheEnabled;
32
+ /** @deprecated Use isSSRDistributedCacheEnabled instead */
51
33
  export function getRedisEnabled() {
52
- return redisEnabled;
34
+ return isSSRDistributedCacheEnabled();
53
35
  }
36
+ /**
37
+ * @deprecated Direct Redis client access is deprecated. Use CacheBackend abstraction.
38
+ * Returns null to force use of CacheBackend path in updated consumers.
39
+ */
54
40
  export function getRedisClientInstance() {
55
- return redisClient;
41
+ return null;
56
42
  }
57
43
  export async function getFromRedis(cacheKey) {
58
- if (!redisEnabled || !redisClient)
44
+ const backend = await getDistributedCache();
45
+ if (!backend)
59
46
  return null;
60
47
  try {
61
- return await redisClient.get(redisKey(cacheKey));
48
+ return await backend.get(cacheKey);
62
49
  }
63
50
  catch (error) {
64
- logger.debug("[SSR-MODULE-LOADER] Redis get failed", { key: cacheKey, error });
51
+ logger.debug("[SSR-MODULE-LOADER] Distributed cache get failed", { key: cacheKey, error });
65
52
  return null;
66
53
  }
67
54
  }
68
55
  /** Store transformed code in Redis with environment-aware TTL */
69
56
  export async function setInRedis(cacheKey, code, options) {
70
- if (!redisEnabled || !redisClient)
57
+ const backend = await getDistributedCache();
58
+ if (!backend)
71
59
  return;
72
60
  const ttl = options?.ttlSeconds ?? getSSRModuleRedisTTL(options?.isProduction ?? true);
73
61
  try {
74
- await redisClient.set(redisKey(cacheKey), code, { EX: ttl });
62
+ await backend.set(cacheKey, code, ttl);
75
63
  }
76
64
  catch (error) {
77
- logger.debug("[SSR-MODULE-LOADER] Redis set failed", { key: cacheKey, error });
65
+ logger.debug("[SSR-MODULE-LOADER] Distributed cache set failed", { key: cacheKey, error });
78
66
  }
79
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/loader.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAuCpC,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAG3E;;;;;GAKG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,OAAO;IAH3B,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,mBAAmB,CAAuB;gBAE9B,OAAO,EAAE,sBAAsB;IAEnD;;OAEG;IACH,UAAU,CACR,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAuDxD,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,yBAAyB;IAuBjC,OAAO,CAAC,kBAAkB;IAK1B;;OAEG;YACW,2BAA2B;IAkGzC,OAAO,CAAC,yBAAyB;YAiBnB,2BAA2B;IA4MzC;;;OAGG;YACW,mBAAmB;IA2CjC,OAAO,CAAC,yBAAyB;IAYjC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,wBAAwB;IAehC,OAAO,CAAC,2BAA2B;IAiBnC,OAAO,CAAC,2BAA2B;IAWnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,aAAa;YAIP,uBAAuB;IAyCrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAUhB;;;OAGG;YACW,gBAAgB;YAgBhB,WAAW;YAeX,YAAY;CAiC3B"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/loader.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAsCpC,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAgC3E;;;;;GAKG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,OAAO;IAH3B,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,mBAAmB,CAAuB;gBAE9B,OAAO,EAAE,sBAAsB;IAEnD;;OAEG;IACH,UAAU,CACR,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAyFxD,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,yBAAyB;IAuBjC,OAAO,CAAC,kBAAkB;IAK1B;;OAEG;YACW,2BAA2B;IAkGzC,OAAO,CAAC,yBAAyB;YAiBnB,2BAA2B;IA6RzC;;;OAGG;YACW,mBAAmB;IA2CjC,OAAO,CAAC,yBAAyB;IAYjC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,wBAAwB;IAehC,OAAO,CAAC,2BAA2B;IAiBnC,OAAO,CAAC,2BAA2B;IAWnC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,aAAa;YAIP,uBAAuB;IAyCrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAUhB;;;OAGG;YACW,gBAAgB;YAgBhB,WAAW;YAeX,YAAY;CAiC3B"}
@@ -21,8 +21,34 @@ import { SpanNames } from "../../../observability/tracing/span-names.js";
21
21
  import { extractComponent } from "../extract-component.js";
22
22
  import { CIRCUIT_BREAKER_RESET_MS, CIRCUIT_BREAKER_THRESHOLD, IN_PROGRESS_WAIT_TIMEOUT_MS, MAX_CONCURRENT_TRANSFORMS, MAX_TRANSFORM_DEPTH, TRANSFORM_ACQUIRE_TIMEOUT_MS, TRANSFORM_BATCH_SIZE, } from "./constants.js";
23
23
  import { withTimeoutThrow } from "../../../rendering/utils/stream-utils.js";
24
- import { failedComponents, getFromRedis, getRedisClientInstance, getRedisEnabled, globalCrossProjectCache, globalInProgress, globalModuleCache, globalTmpDirs, setInRedis, transformSemaphore, } from "./cache/index.js";
25
- import { getCacheBaseDir } from "../../../utils/cache-dir.js";
24
+ import { failedComponents, getFromRedis, globalCrossProjectCache, globalInProgress, globalModuleCache, globalTmpDirs, isSSRDistributedCacheEnabled, setInRedis, transformSemaphore, } from "./cache/index.js";
25
+ import { getCacheBaseDir, getHttpBundleCacheDir } from "../../../utils/cache-dir.js";
26
+ import { ensureHttpBundlesExist } from "../../../transforms/esm/http-cache.js";
27
+ import { LRUCache } from "../../../utils/lru-wrapper.js";
28
+ /** Pattern to match HTTP bundle file:// paths in transformed code */
29
+ const HTTP_BUNDLE_PATTERN = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
30
+ /** Extract HTTP bundle paths from transformed code for proactive recovery */
31
+ function extractHttpBundlePaths(code) {
32
+ const bundles = [];
33
+ const seen = new Set();
34
+ let match;
35
+ while ((match = HTTP_BUNDLE_PATTERN.exec(code)) !== null) {
36
+ const path = match[1];
37
+ const hash = match[2];
38
+ if (!seen.has(hash)) {
39
+ seen.add(hash);
40
+ bundles.push({ path, hash });
41
+ }
42
+ }
43
+ HTTP_BUNDLE_PATTERN.lastIndex = 0;
44
+ return bundles;
45
+ }
46
+ /**
47
+ * Track modules whose HTTP bundles have been verified, keyed by tempPath:contentHash.
48
+ * Bounded LRU to prevent unbounded memory growth in long-running pods.
49
+ * Keying by contentHash ensures verification is re-done when content changes at the same path.
50
+ */
51
+ const verifiedHttpBundlePaths = new LRUCache({ maxEntries: 2000 });
26
52
  /**
27
53
  * SSR Module Loader with Redis Support.
28
54
  *
@@ -59,7 +85,37 @@ export class SSRModuleLoader {
59
85
  context: { file: filePath, phase: "transform" },
60
86
  }));
61
87
  }
62
- const mod = await withSpan(SpanNames.SSR_DYNAMIC_IMPORT, () => import(`file://${cacheEntry.tempPath}?v=${cacheEntry.contentHash}`), { "ssr.file": fileName });
88
+ let mod;
89
+ try {
90
+ mod = await withSpan(SpanNames.SSR_DYNAMIC_IMPORT, () => import(`file://${cacheEntry.tempPath}?v=${cacheEntry.contentHash}`), { "ssr.file": fileName });
91
+ }
92
+ catch (importError) {
93
+ // If import fails due to missing HTTP bundle, try to recover and retry once
94
+ const errorMsg = importError instanceof Error
95
+ ? importError.message
96
+ : String(importError);
97
+ const bundleMatch = errorMsg.match(/veryfront-http-bundle\/http-([a-f0-9]+)\.mjs/);
98
+ if (bundleMatch) {
99
+ const hash = bundleMatch[1];
100
+ logger.warn("[SSR-MODULE-LOADER] Import failed due to missing HTTP bundle, attempting recovery", {
101
+ file: filePath.slice(-40),
102
+ hash,
103
+ });
104
+ const { recoverHttpBundleByHash } = await import("../../../transforms/esm/http-cache.js");
105
+ const cacheDir = getHttpBundleCacheDir();
106
+ const recovered = await recoverHttpBundleByHash(hash, cacheDir);
107
+ if (recovered) {
108
+ logger.info("[SSR-MODULE-LOADER] HTTP bundle recovered, retrying import", { hash });
109
+ mod = await import(`file://${cacheEntry.tempPath}?v=${cacheEntry.contentHash}&retry=1`);
110
+ }
111
+ else {
112
+ throw importError;
113
+ }
114
+ }
115
+ else {
116
+ throw importError;
117
+ }
118
+ }
63
119
  failedComponents.delete(circuitKey);
64
120
  return extractComponent(mod, filePath);
65
121
  }
@@ -247,24 +303,79 @@ export class SSRModuleLoader {
247
303
  const inProgressKey = contentCacheKey;
248
304
  const cachedEntry = globalModuleCache.get(contentCacheKey);
249
305
  if (cachedEntry) {
250
- globalModuleCache.set(filePathCacheKey, cachedEntry);
251
- await this.ensureDependenciesExist(code, filePath, depth);
252
- return;
306
+ // Verify HTTP bundles exist for in-memory cached transforms (once per path+content)
307
+ const verifyKey = `${cachedEntry.tempPath}:${cachedEntry.contentHash}`;
308
+ if (!verifiedHttpBundlePaths.get(verifyKey)) {
309
+ try {
310
+ const cachedCode = await this.fs.readTextFile(cachedEntry.tempPath);
311
+ const bundlePaths = extractHttpBundlePaths(cachedCode);
312
+ if (bundlePaths.length > 0) {
313
+ const cacheDir = getHttpBundleCacheDir();
314
+ const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
315
+ if (failed.length > 0) {
316
+ logger.warn("[SSR-MODULE-LOADER] In-memory cached module has unrecoverable HTTP bundles, re-transforming", {
317
+ file: filePath.slice(-40),
318
+ failed,
319
+ });
320
+ globalModuleCache.delete(contentCacheKey);
321
+ globalModuleCache.delete(filePathCacheKey);
322
+ // Fall through to Redis or fresh transform
323
+ }
324
+ else {
325
+ verifiedHttpBundlePaths.set(verifyKey, true);
326
+ }
327
+ }
328
+ else {
329
+ verifiedHttpBundlePaths.set(verifyKey, true);
330
+ }
331
+ }
332
+ catch {
333
+ // File doesn't exist or unreadable, invalidate cache
334
+ globalModuleCache.delete(contentCacheKey);
335
+ globalModuleCache.delete(filePathCacheKey);
336
+ }
337
+ }
338
+ // Re-check after potential invalidation
339
+ if (globalModuleCache.has(contentCacheKey)) {
340
+ globalModuleCache.set(filePathCacheKey, cachedEntry);
341
+ await this.ensureDependenciesExist(code, filePath, depth);
342
+ return;
343
+ }
253
344
  }
254
- const redisEnabled = getRedisEnabled();
255
- const redisClient = getRedisClientInstance();
256
- if (redisEnabled && redisClient) {
345
+ if (isSSRDistributedCacheEnabled()) {
257
346
  const redisCode = await getFromRedis(contentCacheKey);
258
347
  if (redisCode) {
259
- const tempPath = await this.getTempPath(filePath, contentHash);
260
- await this.fs.mkdir(tempPath.substring(0, tempPath.lastIndexOf("/")), { recursive: true });
261
- await this.fs.writeTextFile(tempPath, redisCode);
262
- const entry = { tempPath, contentHash };
263
- globalModuleCache.set(contentCacheKey, entry);
264
- globalModuleCache.set(filePathCacheKey, entry);
265
- logger.debug("[SSR-MODULE-LOADER] Redis cache hit", { file: filePath.slice(-40) });
266
- await this.ensureDependenciesExist(code, filePath, depth);
267
- return;
348
+ // Proactively ensure HTTP bundles exist before using cached transform.
349
+ // The cached code may reference file:// paths to HTTP bundles that were
350
+ // created on a different pod and may not exist locally.
351
+ let httpBundlesOk = true;
352
+ const bundlePaths = extractHttpBundlePaths(redisCode);
353
+ if (bundlePaths.length > 0) {
354
+ const cacheDir = getHttpBundleCacheDir();
355
+ const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
356
+ if (failed.length > 0) {
357
+ logger.warn("[SSR-MODULE-LOADER] Redis cached code has unrecoverable HTTP bundles, re-transforming", {
358
+ file: filePath.slice(-40),
359
+ failed,
360
+ });
361
+ httpBundlesOk = false;
362
+ }
363
+ }
364
+ if (httpBundlesOk) {
365
+ const tempPath = await this.getTempPath(filePath, contentHash);
366
+ await this.fs.mkdir(tempPath.substring(0, tempPath.lastIndexOf("/")), {
367
+ recursive: true,
368
+ });
369
+ await this.fs.writeTextFile(tempPath, redisCode);
370
+ verifiedHttpBundlePaths.set(`${tempPath}:${contentHash}`, true);
371
+ const entry = { tempPath, contentHash };
372
+ globalModuleCache.set(contentCacheKey, entry);
373
+ globalModuleCache.set(filePathCacheKey, entry);
374
+ logger.debug("[SSR-MODULE-LOADER] Redis cache hit", { file: filePath.slice(-40) });
375
+ await this.ensureDependenciesExist(code, filePath, depth);
376
+ return;
377
+ }
378
+ // Fall through to re-transform, which will create HTTP bundles locally
268
379
  }
269
380
  }
270
381
  const existingTransform = globalInProgress.get(inProgressKey);
@@ -337,16 +448,33 @@ export class SSRModuleLoader {
337
448
  // Rewrite local imports to use hashed temp paths
338
449
  // This ensures that each content version uses its own cached module
339
450
  transformed = this.rewriteLocalImports(transformed, localImportPaths, filePath);
451
+ // Ensure HTTP bundles exist for this transform (handles nested bundle deps)
452
+ const bundlePaths = extractHttpBundlePaths(transformed);
453
+ if (bundlePaths.length > 0) {
454
+ const cacheDir = getHttpBundleCacheDir();
455
+ const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
456
+ if (failed.length > 0) {
457
+ logger.warn("[SSR-MODULE-LOADER] Some HTTP bundles could not be recovered", {
458
+ file: filePath.slice(-40),
459
+ failed,
460
+ });
461
+ }
462
+ }
340
463
  // Hash the TRANSFORMED content (after import rewrites) for cache busting
341
464
  // This ensures Deno's module cache is invalidated when dependencies change
342
465
  const transformedHash = await this.hashContentAsync(transformed);
343
466
  const tempPath = await this.getTempPath(filePath, transformedHash);
344
467
  await this.fs.mkdir(tempPath.substring(0, tempPath.lastIndexOf("/")), { recursive: true });
345
468
  await this.fs.writeTextFile(tempPath, transformed);
346
- if (redisEnabled && redisClient) {
469
+ if (isSSRDistributedCacheEnabled()) {
347
470
  setInRedis(contentCacheKey, transformed, {
348
471
  isProduction: this.isProductionContentSource(),
349
- }).catch(() => { });
472
+ }).catch((error) => {
473
+ logger.debug("[SSR-MODULE-LOADER] Distributed cache set failed", {
474
+ key: contentCacheKey,
475
+ error,
476
+ });
477
+ });
350
478
  }
351
479
  // Use transformedHash for cache busting in dynamic imports
352
480
  const entry = { tempPath, contentHash: transformedHash };