Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d227ffa
fix(cache): repair discovery globs, error discrimination, cache-dir l…
kojiwakayama Aug 3, 2026
ab175bc
fix(cache): make bundle code reference tracking constant-time
kojiwakayama Aug 3, 2026
77bef6e
fix(cache): isolate bundle metadata indexes from caller mutation
kojiwakayama Aug 3, 2026
fe2d726
fix(cache): close discovery and lifetime review gaps
kojiwakayama Aug 3, 2026
4f6e080
Keep cache access timestamps on the injected clock
kojiwakayama Aug 3, 2026
168e831
Make LRU list manager tests type-safe
kojiwakayama Aug 3, 2026
ea51fc3
test(cache): cover Node cache module links
kojiwakayama Aug 3, 2026
489b498
fix(utils): preserve undefined LRU membership
kojiwakayama Aug 3, 2026
3253e13
Keep public cache observations internally consistent
kojiwakayama Aug 3, 2026
af52b46
Preserve cache state boundary review coverage
kojiwakayama Aug 3, 2026
063d2eb
test(cache): remove file-check race
kojiwakayama Aug 3, 2026
84f34b1
fix(cache): preserve errors and portable module links
kojiwakayama Aug 3, 2026
f2fdfc0
fix(cache): address review findings on hot-path pruning, error taxono…
kwakayama Aug 3, 2026
6024e87
fix(cache): revalidate memoized node_modules links
kojiwakayama Aug 3, 2026
c7a5c1c
Avoid cache link path disclosure in warnings
kojiwakayama Aug 3, 2026
45e2471
fix(cache): redact module inspection paths
kojiwakayama Aug 3, 2026
20292b5
Close cache review gaps
kojiwakayama Aug 3, 2026
3afb163
Close cache review comment gaps
kojiwakayama Aug 3, 2026
ea6bf4d
Keep CI workflow formatting stable
kojiwakayama Aug 3, 2026
3ec0087
Stabilize SSR worker permission test
kojiwakayama Aug 3, 2026
c44ddaf
Update cache correctness branch for current main
kojiwakayama Aug 3, 2026
f5f611b
Keep cache diagnostics and Node CI deterministic
kojiwakayama Aug 3, 2026
8c4a431
Propagate cache write stat failures
kojiwakayama Aug 3, 2026
aef65e8
Keep cache correctness branch audit-clean
kojiwakayama Aug 3, 2026
b683181
Fix brace-expansion lock integrity
kojiwakayama Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/modules/react-loader/ssr-module-loader/loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
buildMdxEsmPathCacheKey,
} from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts";
import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts";
import type { FileSystem } from "#veryfront/platform/compat/fs.ts";
import type { ModuleCacheEntry } from "./types.ts";
import {
clearModulePathCache,
Expand Down Expand Up @@ -378,6 +379,65 @@ describe("SSRModuleLoader", { sanitizeResources: false, sanitizeOps: false }, ()
}
});

it("invalidates stale cache indexes when cached output cannot be inspected", async () => {
clearSSRModuleCache();

const projectDir = await makeTempDir({ prefix: "vf-ssr-loader-unreadable-output-" });
const filePath = join(projectDir, "UnreadableCachedOutput.tsx");
const projectId = "project-unreadable-cached-output-test";
const contentSourceId = "preview-main";
const source = "export default function UnreadableCachedOutput() { return null; }";
const contentHash = hashAsLoader(source, filePath, projectDir);
const configHash = computeConfigHashSync({ dev: true });
const reactVersion = "default";
const filePathCacheKey = buildSSRModuleCacheKey(
RUNTIME_VERSION,
projectId,
`${contentSourceId}:${reactVersion}:${configHash}:${filePath}`,
);
const contentCacheKey = buildSSRModuleCacheKey(
RUNTIME_VERSION,
projectId,
`${contentSourceId}:${reactVersion}:${configHash}:${filePath}:${contentHash}`,
);
const staleEntry = {
tempPath: join(projectDir, "unreadable-cache-output.mjs"),
contentHash,
};
globalModuleCache.set(contentCacheKey, staleEntry);
globalModuleCache.set(filePathCacheKey, staleEntry);
verifiedHttpBundlePaths.set(`${staleEntry.tempPath}:${contentHash}`, true);

const loader = new SSRModuleLoader({
projectDir,
projectId,
contentSourceId,
adapter: denoAdapter,
dev: true,
});
const cacheManager = (loader as unknown as {
cache: { fs: FileSystem; getFs(): FileSystem };
}).cache;
const originalFs = cacheManager.getFs();
cacheManager.fs = {
stat: () => Promise.reject(Object.assign(new Error("permission denied"), { code: "EACCES" })),
} as unknown as FileSystem;

try {
await assertRejects(
() => loader.loadModule(filePath, source),
Error,
"permission denied",
);
assertEquals(globalModuleCache.get(contentCacheKey), undefined);
assertEquals(globalModuleCache.get(filePathCacheKey), undefined);
assertEquals(verifiedHttpBundlePaths.get(`${staleEntry.tempPath}:${contentHash}`), undefined);
} finally {
cacheManager.fs = originalFs;
await remove(projectDir, { recursive: true });
}
});

it("clears verified MDX-ESM path cache before retrying stale local dependencies", async () => {
clearSSRModuleCache();
clearModulePathCache();
Expand Down
20 changes: 15 additions & 5 deletions src/modules/react-loader/ssr-module-loader/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,21 @@ export class SSRModuleLoader {
cacheEntry: ModuleCacheEntry,
): Promise<Record<string, unknown>> {
// Verify the cache file exists before attempting dynamic import
const fileExists = await verifyCacheFileExists(
this.cache.getFs(),
cacheEntry.tempPath,
"SSR-MODULE-LOADER",
);
let fileExists: boolean;
try {
fileExists = await verifyCacheFileExists(
this.cache.getFs(),
cacheEntry.tempPath,
"SSR-MODULE-LOADER",
);
} catch (error) {
// An unreadable cache entry cannot be trusted on a later attempt. Keep
// the original operational error, but remove both indexes so a repaired
// filesystem does not keep routing requests back to stale metadata.
await this.invalidateMdxEsmCacheEntry(filePath, cacheEntry);
this.cache.invalidateFilePathCacheEntry(filePath, cacheEntry);
throw error;
}
if (!fileExists) {
logger.debug("Cache file missing before import, invalidating", {
file: filePath.slice(-40),
Expand Down
Loading