veryfront 0.0.80 → 0.0.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/deno.js +1 -1
- package/esm/src/cache/backend.d.ts +20 -0
- package/esm/src/cache/backend.d.ts.map +1 -1
- package/esm/src/cache/backend.js +57 -0
- package/esm/src/cache/hash.d.ts +107 -0
- package/esm/src/cache/hash.d.ts.map +1 -0
- package/esm/src/cache/hash.js +166 -0
- package/esm/src/cache/index.d.ts +3 -0
- package/esm/src/cache/index.d.ts.map +1 -1
- package/esm/src/cache/index.js +3 -0
- package/esm/src/cache/module-cache.d.ts +82 -0
- package/esm/src/cache/module-cache.d.ts.map +1 -0
- package/esm/src/cache/module-cache.js +214 -0
- package/esm/src/cache/multi-tier.d.ts +177 -0
- package/esm/src/cache/multi-tier.d.ts.map +1 -0
- package/esm/src/cache/multi-tier.js +352 -0
- package/esm/src/cli/templates/integration-loader.d.ts.map +1 -1
- package/esm/src/cli/templates/integration-loader.js +2 -4
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +121 -14
- package/esm/src/observability/tracing/span-names.d.ts +2 -0
- package/esm/src/observability/tracing/span-names.d.ts.map +1 -1
- package/esm/src/observability/tracing/span-names.js +2 -0
- package/esm/src/rendering/orchestrator/module-loader/cache.d.ts +10 -2
- package/esm/src/rendering/orchestrator/module-loader/cache.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/module-loader/cache.js +11 -6
- package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/module-loader/index.js +72 -77
- package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache.js +6 -29
- package/esm/src/transforms/esm/transform-cache.d.ts +25 -0
- package/esm/src/transforms/esm/transform-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/transform-cache.js +45 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.js +2 -36
- package/esm/src/utils/constants/cache.d.ts +4 -0
- package/esm/src/utils/constants/cache.d.ts.map +1 -1
- package/esm/src/utils/constants/cache.js +14 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/cache/backend.ts +62 -0
- package/src/src/cache/hash.ts +205 -0
- package/src/src/cache/index.ts +3 -0
- package/src/src/cache/module-cache.ts +252 -0
- package/src/src/cache/multi-tier.ts +503 -0
- package/src/src/cli/templates/integration-loader.ts +2 -8
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +137 -18
- package/src/src/observability/tracing/span-names.ts +2 -0
- package/src/src/rendering/orchestrator/module-loader/cache.ts +14 -8
- package/src/src/rendering/orchestrator/module-loader/index.ts +94 -89
- package/src/src/transforms/esm/http-cache.ts +12 -32
- package/src/src/transforms/esm/transform-cache.ts +53 -0
- package/src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts +2 -40
- package/src/src/utils/constants/cache.ts +21 -1
|
@@ -6,69 +6,41 @@
|
|
|
6
6
|
* @module rendering/orchestrator/module-loader
|
|
7
7
|
*/
|
|
8
8
|
import { parallelMap, rendererLogger as logger } from "../../../utils/index.js";
|
|
9
|
-
import { Singleflight } from "../../../utils/singleflight.js";
|
|
10
9
|
import { getLocalAdapter } from "../../../platform/adapters/registry.js";
|
|
11
10
|
import { generateHash } from "./cache.js";
|
|
12
11
|
import { findLocalLibFile, findSourceFile } from "../file-resolver/index.js";
|
|
13
12
|
import { transformToESM } from "../../../transforms/esm-transform.js";
|
|
14
13
|
import { getProjectTmpDir } from "../../../modules/react-loader/index.js";
|
|
15
|
-
import {
|
|
14
|
+
import { generateCacheKey as generateTransformCacheKey, getOrComputeTransform, initializeTransformCache, setCachedTransformAsync, } from "../../../transforms/esm/transform-cache.js";
|
|
15
|
+
import { hashString } from "../../../cache/hash.js";
|
|
16
|
+
import { TRANSFORM_DISTRIBUTED_TTL_SEC } from "../../../utils/constants/cache.js";
|
|
17
|
+
import { ensureHttpBundlesExist } from "../../../transforms/esm/http-cache.js";
|
|
18
|
+
import { getHttpBundleCacheDir } from "../../../utils/cache-dir.js";
|
|
16
19
|
// Re-export utilities
|
|
17
20
|
export { createEsmCache, createModuleCache, generateHash } from "./cache.js";
|
|
18
21
|
export { fetchEsmModule, rewriteEsmPaths } from "./esm-rewriter.js";
|
|
19
|
-
/**
|
|
20
|
-
const
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
return distributedCacheInit.do("init", async () => {
|
|
34
|
-
try {
|
|
35
|
-
const { CacheBackends } = await import("../../../cache/backend.js");
|
|
36
|
-
const backend = await CacheBackends.transform();
|
|
37
|
-
// Only use distributed cache if API or Redis (not memory - that's per-process)
|
|
38
|
-
if (backend.type === "memory") {
|
|
39
|
-
distributedTransformCache = null;
|
|
40
|
-
logger.debug("[ModuleLoader] No distributed transform cache (memory only)");
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
distributedTransformCache = backend;
|
|
44
|
-
logger.debug("[ModuleLoader] Distributed transform cache initialized", {
|
|
45
|
-
type: backend.type,
|
|
46
|
-
});
|
|
47
|
-
return backend;
|
|
22
|
+
/** Pattern to match HTTP bundle file:// paths in transformed code */
|
|
23
|
+
const HTTP_BUNDLE_PATTERN = /file:\/\/([^"'\s]+veryfront-http-bundle\/http-([a-f0-9]+)\.mjs)/gi;
|
|
24
|
+
/** Extract HTTP bundle paths from transformed code for proactive recovery */
|
|
25
|
+
function extractHttpBundlePaths(code) {
|
|
26
|
+
const bundles = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
let match;
|
|
29
|
+
while ((match = HTTP_BUNDLE_PATTERN.exec(code)) !== null) {
|
|
30
|
+
const path = match[1];
|
|
31
|
+
const hash = match[2];
|
|
32
|
+
if (!seen.has(hash)) {
|
|
33
|
+
seen.add(hash);
|
|
34
|
+
bundles.push({ path, hash });
|
|
48
35
|
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
logger.debug("[ModuleLoader] Failed to init distributed transform cache", { error });
|
|
51
|
-
distributedTransformCache = null;
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Build cache key for transformed module.
|
|
58
|
-
* Includes content hash so cache invalidates when source changes.
|
|
59
|
-
*/
|
|
60
|
-
function getTransformCacheKey(projectId, filePath, contentHash) {
|
|
61
|
-
return `v${TRANSFORM_CACHE_VERSION}:${projectId}:${filePath}:${contentHash}`;
|
|
62
|
-
}
|
|
63
|
-
/** Simple string hash for cache keys */
|
|
64
|
-
function hashString(str) {
|
|
65
|
-
let hash = 0;
|
|
66
|
-
for (let i = 0; i < str.length; i++) {
|
|
67
|
-
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
68
|
-
hash &= hash;
|
|
69
36
|
}
|
|
70
|
-
|
|
37
|
+
HTTP_BUNDLE_PATTERN.lastIndex = 0;
|
|
38
|
+
return bundles;
|
|
71
39
|
}
|
|
40
|
+
/** Cache for created directories to avoid repeated mkdir calls */
|
|
41
|
+
const createdDirs = new Set();
|
|
42
|
+
/** TTL for cached transforms (uses centralized config) */
|
|
43
|
+
const TRANSFORM_CACHE_TTL_SECONDS = TRANSFORM_DISTRIBUTED_TTL_SEC;
|
|
72
44
|
function getModuleCacheKey(filePath, projectId, projectDir) {
|
|
73
45
|
return `${projectId ?? projectDir ?? "default"}:${filePath}`;
|
|
74
46
|
}
|
|
@@ -154,35 +126,41 @@ export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, co
|
|
|
154
126
|
}
|
|
155
127
|
const contentHash = hashString(fileContent);
|
|
156
128
|
const effectiveProjectId = projectId ?? projectDir;
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
logger.debug("[ModuleLoader] Distributed transform cache HIT", {
|
|
166
|
-
filePath,
|
|
167
|
-
cacheKey: transformCacheKey,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
logger.debug("[ModuleLoader] Distributed cache get failed", { filePath, error });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (!transformedCode) {
|
|
176
|
-
transformedCode = await transformToESM(fileContent, filePath, projectDir, adapter, {
|
|
129
|
+
const scopedPath = `${effectiveProjectId}:${filePath}`;
|
|
130
|
+
const transformCacheKey = generateTransformCacheKey(scopedPath, contentHash, true); // ssr=true
|
|
131
|
+
// Initialize transform cache (lazy, only once per pod)
|
|
132
|
+
await initializeTransformCache();
|
|
133
|
+
// Use consolidated transform cache with getOrCompute pattern
|
|
134
|
+
let transformedCode = await getOrComputeTransform(transformCacheKey, () => {
|
|
135
|
+
logger.debug("[ModuleLoader] Transform cache miss, transforming", { filePath });
|
|
136
|
+
return transformToESM(fileContent, filePath, projectDir, adapter, {
|
|
177
137
|
projectId: effectiveProjectId,
|
|
178
138
|
dev: mode === "development",
|
|
179
139
|
ssr: true,
|
|
180
140
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
141
|
+
}, TRANSFORM_CACHE_TTL_SECONDS);
|
|
142
|
+
// Proactively ensure HTTP bundles exist before writing the module.
|
|
143
|
+
// Cached transforms from a different pod may reference file:// paths
|
|
144
|
+
// to HTTP bundles that don't exist locally.
|
|
145
|
+
const bundlePaths = extractHttpBundlePaths(transformedCode);
|
|
146
|
+
if (bundlePaths.length > 0) {
|
|
147
|
+
const cacheDir = getHttpBundleCacheDir();
|
|
148
|
+
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
149
|
+
if (failed.length > 0) {
|
|
150
|
+
logger.warn("[ModuleLoader] HTTP bundle recovery failed, re-transforming", {
|
|
151
|
+
filePath,
|
|
152
|
+
failed,
|
|
153
|
+
});
|
|
154
|
+
transformedCode = await transformToESM(fileContent, filePath, projectDir, adapter, {
|
|
155
|
+
projectId: effectiveProjectId,
|
|
156
|
+
dev: mode === "development",
|
|
157
|
+
ssr: true,
|
|
158
|
+
});
|
|
159
|
+
setCachedTransformAsync(transformCacheKey, transformedCode, contentHash, TRANSFORM_CACHE_TTL_SECONDS).catch((error) => {
|
|
160
|
+
logger.debug("[ModuleLoader] Failed to update transform cache after re-transform", {
|
|
161
|
+
filePath,
|
|
162
|
+
error,
|
|
163
|
+
});
|
|
186
164
|
});
|
|
187
165
|
}
|
|
188
166
|
}
|
|
@@ -219,6 +197,23 @@ export async function loadModule(filePath, config) {
|
|
|
219
197
|
return await import(moduleUrl);
|
|
220
198
|
}
|
|
221
199
|
catch (error) {
|
|
200
|
+
// If import fails due to missing HTTP bundle, try to recover and retry once
|
|
201
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
202
|
+
const bundleMatch = errorMsg.match(/veryfront-http-bundle\/http-([a-f0-9]+)\.mjs/);
|
|
203
|
+
if (bundleMatch) {
|
|
204
|
+
const hash = bundleMatch[1];
|
|
205
|
+
logger.warn("[ModuleLoader] Import failed due to missing HTTP bundle, attempting recovery", {
|
|
206
|
+
filePath,
|
|
207
|
+
hash,
|
|
208
|
+
});
|
|
209
|
+
const { recoverHttpBundleByHash } = await import("../../../transforms/esm/http-cache.js");
|
|
210
|
+
const cacheDir = getHttpBundleCacheDir();
|
|
211
|
+
const recovered = await recoverHttpBundleByHash(hash, cacheDir);
|
|
212
|
+
if (recovered) {
|
|
213
|
+
logger.info("[ModuleLoader] HTTP bundle recovered, retrying import", { hash });
|
|
214
|
+
return await import(`file://${tempFilePath}?t=${Date.now()}&retry=1`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
222
217
|
logger.error("[ModuleLoader] Failed to import module:", {
|
|
223
218
|
filePath,
|
|
224
219
|
tempFilePath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAmBzE,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,eAAe,CAAC;IAC3B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAoUF;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOvF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwC9F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,EAAE,CAAC,CAiFnB"}
|
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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);
|
|
@@ -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":"
|
|
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":"
|
|
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
|
|
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;
|
|
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
|
-
|
|
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);
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
package/src/src/cache/backend.ts
CHANGED
|
@@ -587,6 +587,68 @@ export function createCacheBackend(config: CacheBackendConfig = {}): Promise<Cac
|
|
|
587
587
|
);
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Check if a cache backend supports distributed (cross-pod) caching.
|
|
592
|
+
*
|
|
593
|
+
* Use this instead of checking `backend.type === "memory"` directly,
|
|
594
|
+
* which is a leaky abstraction that exposes implementation details.
|
|
595
|
+
*/
|
|
596
|
+
export function isDistributedBackend(backend: CacheBackend): boolean {
|
|
597
|
+
return backend.type !== "memory";
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Create a lazy-initialized distributed cache accessor.
|
|
602
|
+
*
|
|
603
|
+
* This encapsulates the common pattern of:
|
|
604
|
+
* 1. Lazy-init a cache backend via Singleflight
|
|
605
|
+
* 2. Skip if memory-only (not useful for cross-pod sharing)
|
|
606
|
+
* 3. Return null if init fails
|
|
607
|
+
*
|
|
608
|
+
* @param factory - Function that creates the cache backend
|
|
609
|
+
* @param name - Log prefix for debug messages
|
|
610
|
+
* @returns A function that returns the distributed cache backend or null
|
|
611
|
+
*/
|
|
612
|
+
export function createDistributedCacheAccessor(
|
|
613
|
+
factory: () => Promise<CacheBackend>,
|
|
614
|
+
name: string,
|
|
615
|
+
): () => Promise<CacheBackend | null> {
|
|
616
|
+
let backend: CacheBackend | null | undefined;
|
|
617
|
+
const singleflight = new (class {
|
|
618
|
+
private promise: Promise<CacheBackend | null> | null = null;
|
|
619
|
+
do(fn: () => Promise<CacheBackend | null>): Promise<CacheBackend | null> {
|
|
620
|
+
if (!this.promise) {
|
|
621
|
+
this.promise = fn().finally(() => {
|
|
622
|
+
this.promise = null;
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
return this.promise;
|
|
626
|
+
}
|
|
627
|
+
})();
|
|
628
|
+
|
|
629
|
+
return () => {
|
|
630
|
+
if (backend !== undefined) return Promise.resolve(backend);
|
|
631
|
+
|
|
632
|
+
return singleflight.do(async () => {
|
|
633
|
+
try {
|
|
634
|
+
const b = await factory();
|
|
635
|
+
if (!isDistributedBackend(b)) {
|
|
636
|
+
backend = null;
|
|
637
|
+
logger.debug(`[${name}] No distributed cache available (memory only)`);
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
backend = b;
|
|
641
|
+
logger.debug(`[${name}] Distributed cache initialized`, { type: b.type });
|
|
642
|
+
return b;
|
|
643
|
+
} catch (error) {
|
|
644
|
+
logger.debug(`[${name}] Failed to initialize distributed cache`, { error });
|
|
645
|
+
backend = null;
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
590
652
|
/** Convenience wrappers for common cache patterns. */
|
|
591
653
|
export const CacheBackends = {
|
|
592
654
|
/** Transform cache for compiled code. */
|