veryfront 0.1.86 → 0.1.87
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/platform/adapters/fs/veryfront/content-metrics.d.ts +1 -1
- package/esm/src/platform/adapters/fs/veryfront/content-metrics.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/content-metrics.js +7 -1
- package/esm/src/platform/adapters/fs/veryfront/file-list-index.d.ts +10 -0
- package/esm/src/platform/adapters/fs/veryfront/file-list-index.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/file-list-index.js +83 -19
- package/esm/src/platform/adapters/fs/veryfront/read-operations-helpers.d.ts +5 -0
- package/esm/src/platform/adapters/fs/veryfront/read-operations-helpers.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/read-operations-helpers.js +6 -0
- package/esm/src/platform/adapters/fs/veryfront/read-operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/read-operations.js +53 -34
- package/esm/src/rendering/page-resolution/page-resolver.d.ts.map +1 -1
- package/esm/src/rendering/page-resolution/page-resolver.js +10 -4
- package/esm/src/utils/version.d.ts +1 -1
- package/esm/src/utils/version.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/platform/adapters/fs/veryfront/content-metrics.ts +13 -2
- package/src/src/platform/adapters/fs/veryfront/file-list-index.ts +101 -18
- package/src/src/platform/adapters/fs/veryfront/read-operations-helpers.ts +10 -0
- package/src/src/platform/adapters/fs/veryfront/read-operations.ts +87 -30
- package/src/src/rendering/page-resolution/page-resolver.ts +17 -6
- package/src/src/utils/version.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type MissReason = "cold_start" | "not_in_filelist" | "invalidation" | "no_filelist_cache";
|
|
1
|
+
export type MissReason = "cold_start" | "not_in_filelist" | "invalidation" | "no_filelist_cache" | "indexed_without_content";
|
|
2
2
|
interface CumulativeMetrics {
|
|
3
3
|
requestScopedHits: number;
|
|
4
4
|
persistentCacheHits: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-metrics.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/content-metrics.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"content-metrics.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/content-metrics.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,UAAU,GAClB,YAAY,GACZ,iBAAiB,GACjB,cAAc,GACd,mBAAmB,GACnB,yBAAyB,CAAC;AAe9B,UAAU,iBAAiB;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AA4CD,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,iBAAiB,CAC/B,cAAc,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxE,IAAI,CAuCN;AAED,KAAK,kBAAkB,GACnB,oBAAoB,GACpB,sBAAsB,GACtB,eAAe,GACf,eAAe,GACf,wBAAwB,GACxB,YAAY,CAAC;AAEjB,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,GACA,IAAI,CAyCN;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,GAAG;IAC/D,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAOA;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
|
|
@@ -20,7 +20,13 @@ function createFreshRequestMetrics() {
|
|
|
20
20
|
networkFetches: 0,
|
|
21
21
|
networkMs: 0,
|
|
22
22
|
fetchesByType: { page: 0, layout: 0, component: 0, api: 0, data: 0, config: 0, other: 0 },
|
|
23
|
-
missReasons: {
|
|
23
|
+
missReasons: {
|
|
24
|
+
cold_start: 0,
|
|
25
|
+
not_in_filelist: 0,
|
|
26
|
+
invalidation: 0,
|
|
27
|
+
no_filelist_cache: 0,
|
|
28
|
+
indexed_without_content: 0,
|
|
29
|
+
},
|
|
24
30
|
isPreviewMode: null,
|
|
25
31
|
filesAccessed: new Set(),
|
|
26
32
|
};
|
|
@@ -2,21 +2,31 @@ interface FileListCacheEntry {
|
|
|
2
2
|
path: string;
|
|
3
3
|
content?: string;
|
|
4
4
|
}
|
|
5
|
+
export interface FileListMatchResult {
|
|
6
|
+
status: "unavailable" | "missing" | "present_without_content" | "hit";
|
|
7
|
+
fresh: boolean;
|
|
8
|
+
path?: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
}
|
|
5
11
|
export declare class FileListIndex {
|
|
6
12
|
private readonly getFileListCache?;
|
|
7
13
|
private index;
|
|
14
|
+
private pathSet;
|
|
8
15
|
private indexKey;
|
|
9
16
|
private indexBuiltAt;
|
|
17
|
+
private indexFresh;
|
|
10
18
|
private readyPromise;
|
|
11
19
|
constructor(getFileListCache?: (() => Promise<Array<FileListCacheEntry> | undefined>) | undefined);
|
|
12
20
|
setReadyPromise(promise: Promise<void>): void;
|
|
13
21
|
clear(): void;
|
|
14
22
|
private ensureReady;
|
|
15
23
|
lookup(normalizedPath: string): Promise<string | undefined>;
|
|
24
|
+
match(normalizedPath: string): Promise<FileListMatchResult>;
|
|
16
25
|
findFirstWithContent(normalizedPaths: string[]): Promise<{
|
|
17
26
|
path: string;
|
|
18
27
|
content: string;
|
|
19
28
|
} | undefined>;
|
|
29
|
+
findFirstMatch(normalizedPaths: string[]): Promise<FileListMatchResult>;
|
|
20
30
|
private getOrBuild;
|
|
21
31
|
}
|
|
22
32
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-list-index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/file-list-index.ts"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAeD,qBAAa,aAAa;
|
|
1
|
+
{"version":3,"file":"file-list-index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/file-list-index.ts"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,GAAG,SAAS,GAAG,yBAAyB,GAAG,KAAK,CAAC;IACtE,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAeD,qBAAa,aAAa;IAStB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IARpC,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAA8B;gBAG/B,gBAAgB,CAAC,GAAE,MAAM,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAAC,aAAA;IAG1F,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI7C,KAAK,IAAI,IAAI;YAYC,WAAW;IAWnB,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK3D,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA8C3D,oBAAoB,CACxB,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAMnD,cAAc,CAClB,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,mBAAmB,CAAC;YA6BjB,UAAU;CAoGzB"}
|
|
@@ -13,8 +13,10 @@ const INDEX_STALENESS_LIMIT_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
13
13
|
export class FileListIndex {
|
|
14
14
|
getFileListCache;
|
|
15
15
|
index = null;
|
|
16
|
+
pathSet = null;
|
|
16
17
|
indexKey = null;
|
|
17
18
|
indexBuiltAt = 0;
|
|
19
|
+
indexFresh = false;
|
|
18
20
|
readyPromise = null;
|
|
19
21
|
constructor(getFileListCache) {
|
|
20
22
|
this.getFileListCache = getFileListCache;
|
|
@@ -27,8 +29,10 @@ export class FileListIndex {
|
|
|
27
29
|
return;
|
|
28
30
|
const indexedWithContent = this.index.size;
|
|
29
31
|
this.index = null;
|
|
32
|
+
this.pathSet = null;
|
|
30
33
|
this.indexKey = null;
|
|
31
34
|
this.indexBuiltAt = 0;
|
|
35
|
+
this.indexFresh = false;
|
|
32
36
|
logger.debug("Cleared file list index", { indexedWithContent });
|
|
33
37
|
}
|
|
34
38
|
async ensureReady() {
|
|
@@ -43,19 +47,35 @@ export class FileListIndex {
|
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
async lookup(normalizedPath) {
|
|
50
|
+
const match = await this.match(normalizedPath);
|
|
51
|
+
return match.status === "hit" ? match.content : undefined;
|
|
52
|
+
}
|
|
53
|
+
async match(normalizedPath) {
|
|
46
54
|
await this.ensureReady();
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
55
|
+
const snapshot = await this.getOrBuild();
|
|
56
|
+
if (!snapshot) {
|
|
49
57
|
logger.debug("No file list cache available");
|
|
50
|
-
return
|
|
58
|
+
return { status: "unavailable", fresh: false };
|
|
51
59
|
}
|
|
52
|
-
|
|
53
|
-
if (!content) {
|
|
60
|
+
if (!snapshot.paths.has(normalizedPath)) {
|
|
54
61
|
logger.debug("Content not in file list index", {
|
|
55
62
|
path: normalizedPath,
|
|
56
|
-
indexSize:
|
|
63
|
+
indexSize: snapshot.content.size,
|
|
64
|
+
fresh: snapshot.fresh,
|
|
57
65
|
});
|
|
58
|
-
return
|
|
66
|
+
return { status: "missing", fresh: snapshot.fresh };
|
|
67
|
+
}
|
|
68
|
+
const content = snapshot.content.get(normalizedPath);
|
|
69
|
+
if (!content) {
|
|
70
|
+
logger.debug("File list index contains path without inline content", {
|
|
71
|
+
path: normalizedPath,
|
|
72
|
+
fresh: snapshot.fresh,
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
status: "present_without_content",
|
|
76
|
+
fresh: snapshot.fresh,
|
|
77
|
+
path: normalizedPath,
|
|
78
|
+
};
|
|
59
79
|
}
|
|
60
80
|
logger.debug("FILE_LIST_CACHE_HIT - serving from file list cache", {
|
|
61
81
|
path: normalizedPath,
|
|
@@ -63,19 +83,43 @@ export class FileListIndex {
|
|
|
63
83
|
contentHash: hashPreview(content),
|
|
64
84
|
contentPreview: previewText(content, 200).replace(/\n/g, "\\n"),
|
|
65
85
|
});
|
|
66
|
-
return
|
|
86
|
+
return {
|
|
87
|
+
status: "hit",
|
|
88
|
+
fresh: snapshot.fresh,
|
|
89
|
+
path: normalizedPath,
|
|
90
|
+
content,
|
|
91
|
+
};
|
|
67
92
|
}
|
|
68
93
|
async findFirstWithContent(normalizedPaths) {
|
|
69
|
-
await this.
|
|
70
|
-
|
|
71
|
-
if (!index)
|
|
94
|
+
const match = await this.findFirstMatch(normalizedPaths);
|
|
95
|
+
if (match.status !== "hit" || !match.path || !match.content)
|
|
72
96
|
return undefined;
|
|
97
|
+
return { path: match.path, content: match.content };
|
|
98
|
+
}
|
|
99
|
+
async findFirstMatch(normalizedPaths) {
|
|
100
|
+
await this.ensureReady();
|
|
101
|
+
const snapshot = await this.getOrBuild();
|
|
102
|
+
if (!snapshot)
|
|
103
|
+
return { status: "unavailable", fresh: false };
|
|
73
104
|
for (const path of normalizedPaths) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
if (!snapshot.paths.has(path))
|
|
106
|
+
continue;
|
|
107
|
+
const content = snapshot.content.get(path);
|
|
108
|
+
if (content) {
|
|
109
|
+
return {
|
|
110
|
+
status: "hit",
|
|
111
|
+
fresh: snapshot.fresh,
|
|
112
|
+
path,
|
|
113
|
+
content,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
status: "present_without_content",
|
|
118
|
+
fresh: snapshot.fresh,
|
|
119
|
+
path,
|
|
120
|
+
};
|
|
77
121
|
}
|
|
78
|
-
return
|
|
122
|
+
return { status: "missing", fresh: snapshot.fresh };
|
|
79
123
|
}
|
|
80
124
|
async getOrBuild() {
|
|
81
125
|
if (!this.getFileListCache) {
|
|
@@ -95,7 +139,12 @@ export class FileListIndex {
|
|
|
95
139
|
indexSize: this.index.size,
|
|
96
140
|
indexAgeMs: age,
|
|
97
141
|
});
|
|
98
|
-
|
|
142
|
+
this.indexFresh = false;
|
|
143
|
+
return {
|
|
144
|
+
content: this.index,
|
|
145
|
+
paths: this.pathSet ?? new Set(),
|
|
146
|
+
fresh: false,
|
|
147
|
+
};
|
|
99
148
|
}
|
|
100
149
|
logger.debug("getOrBuildFileListIndex: in-memory index too stale, discarding", {
|
|
101
150
|
indexSize: this.index.size,
|
|
@@ -103,7 +152,9 @@ export class FileListIndex {
|
|
|
103
152
|
staleLimitMs: INDEX_STALENESS_LIMIT_MS,
|
|
104
153
|
});
|
|
105
154
|
this.index = null;
|
|
155
|
+
this.pathSet = null;
|
|
106
156
|
this.indexKey = null;
|
|
157
|
+
this.indexFresh = false;
|
|
107
158
|
}
|
|
108
159
|
logger.debug("[ReadOperations] getOrBuildFileListIndex: getFileListCache returned null/undefined");
|
|
109
160
|
return null;
|
|
@@ -117,18 +168,27 @@ export class FileListIndex {
|
|
|
117
168
|
sampleContentPreview: cacheCheckSample?.content?.slice(0, 200)?.replace(/\n/g, "\\n"),
|
|
118
169
|
});
|
|
119
170
|
const indexKey = `${fileList.length}:${fileList[0]?.path ?? ""}:${fileList[fileList.length - 1]?.path ?? ""}`;
|
|
120
|
-
if (this.index && this.indexKey === indexKey) {
|
|
171
|
+
if (this.index && this.pathSet && this.indexKey === indexKey) {
|
|
121
172
|
this.indexBuiltAt = Date.now();
|
|
122
|
-
|
|
173
|
+
this.indexFresh = true;
|
|
174
|
+
return {
|
|
175
|
+
content: this.index,
|
|
176
|
+
paths: this.pathSet,
|
|
177
|
+
fresh: true,
|
|
178
|
+
};
|
|
123
179
|
}
|
|
124
180
|
const index = new Map();
|
|
181
|
+
const pathSet = new Set();
|
|
125
182
|
for (const file of fileList) {
|
|
183
|
+
pathSet.add(file.path);
|
|
126
184
|
if (file.content)
|
|
127
185
|
index.set(file.path, file.content);
|
|
128
186
|
}
|
|
129
187
|
this.index = index;
|
|
188
|
+
this.pathSet = pathSet;
|
|
130
189
|
this.indexKey = indexKey;
|
|
131
190
|
this.indexBuiltAt = Date.now();
|
|
191
|
+
this.indexFresh = true;
|
|
132
192
|
const sampleFile = fileList.find((f) => /welcome/i.test(f.path));
|
|
133
193
|
const sampleContent = sampleFile?.content;
|
|
134
194
|
logger.debug("Built file list index", {
|
|
@@ -139,6 +199,10 @@ export class FileListIndex {
|
|
|
139
199
|
sampleContentHash: sampleContent ? hashPreview(sampleContent) : undefined,
|
|
140
200
|
sampleContentPreview: sampleContent?.slice(0, 200)?.replace(/\n/g, "\\n"),
|
|
141
201
|
});
|
|
142
|
-
return
|
|
202
|
+
return {
|
|
203
|
+
content: index,
|
|
204
|
+
paths: pathSet,
|
|
205
|
+
fresh: true,
|
|
206
|
+
};
|
|
143
207
|
}
|
|
144
208
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { READ_OPERATION_EXTENSION_PRIORITY } from "./extension-priority.js";
|
|
2
2
|
import type { ResolvedContentContext } from "./types.js";
|
|
3
3
|
export { READ_OPERATION_EXTENSION_PRIORITY };
|
|
4
|
+
export type NotFoundLikeError = Error & {
|
|
5
|
+
code?: string;
|
|
6
|
+
};
|
|
4
7
|
interface ReadContextProviderLike {
|
|
5
8
|
isProductionMode: () => boolean;
|
|
6
9
|
isPersistentCacheInvalidated?: (prefix: string) => boolean;
|
|
@@ -28,9 +31,11 @@ interface BuildReadFetchStateOptions {
|
|
|
28
31
|
export declare function assertProjectSourcePath(normalizedPath: string): void;
|
|
29
32
|
export declare function buildReadFetchState(options: BuildReadFetchStateOptions): ReadFetchState;
|
|
30
33
|
export declare function getResolvedCacheKey(cacheKeyPrefix: string, normalizedResolvedPath: string): string;
|
|
34
|
+
export declare function buildExtensionCandidatePaths(basePath: string): string[];
|
|
31
35
|
export declare function splitKnownFileExtension(apiPath: string): {
|
|
32
36
|
originalExtension: string;
|
|
33
37
|
basePath: string;
|
|
34
38
|
} | null;
|
|
35
39
|
export declare function isNotFoundLikeError(error: unknown): boolean;
|
|
40
|
+
export declare function createNotFoundLikeError(path: string): NotFoundLikeError;
|
|
36
41
|
//# sourceMappingURL=read-operations-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-operations-helpers.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/read-operations-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iCAAiC,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,iCAAiC,EAAE,CAAC;AAE7C,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,4BAA4B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3D,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5D;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,0BAA0B;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC9C,eAAe,CAAC,EAAE,uBAAuB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C;AAED,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAOpE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,CA2BvF;AAED,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,MAAM,EACtB,sBAAsB,EAAE,MAAM,GAC7B,MAAM,CAER;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,GACd;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CASxD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG3D"}
|
|
1
|
+
{"version":3,"file":"read-operations-helpers.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/read-operations-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iCAAiC,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,iCAAiC,EAAE,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,4BAA4B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3D,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5D;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,0BAA0B;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC9C,eAAe,CAAC,EAAE,uBAAuB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C;AAED,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAOpE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,CA2BvF;AAED,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,MAAM,EACtB,sBAAsB,EAAE,MAAM,GAC7B,MAAM,CAER;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvE;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,GACd;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CASxD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG3D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAEvE"}
|
|
@@ -40,6 +40,9 @@ export function buildReadFetchState(options) {
|
|
|
40
40
|
export function getResolvedCacheKey(cacheKeyPrefix, normalizedResolvedPath) {
|
|
41
41
|
return `${cacheKeyPrefix}:${normalizedResolvedPath}`;
|
|
42
42
|
}
|
|
43
|
+
export function buildExtensionCandidatePaths(basePath) {
|
|
44
|
+
return READ_OPERATION_EXTENSION_PRIORITY.map((ext) => `${basePath}${ext}`);
|
|
45
|
+
}
|
|
43
46
|
export function splitKnownFileExtension(apiPath) {
|
|
44
47
|
const extMatch = apiPath.match(/\.(tsx|ts|jsx|js|mdx|md)$/);
|
|
45
48
|
if (!extMatch)
|
|
@@ -54,3 +57,6 @@ export function isNotFoundLikeError(error) {
|
|
|
54
57
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
55
58
|
return errorMessage.includes("404") || errorMessage.includes("Not Found");
|
|
56
59
|
}
|
|
60
|
+
export function createNotFoundLikeError(path) {
|
|
61
|
+
return Object.assign(new Error(`404 Not Found: ${path}`), { code: "ENOENT" });
|
|
62
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-operations.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/read-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAKnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"read-operations.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/read-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAKnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAWtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAI9B,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,MAAM,sBAAsB,GAAG,IAAI,CAAC;IACvD,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,OAAO,CACzB,KAAK,CAAC;QACJ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,GAAG,SAAS,CACf,CAAC;IACF,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,0EAA0E;IAC1E,4BAA4B,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3D,+CAA+C;IAC/C,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5D;AAUD,qBAAa,cAAc;IAWvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAfpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAI9B;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAA6B;gBAGnD,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,SAAS,EAChB,UAAU,EAAE,cAAc,EAC1B,eAAe,CAAC,EAAE,sBAAsB,YAAA,EACxC,kBAAkB,CAAC,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,aAAA,EAC7C,gBAAgB,CAAC,GAAE,MAAM,OAAO,CAC/C,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CACtD,aAAA;IAKH,uBAAuB,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIrD,kBAAkB,IAAI,IAAI;IAK1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAY3C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAY3C,OAAO,CAAC,mBAAmB;YAsBb,+BAA+B;YA0C/B,mBAAmB;IA0CjC,OAAO,CAAC,kBAAkB;YAoFZ,2BAA2B;YA8D3B,uCAAuC;IAmCrD,OAAO,CAAC,oBAAoB;YAgBd,YAAY;YA6JZ,qBAAqB;YA8DrB,qBAAqB;YAgDrB,+BAA+B;YA8D/B,iBAAiB;CAyBhC"}
|
|
@@ -4,7 +4,7 @@ import { logContentMetric } from "./content-metrics.js";
|
|
|
4
4
|
import { FileListIndex } from "./file-list-index.js";
|
|
5
5
|
import { InFlightRequestDeduper } from "./in-flight-dedupe.js";
|
|
6
6
|
import { getRequestScopedFile, setRequestScopedFile } from "./multi-project-adapter.js";
|
|
7
|
-
import { assertProjectSourcePath, buildReadFetchState, getResolvedCacheKey, isNotFoundLikeError, READ_OPERATION_EXTENSION_PRIORITY as EXTENSION_PRIORITY, splitKnownFileExtension, } from "./read-operations-helpers.js";
|
|
7
|
+
import { assertProjectSourcePath, buildExtensionCandidatePaths, buildReadFetchState, createNotFoundLikeError, getResolvedCacheKey, isNotFoundLikeError, READ_OPERATION_EXTENSION_PRIORITY as EXTENSION_PRIORITY, splitKnownFileExtension, } from "./read-operations-helpers.js";
|
|
8
8
|
export { endRequestMetrics, getContentMetricsSnapshot, resetContentMetrics, startRequestMetrics, } from "./content-metrics.js";
|
|
9
9
|
const logger = baseLogger.component("read-operations");
|
|
10
10
|
const IN_FLIGHT_REQUEST_TIMEOUT_MS = 15_000;
|
|
@@ -113,10 +113,15 @@ export class ReadOperations {
|
|
|
113
113
|
// - File list is refreshed on every WebSocket poke (websocket-manager.ts:483-500)
|
|
114
114
|
// - Request-scoped cache ensures consistency within a single render
|
|
115
115
|
// - Persistent cache is only written for production mode (to avoid staleness risk in preview)
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
if (skipPersistentCaches) {
|
|
117
|
+
logger.debug("Skipping file list cache due to invalidation", {
|
|
118
|
+
path: normalizedPath,
|
|
119
|
+
cacheKeyPrefix,
|
|
120
|
+
});
|
|
121
|
+
return { status: "unavailable", fresh: false };
|
|
122
|
+
}
|
|
123
|
+
const match = await this.fileListIndex.match(normalizedPath);
|
|
124
|
+
if (match.status === "hit" && match.content) {
|
|
120
125
|
logContentMetric("FILE_LIST_HIT", {
|
|
121
126
|
path: normalizedPath,
|
|
122
127
|
mode: ctx?.sourceType ?? "unknown",
|
|
@@ -126,25 +131,13 @@ export class ReadOperations {
|
|
|
126
131
|
// Only cache to persistent storage for production mode
|
|
127
132
|
// Preview mode uses file list cache directly without persisting (fresher, WebSocket-driven)
|
|
128
133
|
if (isProduction) {
|
|
129
|
-
this.cache.set(cacheKey,
|
|
134
|
+
this.cache.set(cacheKey, match.content);
|
|
130
135
|
}
|
|
131
|
-
setRequestScopedFile(cacheKey,
|
|
132
|
-
return fileListContent;
|
|
136
|
+
setRequestScopedFile(cacheKey, match.content);
|
|
133
137
|
}
|
|
134
|
-
|
|
135
|
-
logContentMetric("CACHE_MISS", {
|
|
136
|
-
path: normalizedPath,
|
|
137
|
-
mode: ctx?.sourceType ?? "unknown",
|
|
138
|
-
missReason: "invalidation",
|
|
139
|
-
isPreviewMode,
|
|
140
|
-
});
|
|
141
|
-
logger.debug("Skipping file list cache due to invalidation", {
|
|
142
|
-
path: normalizedPath,
|
|
143
|
-
cacheKeyPrefix,
|
|
144
|
-
});
|
|
145
|
-
return null;
|
|
138
|
+
return match;
|
|
146
139
|
}
|
|
147
|
-
setupInFlightFetch(normalizedPath, apiPath, cacheKey, isPublished, isProduction, isPreviewMode, ctx) {
|
|
140
|
+
setupInFlightFetch(normalizedPath, apiPath, cacheKey, isPublished, isProduction, isPreviewMode, ctx, missReason) {
|
|
148
141
|
const cleanupResult = this.inFlightRequests.cleanup();
|
|
149
142
|
if (cleanupResult) {
|
|
150
143
|
logger.warn("Cleaned up in-flight requests", cleanupResult);
|
|
@@ -159,11 +152,10 @@ export class ReadOperations {
|
|
|
159
152
|
return existingEntry.promise;
|
|
160
153
|
}
|
|
161
154
|
// Track why we're making a network fetch (for optimization analysis)
|
|
162
|
-
const hasFileListCache = !!this.getFileListCache;
|
|
163
155
|
logContentMetric("CACHE_MISS", {
|
|
164
156
|
path: normalizedPath,
|
|
165
157
|
mode: ctx?.sourceType ?? "unknown",
|
|
166
|
-
missReason
|
|
158
|
+
missReason,
|
|
167
159
|
isPreviewMode,
|
|
168
160
|
});
|
|
169
161
|
// THIS IS A NETWORK FETCH - every call here = API round trip
|
|
@@ -248,10 +240,10 @@ export class ReadOperations {
|
|
|
248
240
|
}
|
|
249
241
|
}
|
|
250
242
|
async tryResolveExtensionlessPathFromFileList(normalizedPath, cacheKeyPrefix, cacheKey, isProduction, ctx, isPreviewMode) {
|
|
251
|
-
const candidatePaths =
|
|
252
|
-
const resolved = await this.fileListIndex.
|
|
253
|
-
if (!resolved)
|
|
254
|
-
return
|
|
243
|
+
const candidatePaths = buildExtensionCandidatePaths(normalizedPath);
|
|
244
|
+
const resolved = await this.fileListIndex.findFirstMatch(candidatePaths);
|
|
245
|
+
if (resolved.status !== "hit" || !resolved.path || !resolved.content)
|
|
246
|
+
return resolved;
|
|
255
247
|
const resolvedCacheKey = getResolvedCacheKey(cacheKeyPrefix, resolved.path);
|
|
256
248
|
this.extensionResolutionCache.set(normalizedPath, resolved.path);
|
|
257
249
|
logContentMetric("FILE_LIST_HIT", {
|
|
@@ -268,7 +260,7 @@ export class ReadOperations {
|
|
|
268
260
|
resolvedCacheKey: resolvedCacheKey === cacheKey ? undefined : resolvedCacheKey,
|
|
269
261
|
});
|
|
270
262
|
this.cacheResolvedContent(cacheKey, resolvedCacheKey, resolved.content, isProduction);
|
|
271
|
-
return resolved
|
|
263
|
+
return resolved;
|
|
272
264
|
}
|
|
273
265
|
cacheResolvedContent(cacheKey, resolvedCacheKey, content, persistToCache) {
|
|
274
266
|
if (persistToCache) {
|
|
@@ -309,20 +301,47 @@ export class ReadOperations {
|
|
|
309
301
|
const persistentCached = await this.getProductionPersistentCacheHit(normalizedPath, cacheKeyPrefix, cacheKey, isProduction, skipPersistentCaches, currentReleaseId, isPrefixInvalidated, ctx);
|
|
310
302
|
if (persistentCached)
|
|
311
303
|
return persistentCached;
|
|
312
|
-
const
|
|
313
|
-
if (
|
|
314
|
-
return
|
|
304
|
+
const fileListMatch = await this.getFileListCacheHit(normalizedPath, cacheKeyPrefix, cacheKey, isProduction, skipPersistentCaches, isPreviewMode, ctx);
|
|
305
|
+
if (fileListMatch.status === "hit" && fileListMatch.content)
|
|
306
|
+
return fileListMatch.content;
|
|
307
|
+
if (fileListMatch.status === "present_without_content") {
|
|
308
|
+
return this.setupInFlightFetch(normalizedPath, apiPath, cacheKey, isPublished, isProduction, isPreviewMode, ctx, "indexed_without_content");
|
|
309
|
+
}
|
|
315
310
|
if (!hasKnownExt) {
|
|
316
311
|
if (!skipPersistentCaches) {
|
|
317
312
|
const resolvedFromFileList = await this.tryResolveExtensionlessPathFromFileList(normalizedPath, cacheKeyPrefix, cacheKey, isProduction, ctx, isPreviewMode);
|
|
318
|
-
if (resolvedFromFileList)
|
|
319
|
-
return resolvedFromFileList;
|
|
313
|
+
if (resolvedFromFileList.status === "hit" && resolvedFromFileList.content) {
|
|
314
|
+
return resolvedFromFileList.content;
|
|
315
|
+
}
|
|
316
|
+
if (resolvedFromFileList.status === "present_without_content" &&
|
|
317
|
+
resolvedFromFileList.path) {
|
|
318
|
+
const resolvedCacheKey = getResolvedCacheKey(cacheKeyPrefix, resolvedFromFileList.path);
|
|
319
|
+
const resolvedApiPath = this.getOriginalApiPath?.(resolvedFromFileList.path) ??
|
|
320
|
+
resolvedFromFileList.path;
|
|
321
|
+
const fetchedResolved = await this.setupInFlightFetch(resolvedFromFileList.path, resolvedApiPath, resolvedCacheKey, isPublished, isProduction, isPreviewMode, ctx, "indexed_without_content");
|
|
322
|
+
this.extensionResolutionCache.set(normalizedPath, resolvedFromFileList.path);
|
|
323
|
+
this.cacheResolvedContent(cacheKey, resolvedCacheKey, fetchedResolved, isProduction && !skipPersistentCaches);
|
|
324
|
+
return fetchedResolved;
|
|
325
|
+
}
|
|
326
|
+
if (fileListMatch.status === "missing" &&
|
|
327
|
+
fileListMatch.fresh &&
|
|
328
|
+
resolvedFromFileList.status === "missing" &&
|
|
329
|
+
resolvedFromFileList.fresh) {
|
|
330
|
+
throw createNotFoundLikeError(normalizedPath);
|
|
331
|
+
}
|
|
320
332
|
}
|
|
321
333
|
const resolved = await this.tryResolveExtensionlessPath(apiPath, cacheKeyPrefix, cacheKey, isProduction, skipPersistentCaches);
|
|
322
334
|
if (resolved)
|
|
323
335
|
return resolved;
|
|
324
336
|
}
|
|
325
|
-
|
|
337
|
+
if (fileListMatch.status === "missing" && fileListMatch.fresh) {
|
|
338
|
+
throw createNotFoundLikeError(normalizedPath);
|
|
339
|
+
}
|
|
340
|
+
return this.setupInFlightFetch(normalizedPath, apiPath, cacheKey, isPublished, isProduction, isPreviewMode, ctx, skipPersistentCaches
|
|
341
|
+
? "invalidation"
|
|
342
|
+
: fileListMatch.status === "missing" && fileListMatch.fresh
|
|
343
|
+
? "not_in_filelist"
|
|
344
|
+
: "no_filelist_cache");
|
|
326
345
|
}
|
|
327
346
|
async fetchPublishedContent(normalizedPath, apiPath, cacheKey, releaseId, environmentName, shouldCache) {
|
|
328
347
|
logger.debug("Fetching published content", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-resolver.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/page-resolution/page-resolver.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAoCvD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAiB;gBAEpB,OAAO,EAAE,mBAAmB;IAOxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"page-resolver.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/page-resolution/page-resolver.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAoCvD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAiB;gBAEpB,OAAO,EAAE,mBAAmB;IAOxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiExC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAoCxB,sBAAsB;IAoD9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAY1C,aAAa,IAAI,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC;CAShD"}
|
|
@@ -56,10 +56,16 @@ export class PageResolver {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
|
-
// Auto mode stays structural:
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
if (
|
|
59
|
+
// Auto mode stays structural: detect the dominant router once, then keep
|
|
60
|
+
// pages fallback available for mixed or in-transition projects.
|
|
61
|
+
const useAppRouter = await detectAppRouter(this.projectDir, this.config, this.adapter, { projectId: this.projectId });
|
|
62
|
+
if (useAppRouter) {
|
|
63
|
+
pageInfo = await getAppRouteEntity(this.projectDir, slug, this.adapter, appDirName);
|
|
64
|
+
if (!pageInfo) {
|
|
65
|
+
pageInfo = await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
63
69
|
pageInfo = await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
64
70
|
}
|
|
65
71
|
}
|
package/esm/src/utils/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Keep in sync with deno.json version.
|
|
2
2
|
// scripts/release.ts updates this constant during releases.
|
|
3
|
-
export const VERSION = "0.1.
|
|
3
|
+
export const VERSION = "0.1.87";
|
|
4
4
|
export const SERVER_START_TIME = Date.now();
|
|
5
5
|
export function createBuildVersion(projectUpdatedAt) {
|
|
6
6
|
return {
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -8,7 +8,12 @@ import {
|
|
|
8
8
|
const logger = baseLogger.component("content-metrics");
|
|
9
9
|
|
|
10
10
|
type FileType = "page" | "layout" | "component" | "api" | "data" | "config" | "other";
|
|
11
|
-
export type MissReason =
|
|
11
|
+
export type MissReason =
|
|
12
|
+
| "cold_start"
|
|
13
|
+
| "not_in_filelist"
|
|
14
|
+
| "invalidation"
|
|
15
|
+
| "no_filelist_cache"
|
|
16
|
+
| "indexed_without_content";
|
|
12
17
|
|
|
13
18
|
interface PerRequestMetrics {
|
|
14
19
|
startTime: number;
|
|
@@ -52,7 +57,13 @@ function createFreshRequestMetrics(): PerRequestMetrics {
|
|
|
52
57
|
networkFetches: 0,
|
|
53
58
|
networkMs: 0,
|
|
54
59
|
fetchesByType: { page: 0, layout: 0, component: 0, api: 0, data: 0, config: 0, other: 0 },
|
|
55
|
-
missReasons: {
|
|
60
|
+
missReasons: {
|
|
61
|
+
cold_start: 0,
|
|
62
|
+
not_in_filelist: 0,
|
|
63
|
+
invalidation: 0,
|
|
64
|
+
no_filelist_cache: 0,
|
|
65
|
+
indexed_without_content: 0,
|
|
66
|
+
},
|
|
56
67
|
isPreviewMode: null,
|
|
57
68
|
filesAccessed: new Set(),
|
|
58
69
|
};
|
|
@@ -7,6 +7,13 @@ interface FileListCacheEntry {
|
|
|
7
7
|
content?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface FileListMatchResult {
|
|
11
|
+
status: "unavailable" | "missing" | "present_without_content" | "hit";
|
|
12
|
+
fresh: boolean;
|
|
13
|
+
path?: string;
|
|
14
|
+
content?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
function hashPreview(content: string): number {
|
|
11
18
|
return content
|
|
12
19
|
.slice(0, 100)
|
|
@@ -22,8 +29,10 @@ const INDEX_STALENESS_LIMIT_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
22
29
|
|
|
23
30
|
export class FileListIndex {
|
|
24
31
|
private index: Map<string, string> | null = null;
|
|
32
|
+
private pathSet: Set<string> | null = null;
|
|
25
33
|
private indexKey: string | null = null;
|
|
26
34
|
private indexBuiltAt = 0;
|
|
35
|
+
private indexFresh = false;
|
|
27
36
|
private readyPromise: Promise<void> | null = null;
|
|
28
37
|
|
|
29
38
|
constructor(
|
|
@@ -39,8 +48,10 @@ export class FileListIndex {
|
|
|
39
48
|
|
|
40
49
|
const indexedWithContent = this.index.size;
|
|
41
50
|
this.index = null;
|
|
51
|
+
this.pathSet = null;
|
|
42
52
|
this.indexKey = null;
|
|
43
53
|
this.indexBuiltAt = 0;
|
|
54
|
+
this.indexFresh = false;
|
|
44
55
|
logger.debug("Cleared file list index", { indexedWithContent });
|
|
45
56
|
}
|
|
46
57
|
|
|
@@ -56,21 +67,39 @@ export class FileListIndex {
|
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
async lookup(normalizedPath: string): Promise<string | undefined> {
|
|
70
|
+
const match = await this.match(normalizedPath);
|
|
71
|
+
return match.status === "hit" ? match.content : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async match(normalizedPath: string): Promise<FileListMatchResult> {
|
|
59
75
|
await this.ensureReady();
|
|
60
76
|
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
77
|
+
const snapshot = await this.getOrBuild();
|
|
78
|
+
if (!snapshot) {
|
|
63
79
|
logger.debug("No file list cache available");
|
|
64
|
-
return
|
|
80
|
+
return { status: "unavailable", fresh: false };
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
if (!content) {
|
|
83
|
+
if (!snapshot.paths.has(normalizedPath)) {
|
|
69
84
|
logger.debug("Content not in file list index", {
|
|
70
85
|
path: normalizedPath,
|
|
71
|
-
indexSize:
|
|
86
|
+
indexSize: snapshot.content.size,
|
|
87
|
+
fresh: snapshot.fresh,
|
|
72
88
|
});
|
|
73
|
-
return
|
|
89
|
+
return { status: "missing", fresh: snapshot.fresh };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const content = snapshot.content.get(normalizedPath);
|
|
93
|
+
if (!content) {
|
|
94
|
+
logger.debug("File list index contains path without inline content", {
|
|
95
|
+
path: normalizedPath,
|
|
96
|
+
fresh: snapshot.fresh,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
status: "present_without_content",
|
|
100
|
+
fresh: snapshot.fresh,
|
|
101
|
+
path: normalizedPath,
|
|
102
|
+
};
|
|
74
103
|
}
|
|
75
104
|
|
|
76
105
|
logger.debug("FILE_LIST_CACHE_HIT - serving from file list cache", {
|
|
@@ -80,26 +109,60 @@ export class FileListIndex {
|
|
|
80
109
|
contentPreview: previewText(content, 200).replace(/\n/g, "\\n"),
|
|
81
110
|
});
|
|
82
111
|
|
|
83
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
status: "hit",
|
|
114
|
+
fresh: snapshot.fresh,
|
|
115
|
+
path: normalizedPath,
|
|
116
|
+
content,
|
|
117
|
+
};
|
|
84
118
|
}
|
|
85
119
|
|
|
86
120
|
async findFirstWithContent(
|
|
87
121
|
normalizedPaths: string[],
|
|
88
122
|
): Promise<{ path: string; content: string } | undefined> {
|
|
123
|
+
const match = await this.findFirstMatch(normalizedPaths);
|
|
124
|
+
if (match.status !== "hit" || !match.path || !match.content) return undefined;
|
|
125
|
+
return { path: match.path, content: match.content };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async findFirstMatch(
|
|
129
|
+
normalizedPaths: string[],
|
|
130
|
+
): Promise<FileListMatchResult> {
|
|
89
131
|
await this.ensureReady();
|
|
90
132
|
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
133
|
+
const snapshot = await this.getOrBuild();
|
|
134
|
+
if (!snapshot) return { status: "unavailable", fresh: false };
|
|
93
135
|
|
|
94
136
|
for (const path of normalizedPaths) {
|
|
95
|
-
|
|
96
|
-
|
|
137
|
+
if (!snapshot.paths.has(path)) continue;
|
|
138
|
+
|
|
139
|
+
const content = snapshot.content.get(path);
|
|
140
|
+
if (content) {
|
|
141
|
+
return {
|
|
142
|
+
status: "hit",
|
|
143
|
+
fresh: snapshot.fresh,
|
|
144
|
+
path,
|
|
145
|
+
content,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
status: "present_without_content",
|
|
151
|
+
fresh: snapshot.fresh,
|
|
152
|
+
path,
|
|
153
|
+
};
|
|
97
154
|
}
|
|
98
155
|
|
|
99
|
-
return
|
|
156
|
+
return { status: "missing", fresh: snapshot.fresh };
|
|
100
157
|
}
|
|
101
158
|
|
|
102
|
-
private async getOrBuild(): Promise<
|
|
159
|
+
private async getOrBuild(): Promise<
|
|
160
|
+
{
|
|
161
|
+
content: Map<string, string>;
|
|
162
|
+
paths: Set<string>;
|
|
163
|
+
fresh: boolean;
|
|
164
|
+
} | null
|
|
165
|
+
> {
|
|
103
166
|
if (!this.getFileListCache) {
|
|
104
167
|
logger.debug("getOrBuildFileListIndex: no getFileListCache function");
|
|
105
168
|
return null;
|
|
@@ -118,7 +181,12 @@ export class FileListIndex {
|
|
|
118
181
|
indexSize: this.index.size,
|
|
119
182
|
indexAgeMs: age,
|
|
120
183
|
});
|
|
121
|
-
|
|
184
|
+
this.indexFresh = false;
|
|
185
|
+
return {
|
|
186
|
+
content: this.index,
|
|
187
|
+
paths: this.pathSet ?? new Set<string>(),
|
|
188
|
+
fresh: false,
|
|
189
|
+
};
|
|
122
190
|
}
|
|
123
191
|
logger.debug("getOrBuildFileListIndex: in-memory index too stale, discarding", {
|
|
124
192
|
indexSize: this.index.size,
|
|
@@ -126,7 +194,9 @@ export class FileListIndex {
|
|
|
126
194
|
staleLimitMs: INDEX_STALENESS_LIMIT_MS,
|
|
127
195
|
});
|
|
128
196
|
this.index = null;
|
|
197
|
+
this.pathSet = null;
|
|
129
198
|
this.indexKey = null;
|
|
199
|
+
this.indexFresh = false;
|
|
130
200
|
}
|
|
131
201
|
logger.debug(
|
|
132
202
|
"[ReadOperations] getOrBuildFileListIndex: getFileListCache returned null/undefined",
|
|
@@ -146,19 +216,28 @@ export class FileListIndex {
|
|
|
146
216
|
const indexKey = `${fileList.length}:${fileList[0]?.path ?? ""}:${
|
|
147
217
|
fileList[fileList.length - 1]?.path ?? ""
|
|
148
218
|
}`;
|
|
149
|
-
if (this.index && this.indexKey === indexKey) {
|
|
219
|
+
if (this.index && this.pathSet && this.indexKey === indexKey) {
|
|
150
220
|
this.indexBuiltAt = Date.now();
|
|
151
|
-
|
|
221
|
+
this.indexFresh = true;
|
|
222
|
+
return {
|
|
223
|
+
content: this.index,
|
|
224
|
+
paths: this.pathSet,
|
|
225
|
+
fresh: true,
|
|
226
|
+
};
|
|
152
227
|
}
|
|
153
228
|
|
|
154
229
|
const index = new Map<string, string>();
|
|
230
|
+
const pathSet = new Set<string>();
|
|
155
231
|
for (const file of fileList) {
|
|
232
|
+
pathSet.add(file.path);
|
|
156
233
|
if (file.content) index.set(file.path, file.content);
|
|
157
234
|
}
|
|
158
235
|
|
|
159
236
|
this.index = index;
|
|
237
|
+
this.pathSet = pathSet;
|
|
160
238
|
this.indexKey = indexKey;
|
|
161
239
|
this.indexBuiltAt = Date.now();
|
|
240
|
+
this.indexFresh = true;
|
|
162
241
|
|
|
163
242
|
const sampleFile = fileList.find((f) => /welcome/i.test(f.path));
|
|
164
243
|
const sampleContent = sampleFile?.content;
|
|
@@ -171,6 +250,10 @@ export class FileListIndex {
|
|
|
171
250
|
sampleContentPreview: sampleContent?.slice(0, 200)?.replace(/\n/g, "\\n"),
|
|
172
251
|
});
|
|
173
252
|
|
|
174
|
-
return
|
|
253
|
+
return {
|
|
254
|
+
content: index,
|
|
255
|
+
paths: pathSet,
|
|
256
|
+
fresh: true,
|
|
257
|
+
};
|
|
175
258
|
}
|
|
176
259
|
}
|
|
@@ -6,6 +6,8 @@ import type { ResolvedContentContext } from "./types.js";
|
|
|
6
6
|
|
|
7
7
|
export { READ_OPERATION_EXTENSION_PRIORITY };
|
|
8
8
|
|
|
9
|
+
export type NotFoundLikeError = Error & { code?: string };
|
|
10
|
+
|
|
9
11
|
interface ReadContextProviderLike {
|
|
10
12
|
isProductionMode: () => boolean;
|
|
11
13
|
isPersistentCacheInvalidated?: (prefix: string) => boolean;
|
|
@@ -78,6 +80,10 @@ export function getResolvedCacheKey(
|
|
|
78
80
|
return `${cacheKeyPrefix}:${normalizedResolvedPath}`;
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
export function buildExtensionCandidatePaths(basePath: string): string[] {
|
|
84
|
+
return READ_OPERATION_EXTENSION_PRIORITY.map((ext) => `${basePath}${ext}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
export function splitKnownFileExtension(
|
|
82
88
|
apiPath: string,
|
|
83
89
|
): { originalExtension: string; basePath: string } | null {
|
|
@@ -95,3 +101,7 @@ export function isNotFoundLikeError(error: unknown): boolean {
|
|
|
95
101
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
96
102
|
return errorMessage.includes("404") || errorMessage.includes("Not Found");
|
|
97
103
|
}
|
|
104
|
+
|
|
105
|
+
export function createNotFoundLikeError(path: string): NotFoundLikeError {
|
|
106
|
+
return Object.assign(new Error(`404 Not Found: ${path}`), { code: "ENOENT" });
|
|
107
|
+
}
|
|
@@ -3,13 +3,15 @@ import { withSpan } from "../../../../observability/tracing/otlp-setup.js";
|
|
|
3
3
|
import type { VeryfrontApiClient } from "../../veryfront-api-client/index.js";
|
|
4
4
|
import { FileCache } from "../cache/file-cache.js";
|
|
5
5
|
import { logContentMetric, type MissReason } from "./content-metrics.js";
|
|
6
|
-
import { FileListIndex } from "./file-list-index.js";
|
|
6
|
+
import { FileListIndex, type FileListMatchResult } from "./file-list-index.js";
|
|
7
7
|
import { InFlightRequestDeduper } from "./in-flight-dedupe.js";
|
|
8
8
|
import { getRequestScopedFile, setRequestScopedFile } from "./multi-project-adapter.js";
|
|
9
9
|
import { PathNormalizer } from "./path-normalizer.js";
|
|
10
10
|
import {
|
|
11
11
|
assertProjectSourcePath,
|
|
12
|
+
buildExtensionCandidatePaths,
|
|
12
13
|
buildReadFetchState,
|
|
14
|
+
createNotFoundLikeError,
|
|
13
15
|
getResolvedCacheKey,
|
|
14
16
|
isNotFoundLikeError,
|
|
15
17
|
READ_OPERATION_EXTENSION_PRIORITY as EXTENSION_PRIORITY,
|
|
@@ -184,17 +186,23 @@ export class ReadOperations {
|
|
|
184
186
|
skipPersistentCaches: boolean,
|
|
185
187
|
isPreviewMode: boolean,
|
|
186
188
|
ctx: ResolvedContentContext | null,
|
|
187
|
-
): Promise<
|
|
189
|
+
): Promise<FileListMatchResult> {
|
|
188
190
|
// File list cache is enabled for BOTH preview and production modes.
|
|
189
191
|
// The file list is an in-memory index built from API response at init, updated by WebSocket pokes.
|
|
190
192
|
// This is safe because:
|
|
191
193
|
// - File list is refreshed on every WebSocket poke (websocket-manager.ts:483-500)
|
|
192
194
|
// - Request-scoped cache ensures consistency within a single render
|
|
193
195
|
// - Persistent cache is only written for production mode (to avoid staleness risk in preview)
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
if (skipPersistentCaches) {
|
|
197
|
+
logger.debug("Skipping file list cache due to invalidation", {
|
|
198
|
+
path: normalizedPath,
|
|
199
|
+
cacheKeyPrefix,
|
|
200
|
+
});
|
|
201
|
+
return { status: "unavailable", fresh: false };
|
|
202
|
+
}
|
|
197
203
|
|
|
204
|
+
const match = await this.fileListIndex.match(normalizedPath);
|
|
205
|
+
if (match.status === "hit" && match.content) {
|
|
198
206
|
logContentMetric("FILE_LIST_HIT", {
|
|
199
207
|
path: normalizedPath,
|
|
200
208
|
mode: ctx?.sourceType ?? "unknown",
|
|
@@ -204,24 +212,12 @@ export class ReadOperations {
|
|
|
204
212
|
// Only cache to persistent storage for production mode
|
|
205
213
|
// Preview mode uses file list cache directly without persisting (fresher, WebSocket-driven)
|
|
206
214
|
if (isProduction) {
|
|
207
|
-
this.cache.set(cacheKey,
|
|
215
|
+
this.cache.set(cacheKey, match.content);
|
|
208
216
|
}
|
|
209
|
-
setRequestScopedFile(cacheKey,
|
|
210
|
-
return fileListContent;
|
|
217
|
+
setRequestScopedFile(cacheKey, match.content);
|
|
211
218
|
}
|
|
212
219
|
|
|
213
|
-
|
|
214
|
-
logContentMetric("CACHE_MISS", {
|
|
215
|
-
path: normalizedPath,
|
|
216
|
-
mode: ctx?.sourceType ?? "unknown",
|
|
217
|
-
missReason: "invalidation" as MissReason,
|
|
218
|
-
isPreviewMode,
|
|
219
|
-
});
|
|
220
|
-
logger.debug("Skipping file list cache due to invalidation", {
|
|
221
|
-
path: normalizedPath,
|
|
222
|
-
cacheKeyPrefix,
|
|
223
|
-
});
|
|
224
|
-
return null;
|
|
220
|
+
return match;
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
private setupInFlightFetch(
|
|
@@ -232,6 +228,7 @@ export class ReadOperations {
|
|
|
232
228
|
isProduction: boolean,
|
|
233
229
|
isPreviewMode: boolean,
|
|
234
230
|
ctx: ResolvedContentContext | null,
|
|
231
|
+
missReason: MissReason,
|
|
235
232
|
): Promise<string> {
|
|
236
233
|
const cleanupResult = this.inFlightRequests.cleanup();
|
|
237
234
|
if (cleanupResult) {
|
|
@@ -249,11 +246,10 @@ export class ReadOperations {
|
|
|
249
246
|
}
|
|
250
247
|
|
|
251
248
|
// Track why we're making a network fetch (for optimization analysis)
|
|
252
|
-
const hasFileListCache = !!this.getFileListCache;
|
|
253
249
|
logContentMetric("CACHE_MISS", {
|
|
254
250
|
path: normalizedPath,
|
|
255
251
|
mode: ctx?.sourceType ?? "unknown",
|
|
256
|
-
missReason
|
|
252
|
+
missReason,
|
|
257
253
|
isPreviewMode,
|
|
258
254
|
});
|
|
259
255
|
|
|
@@ -377,10 +373,10 @@ export class ReadOperations {
|
|
|
377
373
|
isProduction: boolean,
|
|
378
374
|
ctx: ResolvedContentContext | null,
|
|
379
375
|
isPreviewMode: boolean,
|
|
380
|
-
): Promise<
|
|
381
|
-
const candidatePaths =
|
|
382
|
-
const resolved = await this.fileListIndex.
|
|
383
|
-
if (!resolved) return
|
|
376
|
+
): Promise<FileListMatchResult> {
|
|
377
|
+
const candidatePaths = buildExtensionCandidatePaths(normalizedPath);
|
|
378
|
+
const resolved = await this.fileListIndex.findFirstMatch(candidatePaths);
|
|
379
|
+
if (resolved.status !== "hit" || !resolved.path || !resolved.content) return resolved;
|
|
384
380
|
|
|
385
381
|
const resolvedCacheKey = getResolvedCacheKey(cacheKeyPrefix, resolved.path);
|
|
386
382
|
|
|
@@ -402,7 +398,7 @@ export class ReadOperations {
|
|
|
402
398
|
|
|
403
399
|
this.cacheResolvedContent(cacheKey, resolvedCacheKey, resolved.content, isProduction);
|
|
404
400
|
|
|
405
|
-
return resolved
|
|
401
|
+
return resolved;
|
|
406
402
|
}
|
|
407
403
|
|
|
408
404
|
private cacheResolvedContent(
|
|
@@ -472,7 +468,7 @@ export class ReadOperations {
|
|
|
472
468
|
);
|
|
473
469
|
if (persistentCached) return persistentCached;
|
|
474
470
|
|
|
475
|
-
const
|
|
471
|
+
const fileListMatch = await this.getFileListCacheHit(
|
|
476
472
|
normalizedPath,
|
|
477
473
|
cacheKeyPrefix,
|
|
478
474
|
cacheKey,
|
|
@@ -481,7 +477,19 @@ export class ReadOperations {
|
|
|
481
477
|
isPreviewMode,
|
|
482
478
|
ctx,
|
|
483
479
|
);
|
|
484
|
-
if (
|
|
480
|
+
if (fileListMatch.status === "hit" && fileListMatch.content) return fileListMatch.content;
|
|
481
|
+
if (fileListMatch.status === "present_without_content") {
|
|
482
|
+
return this.setupInFlightFetch(
|
|
483
|
+
normalizedPath,
|
|
484
|
+
apiPath,
|
|
485
|
+
cacheKey,
|
|
486
|
+
isPublished,
|
|
487
|
+
isProduction,
|
|
488
|
+
isPreviewMode,
|
|
489
|
+
ctx,
|
|
490
|
+
"indexed_without_content",
|
|
491
|
+
);
|
|
492
|
+
}
|
|
485
493
|
|
|
486
494
|
if (!hasKnownExt) {
|
|
487
495
|
if (!skipPersistentCaches) {
|
|
@@ -493,7 +501,47 @@ export class ReadOperations {
|
|
|
493
501
|
ctx,
|
|
494
502
|
isPreviewMode,
|
|
495
503
|
);
|
|
496
|
-
if (resolvedFromFileList
|
|
504
|
+
if (resolvedFromFileList.status === "hit" && resolvedFromFileList.content) {
|
|
505
|
+
return resolvedFromFileList.content;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (
|
|
509
|
+
resolvedFromFileList.status === "present_without_content" &&
|
|
510
|
+
resolvedFromFileList.path
|
|
511
|
+
) {
|
|
512
|
+
const resolvedCacheKey = getResolvedCacheKey(cacheKeyPrefix, resolvedFromFileList.path);
|
|
513
|
+
const resolvedApiPath = this.getOriginalApiPath?.(resolvedFromFileList.path) ??
|
|
514
|
+
resolvedFromFileList.path;
|
|
515
|
+
const fetchedResolved = await this.setupInFlightFetch(
|
|
516
|
+
resolvedFromFileList.path,
|
|
517
|
+
resolvedApiPath,
|
|
518
|
+
resolvedCacheKey,
|
|
519
|
+
isPublished,
|
|
520
|
+
isProduction,
|
|
521
|
+
isPreviewMode,
|
|
522
|
+
ctx,
|
|
523
|
+
"indexed_without_content",
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
this.extensionResolutionCache.set(normalizedPath, resolvedFromFileList.path);
|
|
527
|
+
this.cacheResolvedContent(
|
|
528
|
+
cacheKey,
|
|
529
|
+
resolvedCacheKey,
|
|
530
|
+
fetchedResolved,
|
|
531
|
+
isProduction && !skipPersistentCaches,
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
return fetchedResolved;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (
|
|
538
|
+
fileListMatch.status === "missing" &&
|
|
539
|
+
fileListMatch.fresh &&
|
|
540
|
+
resolvedFromFileList.status === "missing" &&
|
|
541
|
+
resolvedFromFileList.fresh
|
|
542
|
+
) {
|
|
543
|
+
throw createNotFoundLikeError(normalizedPath);
|
|
544
|
+
}
|
|
497
545
|
}
|
|
498
546
|
|
|
499
547
|
const resolved = await this.tryResolveExtensionlessPath(
|
|
@@ -506,6 +554,10 @@ export class ReadOperations {
|
|
|
506
554
|
if (resolved) return resolved;
|
|
507
555
|
}
|
|
508
556
|
|
|
557
|
+
if (fileListMatch.status === "missing" && fileListMatch.fresh) {
|
|
558
|
+
throw createNotFoundLikeError(normalizedPath);
|
|
559
|
+
}
|
|
560
|
+
|
|
509
561
|
return this.setupInFlightFetch(
|
|
510
562
|
normalizedPath,
|
|
511
563
|
apiPath,
|
|
@@ -514,6 +566,11 @@ export class ReadOperations {
|
|
|
514
566
|
isProduction,
|
|
515
567
|
isPreviewMode,
|
|
516
568
|
ctx,
|
|
569
|
+
skipPersistentCaches
|
|
570
|
+
? "invalidation"
|
|
571
|
+
: fileListMatch.status === "missing" && fileListMatch.fresh
|
|
572
|
+
? "not_in_filelist"
|
|
573
|
+
: "no_filelist_cache",
|
|
517
574
|
);
|
|
518
575
|
}
|
|
519
576
|
|
|
@@ -86,15 +86,26 @@ export class PageResolver {
|
|
|
86
86
|
primeRouterDetectionCache(cacheKey, "pages");
|
|
87
87
|
}
|
|
88
88
|
} else {
|
|
89
|
-
// Auto mode stays structural:
|
|
90
|
-
//
|
|
91
|
-
|
|
89
|
+
// Auto mode stays structural: detect the dominant router once, then keep
|
|
90
|
+
// pages fallback available for mixed or in-transition projects.
|
|
91
|
+
const useAppRouter = await detectAppRouter(
|
|
92
92
|
this.projectDir,
|
|
93
|
-
|
|
93
|
+
this.config,
|
|
94
94
|
this.adapter,
|
|
95
|
-
|
|
95
|
+
{ projectId: this.projectId },
|
|
96
96
|
);
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
if (useAppRouter) {
|
|
99
|
+
pageInfo = await getAppRouteEntity(
|
|
100
|
+
this.projectDir,
|
|
101
|
+
slug,
|
|
102
|
+
this.adapter,
|
|
103
|
+
appDirName,
|
|
104
|
+
);
|
|
105
|
+
if (!pageInfo) {
|
|
106
|
+
pageInfo = await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
98
109
|
pageInfo = await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
99
110
|
}
|
|
100
111
|
}
|
package/src/src/utils/version.ts
CHANGED