vinext 0.0.54 → 0.0.55
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/README.md +1 -0
- package/dist/check.js +15 -3
- package/dist/check.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +1 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/config/next-config.d.ts +14 -1
- package/dist/config/next-config.js +24 -4
- package/dist/config/next-config.js.map +1 -1
- package/dist/config/tsconfig-paths.d.ts +12 -3
- package/dist/config/tsconfig-paths.js +55 -24
- package/dist/config/tsconfig-paths.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +2 -1
- package/dist/entries/app-rsc-entry.js +12 -0
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.js +22 -5
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-server-entry.js +41 -4
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.js +81 -39
- package/dist/index.js.map +1 -1
- package/dist/plugins/import-meta-url.d.ts +16 -0
- package/dist/plugins/import-meta-url.js +193 -0
- package/dist/plugins/import-meta-url.js.map +1 -0
- package/dist/server/app-browser-action-result.d.ts +9 -16
- package/dist/server/app-browser-action-result.js +25 -14
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +171 -45
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
- package/dist/server/app-browser-mpa-navigation.js +36 -0
- package/dist/server/app-browser-mpa-navigation.js.map +1 -0
- package/dist/server/app-browser-popstate.d.ts +3 -1
- package/dist/server/app-browser-popstate.js +15 -1
- package/dist/server/app-browser-popstate.js.map +1 -1
- package/dist/server/app-browser-state.js +2 -1
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-layout-param-observation.d.ts +30 -0
- package/dist/server/app-layout-param-observation.js +130 -0
- package/dist/server/app-layout-param-observation.js.map +1 -0
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-dispatch.js +1 -1
- package/dist/server/app-page-params.d.ts +2 -1
- package/dist/server/app-page-params.js +14 -1
- package/dist/server/app-page-params.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +12 -1
- package/dist/server/app-page-probe.js +116 -1
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-route-handler-response.js +1 -1
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +3 -2
- package/dist/server/app-rsc-cache-busting.js +9 -7
- package/dist/server/app-rsc-cache-busting.js.map +1 -1
- package/dist/server/app-rsc-handler.js +11 -1
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-segment-config.d.ts +1 -1
- package/dist/server/app-segment-config.js +4 -1
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +5 -0
- package/dist/server/app-server-action-execution.js +198 -22
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +2 -1
- package/dist/server/artifact-compatibility.js +10 -1
- package/dist/server/artifact-compatibility.js.map +1 -1
- package/dist/server/client-reuse-manifest.d.ts +9 -4
- package/dist/server/client-reuse-manifest.js +2 -1
- package/dist/server/client-reuse-manifest.js.map +1 -1
- package/dist/server/dev-server.js +52 -10
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/document-initial-head.d.ts +7 -0
- package/dist/server/document-initial-head.js +35 -0
- package/dist/server/document-initial-head.js.map +1 -0
- package/dist/server/pages-document-initial-props.d.ts +84 -2
- package/dist/server/pages-document-initial-props.js +127 -1
- package/dist/server/pages-document-initial-props.js.map +1 -1
- package/dist/server/pages-node-compat.js +1 -1
- package/dist/server/pages-page-response.d.ts +14 -0
- package/dist/server/pages-page-response.js +31 -8
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.js +13 -6
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/skip-cache-proof.d.ts +23 -2
- package/dist/server/skip-cache-proof.js +81 -12
- package/dist/server/skip-cache-proof.js.map +1 -1
- package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
- package/dist/server/static-layout-client-reuse-proof.js +35 -0
- package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
- package/dist/shims/cache.d.ts +21 -1
- package/dist/shims/cache.js +101 -6
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/document.d.ts +6 -0
- package/dist/shims/document.js +7 -8
- package/dist/shims/document.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +4 -4
- package/dist/shims/error-boundary.js +27 -28
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +16 -5
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/hash-scroll.d.ts +4 -1
- package/dist/shims/hash-scroll.js +13 -1
- package/dist/shims/hash-scroll.js.map +1 -1
- package/dist/shims/head-state.d.ts +1 -0
- package/dist/shims/head-state.js +18 -3
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +35 -1
- package/dist/shims/head.js +113 -14
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
- package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
- package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
- package/dist/shims/link.js +28 -2
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/navigation.d.ts +39 -1
- package/dist/shims/navigation.js +61 -13
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.js +37 -17
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/thenable-params.d.ts +5 -2
- package/dist/shims/thenable-params.js +25 -1
- package/dist/shims/thenable-params.js.map +1 -1
- package/dist/shims/unified-request-context.js +3 -0
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/utils/client-build-manifest.d.ts +15 -0
- package/dist/utils/client-build-manifest.js +54 -0
- package/dist/utils/client-build-manifest.js.map +1 -0
- package/dist/utils/hash.js +1 -1
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +1 -1
- package/dist/utils/lazy-chunks.js.map +1 -1
- package/dist/utils/vite-version.d.ts +11 -0
- package/dist/utils/vite-version.js +36 -0
- package/dist/utils/vite-version.js.map +1 -0
- package/package.json +2 -2
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { evaluateArtifactCompatibility } from "./artifact-compatibility.js";
|
|
1
|
+
import { ARTIFACT_COMPATIBILITY_PROOF_FIELDS, evaluateArtifactCompatibility } from "./artifact-compatibility.js";
|
|
2
2
|
import { createClientReusePayloadHash } from "./client-reuse-manifest.js";
|
|
3
|
+
import { createStaticLayoutClientReuseArtifactCompatibility, createStaticLayoutClientReusePayloadHash, createStaticLayoutClientReuseRouteId } from "./static-layout-client-reuse-proof.js";
|
|
3
4
|
//#region src/server/skip-cache-proof.ts
|
|
4
|
-
const ARTIFACT_COMPATIBILITY_PROOF_FIELDS = [
|
|
5
|
-
"schemaVersion",
|
|
6
|
-
"graphVersion",
|
|
7
|
-
"deploymentVersion",
|
|
8
|
-
"appElementsSchemaVersion",
|
|
9
|
-
"rscPayloadSchemaVersion",
|
|
10
|
-
"rootBoundaryId",
|
|
11
|
-
"renderEpoch"
|
|
12
|
-
];
|
|
13
5
|
function createDisabledSkipDisposition() {
|
|
14
6
|
return {
|
|
15
7
|
code: "SKIP_MODEL_DISABLED",
|
|
@@ -17,6 +9,14 @@ function createDisabledSkipDisposition() {
|
|
|
17
9
|
mode: "renderAndSend"
|
|
18
10
|
};
|
|
19
11
|
}
|
|
12
|
+
function createStaticLayoutSkipDisposition(skippedEntryIds) {
|
|
13
|
+
return {
|
|
14
|
+
code: "SKIP_STATIC_LAYOUT_VERIFIED",
|
|
15
|
+
enabled: true,
|
|
16
|
+
mode: "skipStaticLayout",
|
|
17
|
+
skippedEntryIds: [...skippedEntryIds]
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
20
|
function rejectSkipCacheCrossCheck(entry, code, fields = {}) {
|
|
21
21
|
return {
|
|
22
22
|
kind: "rejected",
|
|
@@ -33,9 +33,31 @@ function collectArtifactCompatibilityProofMismatches(artifactCompatibility, proo
|
|
|
33
33
|
for (const field of ARTIFACT_COMPATIBILITY_PROOF_FIELDS) if (artifactCompatibility[field] !== proofCompatibility[field]) mismatchedFields.push(field);
|
|
34
34
|
return mismatchedFields;
|
|
35
35
|
}
|
|
36
|
+
function isExactArtifactCompatibility(artifactCompatibility, entryCompatibility) {
|
|
37
|
+
return collectArtifactCompatibilityProofMismatches(artifactCompatibility, entryCompatibility).length === 0;
|
|
38
|
+
}
|
|
36
39
|
function assertNever(value) {
|
|
37
40
|
throw new Error(`Unhandled skip/cache proof state: ${String(value)}`);
|
|
38
41
|
}
|
|
42
|
+
function createRenderAndSendPlan(options) {
|
|
43
|
+
return {
|
|
44
|
+
kind: "renderAndSend",
|
|
45
|
+
entryRejections: options.entryRejections ?? [],
|
|
46
|
+
...options.manifestRejection ? { manifestRejection: options.manifestRejection } : {},
|
|
47
|
+
skipDisposition: createDisabledSkipDisposition(),
|
|
48
|
+
skipIneligibleEntryIds: options.skipIneligibleEntryIds ?? [],
|
|
49
|
+
skippedEntryIds: []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createVerificationBudgetExceededRejection(totalWireEntries, maxWireEntriesToVerify) {
|
|
53
|
+
return {
|
|
54
|
+
code: "SKIP_VERIFICATION_BUDGET_EXCEEDED",
|
|
55
|
+
fields: {
|
|
56
|
+
totalWireEntries,
|
|
57
|
+
maxWireEntriesToVerify
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
39
61
|
function crossCheckInvalidationProof(entry, invalidation) {
|
|
40
62
|
switch (invalidation.kind) {
|
|
41
63
|
case "valid": return null;
|
|
@@ -83,6 +105,7 @@ function crossCheckClientReuseManifestEntryWithCache(input) {
|
|
|
83
105
|
});
|
|
84
106
|
const invalidationRejection = crossCheckInvalidationProof(entry, input.artifact.invalidation);
|
|
85
107
|
if (invalidationRejection) return invalidationRejection;
|
|
108
|
+
const skipDisposition = isExactArtifactCompatibility(input.artifact.compatibility, entry.artifactCompatibility) ? createStaticLayoutSkipDisposition([entry.id]) : createDisabledSkipDisposition();
|
|
86
109
|
return {
|
|
87
110
|
kind: "verified",
|
|
88
111
|
code: "SKIP_CACHE_CROSS_CHECK_PASSED",
|
|
@@ -92,10 +115,56 @@ function crossCheckClientReuseManifestEntryWithCache(input) {
|
|
|
92
115
|
reuseClass: proof.reuseClass,
|
|
93
116
|
variantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey)
|
|
94
117
|
},
|
|
95
|
-
skipDisposition
|
|
118
|
+
skipDisposition
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function createClientReuseSkipTransportPlan(input) {
|
|
122
|
+
const { manifest } = input;
|
|
123
|
+
if (manifest.kind === "absent") return createRenderAndSendPlan({});
|
|
124
|
+
if (manifest.kind === "rejected") return createRenderAndSendPlan({ manifestRejection: manifest.rejection });
|
|
125
|
+
const maxWireEntriesToVerify = input.maxWireEntriesToVerify ?? 8;
|
|
126
|
+
if (!Number.isSafeInteger(maxWireEntriesToVerify) || maxWireEntriesToVerify < 0) throw new RangeError("maxWireEntriesToVerify must be a non-negative safe integer");
|
|
127
|
+
const totalWireEntries = manifest.manifest.entries.length + manifest.entryRejections.length;
|
|
128
|
+
if (totalWireEntries > maxWireEntriesToVerify) return createRenderAndSendPlan({
|
|
129
|
+
entryRejections: manifest.entryRejections,
|
|
130
|
+
manifestRejection: createVerificationBudgetExceededRejection(totalWireEntries, maxWireEntriesToVerify)
|
|
131
|
+
});
|
|
132
|
+
const skippedEntryIds = [];
|
|
133
|
+
const skipIneligibleEntryIds = [];
|
|
134
|
+
const entryRejections = [...manifest.entryRejections];
|
|
135
|
+
for (const entry of manifest.manifest.entries) {
|
|
136
|
+
const verification = input.verifyEntry(entry);
|
|
137
|
+
if (verification.kind === "rejected") {
|
|
138
|
+
entryRejections.push(verification.rejection);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (verification.entryId !== entry.id) {
|
|
142
|
+
entryRejections.push({
|
|
143
|
+
code: "SKIP_CACHE_ENTRY_ID_MISMATCH",
|
|
144
|
+
entryId: entry.id,
|
|
145
|
+
fields: {
|
|
146
|
+
verifierEntryId: verification.entryId,
|
|
147
|
+
manifestEntryId: entry.id
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (verification.skipDisposition.enabled) skippedEntryIds.push(entry.id);
|
|
153
|
+
else skipIneligibleEntryIds.push(entry.id);
|
|
154
|
+
}
|
|
155
|
+
if (skippedEntryIds.length === 0) return createRenderAndSendPlan({
|
|
156
|
+
entryRejections,
|
|
157
|
+
skipIneligibleEntryIds
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
kind: "skip",
|
|
161
|
+
entryRejections,
|
|
162
|
+
skipDisposition: createStaticLayoutSkipDisposition(skippedEntryIds),
|
|
163
|
+
skipIneligibleEntryIds,
|
|
164
|
+
skippedEntryIds
|
|
96
165
|
};
|
|
97
166
|
}
|
|
98
167
|
//#endregion
|
|
99
|
-
export { crossCheckClientReuseManifestEntryWithCache };
|
|
168
|
+
export { createClientReuseSkipTransportPlan, createStaticLayoutClientReuseArtifactCompatibility, createStaticLayoutClientReusePayloadHash, createStaticLayoutClientReuseRouteId, crossCheckClientReuseManifestEntryWithCache };
|
|
100
169
|
|
|
101
170
|
//# sourceMappingURL=skip-cache-proof.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skip-cache-proof.js","names":[],"sources":["../../src/server/skip-cache-proof.ts"],"sourcesContent":["import {\n evaluateArtifactCompatibility,\n type ArtifactCompatibilityEnvelope,\n type ArtifactCompatibilityEvaluationOptions,\n} from \"./artifact-compatibility.js\";\nimport type { StaticLayoutArtifactReuseDecision } from \"./cache-proof.js\";\nimport {\n createClientReusePayloadHash,\n type ClientReuseManifestEntry,\n type ClientReuseManifestEntryRejection,\n type ClientReuseManifestSkipDisposition,\n type ClientReuseManifestTraceFields,\n} from \"./client-reuse-manifest.js\";\n\nexport type SkipCacheInvalidationProof =\n | Readonly<{ kind: \"invalidated\"; invalidationEpoch: string | null }>\n | Readonly<{ kind: \"unknown\" }>\n | Readonly<{ kind: \"valid\" }>;\n\ntype SkipCacheArtifactProof = Readonly<{\n compatibility: ArtifactCompatibilityEnvelope;\n invalidation: SkipCacheInvalidationProof;\n payloadHash: string | null;\n}>;\n\ntype SkipCacheCrossCheckAcceptanceCode = \"SKIP_CACHE_CROSS_CHECK_PASSED\";\n\ntype SkipCacheCrossCheckVerified = Readonly<{\n code: SkipCacheCrossCheckAcceptanceCode;\n entryId: string;\n fields: ClientReuseManifestTraceFields;\n kind: \"verified\";\n skipDisposition: ClientReuseManifestSkipDisposition;\n}>;\n\ntype SkipCacheCrossCheckRejected = Readonly<{\n kind: \"rejected\";\n rejection: ClientReuseManifestEntryRejection;\n skipDisposition: ClientReuseManifestSkipDisposition;\n}>;\n\ntype SkipCacheCrossCheckResult = SkipCacheCrossCheckRejected | SkipCacheCrossCheckVerified;\n\ntype CrossCheckClientReuseManifestEntryWithCacheInput = Readonly<{\n artifact: SkipCacheArtifactProof;\n cacheDecision: StaticLayoutArtifactReuseDecision | null;\n entry: ClientReuseManifestEntry;\n}> &\n ArtifactCompatibilityEvaluationOptions;\n\nconst ARTIFACT_COMPATIBILITY_PROOF_FIELDS: readonly (keyof ArtifactCompatibilityEnvelope)[] = [\n \"schemaVersion\",\n \"graphVersion\",\n \"deploymentVersion\",\n \"appElementsSchemaVersion\",\n \"rscPayloadSchemaVersion\",\n \"rootBoundaryId\",\n \"renderEpoch\",\n];\n\nfunction createDisabledSkipDisposition(): ClientReuseManifestSkipDisposition {\n return {\n code: \"SKIP_MODEL_DISABLED\",\n enabled: false,\n mode: \"renderAndSend\",\n };\n}\n\nfunction rejectSkipCacheCrossCheck(\n entry: ClientReuseManifestEntry,\n code: ClientReuseManifestEntryRejection[\"code\"],\n fields: ClientReuseManifestTraceFields = {},\n): SkipCacheCrossCheckRejected {\n return {\n kind: \"rejected\",\n rejection: {\n code,\n entryId: entry.id,\n fields,\n },\n skipDisposition: createDisabledSkipDisposition(),\n };\n}\n\nfunction collectArtifactCompatibilityProofMismatches(\n artifactCompatibility: ArtifactCompatibilityEnvelope,\n proofCompatibility: ArtifactCompatibilityEnvelope,\n): readonly string[] {\n const mismatchedFields: string[] = [];\n for (const field of ARTIFACT_COMPATIBILITY_PROOF_FIELDS) {\n if (artifactCompatibility[field] !== proofCompatibility[field]) {\n mismatchedFields.push(field);\n }\n }\n return mismatchedFields;\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unhandled skip/cache proof state: ${String(value)}`);\n}\n\nfunction crossCheckInvalidationProof(\n entry: ClientReuseManifestEntry,\n invalidation: SkipCacheInvalidationProof,\n): SkipCacheCrossCheckRejected | null {\n switch (invalidation.kind) {\n case \"valid\":\n return null;\n case \"unknown\":\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_INVALIDATION_UNKNOWN\");\n case \"invalidated\":\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_INVALIDATED\", {\n invalidationEpoch: invalidation.invalidationEpoch,\n });\n default:\n return assertNever(invalidation);\n }\n}\n\nexport function crossCheckClientReuseManifestEntryWithCache(\n input: CrossCheckClientReuseManifestEntryWithCacheInput,\n): SkipCacheCrossCheckResult {\n const { cacheDecision, entry } = input;\n if (cacheDecision === null) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PROOF_MISSING\");\n }\n if (cacheDecision.kind === \"fallback\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PROOF_REJECTED\", {\n cacheProofCode: cacheDecision.fallback.code,\n cacheProofMode: cacheDecision.fallback.mode,\n cacheProofScope: cacheDecision.fallback.scope,\n });\n }\n\n const { proof } = cacheDecision;\n if (entry.kind !== \"layout\" || proof.reuseClass !== \"static-layout\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_REUSE_CLASS_UNSUPPORTED\", {\n entryKind: entry.kind,\n reuseClass: proof.reuseClass,\n });\n }\n\n if (entry.id !== proof.candidateOutput.layoutId) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ENTRY_ID_MISMATCH\", {\n cacheEntryId: proof.candidateOutput.layoutId,\n manifestEntryId: entry.id,\n });\n }\n\n const artifactProofMismatches = collectArtifactCompatibilityProofMismatches(\n input.artifact.compatibility,\n proof.candidateArtifactCompatibility,\n );\n if (artifactProofMismatches.length > 0) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_PROOF_MISMATCH\", {\n mismatchedFields: artifactProofMismatches,\n });\n }\n\n const artifactCompatibility = evaluateArtifactCompatibility(\n input.artifact.compatibility,\n entry.artifactCompatibility,\n { compatibilityMap: input.compatibilityMap },\n );\n if (artifactCompatibility.kind === \"unknown\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_COMPATIBILITY_UNKNOWN\", {\n compatibilityFallback: artifactCompatibility.fallback,\n reason: artifactCompatibility.reason,\n });\n }\n if (artifactCompatibility.kind === \"incompatible\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_COMPATIBILITY_INCOMPATIBLE\", {\n compatibilityFallback: artifactCompatibility.fallback,\n reason: artifactCompatibility.reason,\n });\n }\n\n if (entry.variantCacheKey !== proof.variant.cacheKey) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_VARIANT_MISMATCH\", {\n cacheVariantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),\n entryVariantCacheKeyHash: createClientReusePayloadHash(entry.variantCacheKey),\n });\n }\n\n if (input.artifact.payloadHash === null) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PAYLOAD_HASH_MISSING\");\n }\n\n if (entry.payloadHash !== input.artifact.payloadHash) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PAYLOAD_HASH_MISMATCH\", {\n cachePayloadHash: input.artifact.payloadHash,\n entryPayloadHash: entry.payloadHash,\n });\n }\n\n const invalidationRejection = crossCheckInvalidationProof(entry, input.artifact.invalidation);\n if (invalidationRejection) return invalidationRejection;\n\n return {\n kind: \"verified\",\n code: \"SKIP_CACHE_CROSS_CHECK_PASSED\",\n entryId: entry.id,\n fields: {\n entryKind: entry.kind,\n reuseClass: proof.reuseClass,\n variantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),\n },\n skipDisposition: createDisabledSkipDisposition(),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"skip-cache-proof.js","names":[],"sources":["../../src/server/skip-cache-proof.ts"],"sourcesContent":["import {\n evaluateArtifactCompatibility,\n ARTIFACT_COMPATIBILITY_PROOF_FIELDS,\n type ArtifactCompatibilityEnvelope,\n type ArtifactCompatibilityEvaluationOptions,\n} from \"./artifact-compatibility.js\";\nimport type { StaticLayoutArtifactReuseDecision } from \"./cache-proof.js\";\nimport {\n CLIENT_REUSE_MANIFEST_SKIP_VERIFICATION_ENTRY_BUDGET,\n createClientReusePayloadHash,\n type ClientReuseManifestEntry,\n type ClientReuseManifestEntryRejection,\n type ClientReuseManifestParseResult,\n type ClientReuseManifestRejection,\n type ClientReuseManifestSkipDisposition,\n type ClientReuseManifestTraceFields,\n} from \"./client-reuse-manifest.js\";\nexport {\n createStaticLayoutClientReuseArtifactCompatibility,\n createStaticLayoutClientReusePayloadHash,\n createStaticLayoutClientReuseRouteId,\n} from \"./static-layout-client-reuse-proof.js\";\n\nexport type SkipCacheInvalidationProof =\n | Readonly<{ kind: \"invalidated\"; invalidationEpoch: string | null }>\n | Readonly<{ kind: \"unknown\" }>\n | Readonly<{ kind: \"valid\" }>;\n\ntype SkipCacheArtifactProof = Readonly<{\n compatibility: ArtifactCompatibilityEnvelope;\n invalidation: SkipCacheInvalidationProof;\n payloadHash: string | null;\n}>;\n\ntype SkipCacheCrossCheckAcceptanceCode = \"SKIP_CACHE_CROSS_CHECK_PASSED\";\n\ntype SkipCacheCrossCheckVerified = Readonly<{\n code: SkipCacheCrossCheckAcceptanceCode;\n entryId: string;\n fields: ClientReuseManifestTraceFields;\n kind: \"verified\";\n skipDisposition: ClientReuseManifestSkipDisposition;\n}>;\n\ntype SkipCacheCrossCheckRejected = Readonly<{\n kind: \"rejected\";\n rejection: ClientReuseManifestEntryRejection;\n skipDisposition: ClientReuseManifestSkipDisposition;\n}>;\n\ntype SkipCacheCrossCheckResult = SkipCacheCrossCheckRejected | SkipCacheCrossCheckVerified;\n\ntype CrossCheckClientReuseManifestEntryWithCacheInput = Readonly<{\n artifact: SkipCacheArtifactProof;\n cacheDecision: StaticLayoutArtifactReuseDecision | null;\n entry: ClientReuseManifestEntry;\n}> &\n ArtifactCompatibilityEvaluationOptions;\n\ntype ClientReuseSkipTransportPlan =\n | Readonly<{\n entryRejections: readonly ClientReuseManifestEntryRejection[];\n kind: \"renderAndSend\";\n manifestRejection?: ClientReuseManifestRejection;\n skipDisposition: ClientReuseManifestSkipDisposition;\n skipIneligibleEntryIds: readonly string[];\n skippedEntryIds: readonly string[];\n }>\n | Readonly<{\n entryRejections: readonly ClientReuseManifestEntryRejection[];\n kind: \"skip\";\n skipDisposition: ClientReuseManifestSkipDisposition;\n skipIneligibleEntryIds: readonly string[];\n skippedEntryIds: readonly string[];\n }>;\n\ntype CreateClientReuseSkipTransportPlanInput = Readonly<{\n manifest: ClientReuseManifestParseResult;\n maxWireEntriesToVerify?: number;\n verifyEntry: (entry: ClientReuseManifestEntry) => SkipCacheCrossCheckResult;\n}>;\n\nfunction createDisabledSkipDisposition(): ClientReuseManifestSkipDisposition {\n return {\n code: \"SKIP_MODEL_DISABLED\",\n enabled: false,\n mode: \"renderAndSend\",\n };\n}\n\nfunction createStaticLayoutSkipDisposition(\n skippedEntryIds: readonly string[],\n): ClientReuseManifestSkipDisposition {\n return {\n code: \"SKIP_STATIC_LAYOUT_VERIFIED\",\n enabled: true,\n mode: \"skipStaticLayout\",\n skippedEntryIds: [...skippedEntryIds],\n };\n}\n\nfunction rejectSkipCacheCrossCheck(\n entry: ClientReuseManifestEntry,\n code: ClientReuseManifestEntryRejection[\"code\"],\n fields: ClientReuseManifestTraceFields = {},\n): SkipCacheCrossCheckRejected {\n return {\n kind: \"rejected\",\n rejection: {\n code,\n entryId: entry.id,\n fields,\n },\n skipDisposition: createDisabledSkipDisposition(),\n };\n}\n\nfunction collectArtifactCompatibilityProofMismatches(\n artifactCompatibility: ArtifactCompatibilityEnvelope,\n proofCompatibility: ArtifactCompatibilityEnvelope,\n): readonly string[] {\n const mismatchedFields: string[] = [];\n for (const field of ARTIFACT_COMPATIBILITY_PROOF_FIELDS) {\n if (artifactCompatibility[field] !== proofCompatibility[field]) {\n mismatchedFields.push(field);\n }\n }\n return mismatchedFields;\n}\n\nfunction isExactArtifactCompatibility(\n artifactCompatibility: ArtifactCompatibilityEnvelope,\n entryCompatibility: ArtifactCompatibilityEnvelope,\n): boolean {\n return (\n collectArtifactCompatibilityProofMismatches(artifactCompatibility, entryCompatibility)\n .length === 0\n );\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unhandled skip/cache proof state: ${String(value)}`);\n}\n\nfunction createRenderAndSendPlan(options: {\n entryRejections?: readonly ClientReuseManifestEntryRejection[];\n manifestRejection?: ClientReuseManifestRejection;\n skipIneligibleEntryIds?: readonly string[];\n}): ClientReuseSkipTransportPlan {\n return {\n kind: \"renderAndSend\",\n entryRejections: options.entryRejections ?? [],\n ...(options.manifestRejection ? { manifestRejection: options.manifestRejection } : {}),\n skipDisposition: createDisabledSkipDisposition(),\n skipIneligibleEntryIds: options.skipIneligibleEntryIds ?? [],\n skippedEntryIds: [],\n };\n}\n\nfunction createVerificationBudgetExceededRejection(\n totalWireEntries: number,\n maxWireEntriesToVerify: number,\n): ClientReuseManifestRejection {\n return {\n code: \"SKIP_VERIFICATION_BUDGET_EXCEEDED\",\n fields: { totalWireEntries, maxWireEntriesToVerify },\n };\n}\n\nfunction crossCheckInvalidationProof(\n entry: ClientReuseManifestEntry,\n invalidation: SkipCacheInvalidationProof,\n): SkipCacheCrossCheckRejected | null {\n switch (invalidation.kind) {\n case \"valid\":\n return null;\n case \"unknown\":\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_INVALIDATION_UNKNOWN\");\n case \"invalidated\":\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_INVALIDATED\", {\n invalidationEpoch: invalidation.invalidationEpoch,\n });\n default:\n return assertNever(invalidation);\n }\n}\n\nexport function crossCheckClientReuseManifestEntryWithCache(\n input: CrossCheckClientReuseManifestEntryWithCacheInput,\n): SkipCacheCrossCheckResult {\n const { cacheDecision, entry } = input;\n if (cacheDecision === null) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PROOF_MISSING\");\n }\n if (cacheDecision.kind === \"fallback\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PROOF_REJECTED\", {\n cacheProofCode: cacheDecision.fallback.code,\n cacheProofMode: cacheDecision.fallback.mode,\n cacheProofScope: cacheDecision.fallback.scope,\n });\n }\n\n const { proof } = cacheDecision;\n if (entry.kind !== \"layout\" || proof.reuseClass !== \"static-layout\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_REUSE_CLASS_UNSUPPORTED\", {\n entryKind: entry.kind,\n reuseClass: proof.reuseClass,\n });\n }\n\n if (entry.id !== proof.candidateOutput.layoutId) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ENTRY_ID_MISMATCH\", {\n cacheEntryId: proof.candidateOutput.layoutId,\n manifestEntryId: entry.id,\n });\n }\n\n const artifactProofMismatches = collectArtifactCompatibilityProofMismatches(\n input.artifact.compatibility,\n proof.candidateArtifactCompatibility,\n );\n if (artifactProofMismatches.length > 0) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_PROOF_MISMATCH\", {\n mismatchedFields: artifactProofMismatches,\n });\n }\n\n const artifactCompatibility = evaluateArtifactCompatibility(\n input.artifact.compatibility,\n entry.artifactCompatibility,\n { compatibilityMap: input.compatibilityMap },\n );\n if (artifactCompatibility.kind === \"unknown\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_COMPATIBILITY_UNKNOWN\", {\n compatibilityFallback: artifactCompatibility.fallback,\n reason: artifactCompatibility.reason,\n });\n }\n if (artifactCompatibility.kind === \"incompatible\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_COMPATIBILITY_INCOMPATIBLE\", {\n compatibilityFallback: artifactCompatibility.fallback,\n reason: artifactCompatibility.reason,\n });\n }\n\n if (entry.variantCacheKey !== proof.variant.cacheKey) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_VARIANT_MISMATCH\", {\n cacheVariantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),\n entryVariantCacheKeyHash: createClientReusePayloadHash(entry.variantCacheKey),\n });\n }\n\n if (input.artifact.payloadHash === null) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PAYLOAD_HASH_MISSING\");\n }\n\n if (entry.payloadHash !== input.artifact.payloadHash) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PAYLOAD_HASH_MISMATCH\", {\n cachePayloadHash: input.artifact.payloadHash,\n entryPayloadHash: entry.payloadHash,\n });\n }\n\n const invalidationRejection = crossCheckInvalidationProof(entry, input.artifact.invalidation);\n if (invalidationRejection) return invalidationRejection;\n\n // Skip transport requires exact artifact field equality — not just\n // compatibility-map-based equivalence. evaluateArtifactCompatibility (above)\n // returns \"compatible\" when a declared set bridges differing fields (canary →\n // rollback), but the skip planner must not elide a fresh render when the two\n // payloads were built from observably different artifact environments.\n // isExactArtifactCompatibility performs a distinct field-level walk that\n // ignores the compatibility map, guaranteeing only bit-identical artifact\n // envelopes are eligible for transport skip.\n const skipDisposition = isExactArtifactCompatibility(\n input.artifact.compatibility,\n entry.artifactCompatibility,\n )\n ? createStaticLayoutSkipDisposition([entry.id])\n : createDisabledSkipDisposition();\n\n return {\n kind: \"verified\",\n code: \"SKIP_CACHE_CROSS_CHECK_PASSED\",\n entryId: entry.id,\n fields: {\n entryKind: entry.kind,\n reuseClass: proof.reuseClass,\n variantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),\n },\n skipDisposition,\n };\n}\n\nexport function createClientReuseSkipTransportPlan(\n input: CreateClientReuseSkipTransportPlanInput,\n): ClientReuseSkipTransportPlan {\n const { manifest } = input;\n if (manifest.kind === \"absent\") {\n return createRenderAndSendPlan({});\n }\n if (manifest.kind === \"rejected\") {\n return createRenderAndSendPlan({ manifestRejection: manifest.rejection });\n }\n\n const maxWireEntriesToVerify =\n input.maxWireEntriesToVerify ?? CLIENT_REUSE_MANIFEST_SKIP_VERIFICATION_ENTRY_BUDGET;\n if (!Number.isSafeInteger(maxWireEntriesToVerify) || maxWireEntriesToVerify < 0) {\n throw new RangeError(\"maxWireEntriesToVerify must be a non-negative safe integer\");\n }\n\n const totalWireEntries = manifest.manifest.entries.length + manifest.entryRejections.length;\n if (totalWireEntries > maxWireEntriesToVerify) {\n return createRenderAndSendPlan({\n entryRejections: manifest.entryRejections,\n manifestRejection: createVerificationBudgetExceededRejection(\n totalWireEntries,\n maxWireEntriesToVerify,\n ),\n });\n }\n\n const skippedEntryIds: string[] = [];\n const skipIneligibleEntryIds: string[] = [];\n const entryRejections: ClientReuseManifestEntryRejection[] = [...manifest.entryRejections];\n for (const entry of manifest.manifest.entries) {\n const verification = input.verifyEntry(entry);\n if (verification.kind === \"rejected\") {\n entryRejections.push(verification.rejection);\n continue;\n }\n\n // The planner must not trust a verifier that returns a verified result\n // for a different entry than the one it was given. This is an internal\n // invariant, not a hostile-client defence.\n if (verification.entryId !== entry.id) {\n entryRejections.push({\n code: \"SKIP_CACHE_ENTRY_ID_MISMATCH\",\n entryId: entry.id,\n fields: {\n verifierEntryId: verification.entryId,\n manifestEntryId: entry.id,\n },\n });\n continue;\n }\n\n if (verification.skipDisposition.enabled) {\n skippedEntryIds.push(entry.id);\n } else {\n skipIneligibleEntryIds.push(entry.id);\n }\n }\n\n if (skippedEntryIds.length === 0) {\n return createRenderAndSendPlan({ entryRejections, skipIneligibleEntryIds });\n }\n\n return {\n kind: \"skip\",\n entryRejections,\n skipDisposition: createStaticLayoutSkipDisposition(skippedEntryIds),\n skipIneligibleEntryIds,\n skippedEntryIds,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
|
|
2
|
+
|
|
3
|
+
//#region src/server/static-layout-client-reuse-proof.d.ts
|
|
4
|
+
type StaticLayoutClientReuseProofInput = Readonly<{
|
|
5
|
+
artifactCompatibility: ArtifactCompatibilityEnvelope;
|
|
6
|
+
layoutId: string;
|
|
7
|
+
rootBoundaryId: string | null;
|
|
8
|
+
routeId: string;
|
|
9
|
+
variantCacheKey: string;
|
|
10
|
+
}>;
|
|
11
|
+
declare function createStaticLayoutClientReuseRouteId(layoutId: string): string;
|
|
12
|
+
declare function createStaticLayoutClientReusePayloadHash(input: StaticLayoutClientReuseProofInput): string;
|
|
13
|
+
declare function createStaticLayoutClientReuseArtifactCompatibility(input: StaticLayoutClientReuseProofInput): ArtifactCompatibilityEnvelope;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createStaticLayoutClientReuseArtifactCompatibility, createStaticLayoutClientReusePayloadHash, createStaticLayoutClientReuseRouteId };
|
|
16
|
+
//# sourceMappingURL=static-layout-client-reuse-proof.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ARTIFACT_COMPATIBILITY_PROOF_FIELDS } from "./artifact-compatibility.js";
|
|
2
|
+
import { createClientReusePayloadHash } from "./client-reuse-manifest.js";
|
|
3
|
+
//#region src/server/static-layout-client-reuse-proof.ts
|
|
4
|
+
function createStaticLayoutClientReuseRouteId(layoutId) {
|
|
5
|
+
return `static-layout:${createClientReusePayloadHash(layoutId)}`;
|
|
6
|
+
}
|
|
7
|
+
function createCanonicalProofPairs(input) {
|
|
8
|
+
return ARTIFACT_COMPATIBILITY_PROOF_FIELDS.map((field) => [field, input.artifactCompatibility[field]]);
|
|
9
|
+
}
|
|
10
|
+
function createStaticLayoutClientReusePayloadHash(input) {
|
|
11
|
+
return createClientReusePayloadHash(JSON.stringify({
|
|
12
|
+
artifactCompatibilityPairs: createCanonicalProofPairs(input),
|
|
13
|
+
layoutId: input.layoutId,
|
|
14
|
+
rootBoundaryId: input.rootBoundaryId,
|
|
15
|
+
variantCacheKey: input.variantCacheKey
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
function createStaticLayoutClientReuseArtifactCompatibility(input) {
|
|
19
|
+
return {
|
|
20
|
+
...input.artifactCompatibility,
|
|
21
|
+
graphVersion: `static-layout-graph:${createClientReusePayloadHash(JSON.stringify({
|
|
22
|
+
layoutId: input.layoutId,
|
|
23
|
+
rootBoundaryId: input.rootBoundaryId
|
|
24
|
+
}))}`,
|
|
25
|
+
renderEpoch: `static-layout:${createClientReusePayloadHash(JSON.stringify({
|
|
26
|
+
layoutId: input.layoutId,
|
|
27
|
+
rootBoundaryId: input.rootBoundaryId,
|
|
28
|
+
variantCacheKey: input.variantCacheKey
|
|
29
|
+
}))}`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createStaticLayoutClientReuseArtifactCompatibility, createStaticLayoutClientReusePayloadHash, createStaticLayoutClientReuseRouteId };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=static-layout-client-reuse-proof.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-layout-client-reuse-proof.js","names":[],"sources":["../../src/server/static-layout-client-reuse-proof.ts"],"sourcesContent":["import {\n type ArtifactCompatibilityEnvelope,\n ARTIFACT_COMPATIBILITY_PROOF_FIELDS,\n} from \"./artifact-compatibility.js\";\nimport { createClientReusePayloadHash } from \"./client-reuse-manifest.js\";\n\ntype StaticLayoutClientReuseProofInput = Readonly<{\n artifactCompatibility: ArtifactCompatibilityEnvelope;\n layoutId: string;\n rootBoundaryId: string | null;\n routeId: string;\n variantCacheKey: string;\n}>;\n\ntype ProofFieldPair = readonly [string, string | number | null];\n\nexport function createStaticLayoutClientReuseRouteId(layoutId: string): string {\n return `static-layout:${createClientReusePayloadHash(layoutId)}`;\n}\n\nfunction createCanonicalProofPairs(\n input: StaticLayoutClientReuseProofInput,\n): readonly ProofFieldPair[] {\n return ARTIFACT_COMPATIBILITY_PROOF_FIELDS.map((field) => [\n field,\n input.artifactCompatibility[field],\n ]);\n}\n\nexport function createStaticLayoutClientReusePayloadHash(\n input: StaticLayoutClientReuseProofInput,\n): string {\n // ARTIFACT_COMPATIBILITY_PROOF_FIELDS order is load-bearing for hash\n // determinism. The artifact compatibility section is serialized as an\n // array of [field, value] pairs in declaration order — no object key-order\n // dependency — so the hash is invariant across runtimes. Reordering the\n // field list silently changes every hash.\n //\n // routeId is intentionally excluded. This is a layout-scoped hash —\n // sibling routes under the same layout must produce identical payload\n // hashes, otherwise the cross-check rejects on payload hash mismatch\n // before reaching the exact-equality gate.\n // If the field list must change, bump the hash algorithm or namespace to\n // avoid silent cross-deployment hash drift.\n return createClientReusePayloadHash(\n JSON.stringify({\n artifactCompatibilityPairs: createCanonicalProofPairs(input),\n layoutId: input.layoutId,\n rootBoundaryId: input.rootBoundaryId,\n variantCacheKey: input.variantCacheKey,\n }),\n );\n}\n\n// The app-route payload envelope is route-scoped and may not carry a render epoch\n// during this wave. Static-layout skip needs layout-scoped compatibility so\n// sibling-route navigation can reuse a retained static layout without treating\n// the source route id as part of the retained artifact identity.\n//\n// renderEpoch is scoped to layout identity (layoutId, rootBoundaryId) and the\n// rendering variant (variantCacheKey). The leaf routeId is intentionally\n// excluded — sibling routes under the same layout must produce the same\n// renderEpoch, otherwise exact-equality skip gating would silently disable\n// skip transport for the primary use case (navigating between sibling pages).\nexport function createStaticLayoutClientReuseArtifactCompatibility(\n input: StaticLayoutClientReuseProofInput,\n): ArtifactCompatibilityEnvelope {\n return {\n ...input.artifactCompatibility,\n graphVersion: `static-layout-graph:${createClientReusePayloadHash(\n JSON.stringify({\n layoutId: input.layoutId,\n rootBoundaryId: input.rootBoundaryId,\n }),\n )}`,\n renderEpoch: `static-layout:${createClientReusePayloadHash(\n JSON.stringify({\n layoutId: input.layoutId,\n rootBoundaryId: input.rootBoundaryId,\n variantCacheKey: input.variantCacheKey,\n }),\n )}`,\n };\n}\n"],"mappings":";;;AAgBA,SAAgB,qCAAqC,UAA0B;CAC7E,OAAO,iBAAiB,6BAA6B,SAAS;;AAGhE,SAAS,0BACP,OAC2B;CAC3B,OAAO,oCAAoC,KAAK,UAAU,CACxD,OACA,MAAM,sBAAsB,OAC7B,CAAC;;AAGJ,SAAgB,yCACd,OACQ;CAaR,OAAO,6BACL,KAAK,UAAU;EACb,4BAA4B,0BAA0B,MAAM;EAC5D,UAAU,MAAM;EAChB,gBAAgB,MAAM;EACtB,iBAAiB,MAAM;EACxB,CAAC,CACH;;AAaH,SAAgB,mDACd,OAC+B;CAC/B,OAAO;EACL,GAAG,MAAM;EACT,cAAc,uBAAuB,6BACnC,KAAK,UAAU;GACb,UAAU,MAAM;GAChB,gBAAgB,MAAM;GACvB,CAAC,CACH;EACD,aAAa,iBAAiB,6BAC5B,KAAK,UAAU;GACb,UAAU,MAAM;GAChB,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACxB,CAAC,CACH;EACF"}
|
package/dist/shims/cache.d.ts
CHANGED
|
@@ -94,9 +94,19 @@ declare class NoOpCacheHandler implements CacheHandler {
|
|
|
94
94
|
expire?: number;
|
|
95
95
|
}): Promise<void>;
|
|
96
96
|
}
|
|
97
|
+
type MemoryCacheHandlerOptions = Pick<CacheHandlerContext, "maxMemoryCacheSize"> & {
|
|
98
|
+
cacheMaxMemorySize?: number;
|
|
99
|
+
};
|
|
97
100
|
declare class MemoryCacheHandler implements CacheHandler {
|
|
98
101
|
private store;
|
|
99
102
|
private tagRevalidatedAt;
|
|
103
|
+
private readonly maxMemoryCacheSize;
|
|
104
|
+
private currentMemoryCacheSize;
|
|
105
|
+
constructor(options?: number | MemoryCacheHandlerOptions);
|
|
106
|
+
private estimateEntrySize;
|
|
107
|
+
private deleteEntry;
|
|
108
|
+
private touchEntry;
|
|
109
|
+
private evictLeastRecentlyUsed;
|
|
100
110
|
get(key: string, _ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
|
|
101
111
|
set(key: string, data: IncrementalCacheValue | null, ctx?: Record<string, unknown>): Promise<void>;
|
|
102
112
|
revalidateTag(tags: string | string[], _durations?: {
|
|
@@ -104,6 +114,7 @@ declare class MemoryCacheHandler implements CacheHandler {
|
|
|
104
114
|
}): Promise<void>;
|
|
105
115
|
resetRequestCache(): void;
|
|
106
116
|
}
|
|
117
|
+
declare function configureMemoryCacheHandler(options?: MemoryCacheHandlerOptions): void;
|
|
107
118
|
/**
|
|
108
119
|
* Set a custom CacheHandler. Call this during server startup to
|
|
109
120
|
* plug in Cloudflare KV, Redis, DynamoDB, or any other backend.
|
|
@@ -212,9 +223,17 @@ declare function io(): Promise<void>;
|
|
|
212
223
|
declare function unstable_io(): Promise<void>;
|
|
213
224
|
type UnstableCacheRevalidationMode = "foreground" | "background";
|
|
214
225
|
type ActionRevalidationKind = 0 | 1 | 2;
|
|
226
|
+
type UnstableCacheObservation = Readonly<{
|
|
227
|
+
kind: "unstable_cache";
|
|
228
|
+
keyHash: string;
|
|
229
|
+
revalidate: number | false | null;
|
|
230
|
+
tagCount: number;
|
|
231
|
+
tagHash: string | null;
|
|
232
|
+
}>;
|
|
215
233
|
type CacheState = {
|
|
216
234
|
actionRevalidationKind: ActionRevalidationKind;
|
|
217
235
|
requestScopedCacheLife: CacheLifeConfig | null;
|
|
236
|
+
unstableCacheObservations: Map<string, UnstableCacheObservation>;
|
|
218
237
|
unstableCacheRevalidation: UnstableCacheRevalidationMode;
|
|
219
238
|
};
|
|
220
239
|
/**
|
|
@@ -250,6 +269,7 @@ declare function _peekRequestScopedCacheLife(): CacheLifeConfig | null;
|
|
|
250
269
|
* @internal
|
|
251
270
|
*/
|
|
252
271
|
declare function _consumeRequestScopedCacheLife(): CacheLifeConfig | null;
|
|
272
|
+
declare function _peekUnstableCacheObservations(): UnstableCacheObservation[];
|
|
253
273
|
/**
|
|
254
274
|
* Cache life configuration. Controls stale-while-revalidate behavior.
|
|
255
275
|
*/
|
|
@@ -307,5 +327,5 @@ type UnstableCacheOptions = {
|
|
|
307
327
|
*/
|
|
308
328
|
declare function unstable_cache<T extends (...args: any[]) => Promise<any>>(fn: T, keyParts?: string[], options?: UnstableCacheOptions): T;
|
|
309
329
|
//#endregion
|
|
310
|
-
export { ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CacheLifeConfig, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, UnstableCacheRevalidationMode, _consumeRequestScopedCacheLife, _initRequestScopedCacheState, _peekRequestScopedCacheLife, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, getAndClearActionRevalidationKind, getCacheHandler, getRequestExecutionContext, io, isInsideUnstableCacheScope, unstable_noStore as noStore, unstable_noStore, refresh, revalidatePath, revalidateTag, runWithExecutionContext, setCacheHandler, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
|
|
330
|
+
export { ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CacheLifeConfig, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, UnstableCacheObservation, UnstableCacheRevalidationMode, _consumeRequestScopedCacheLife, _initRequestScopedCacheState, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getRequestExecutionContext, io, isInsideUnstableCacheScope, unstable_noStore as noStore, unstable_noStore, refresh, revalidatePath, revalidateTag, runWithExecutionContext, setCacheHandler, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
|
|
311
331
|
//# sourceMappingURL=cache.d.ts.map
|
package/dist/shims/cache.js
CHANGED
|
@@ -42,6 +42,36 @@ var NoOpCacheHandler = class {
|
|
|
42
42
|
async set(_key, _data, _ctx) {}
|
|
43
43
|
async revalidateTag(_tags, _durations) {}
|
|
44
44
|
};
|
|
45
|
+
const DEFAULT_MEMORY_CACHE_MAX_SIZE = 50 * 1024 * 1024;
|
|
46
|
+
const MAX_REVALIDATED_TAG_ENTRIES = 1e4;
|
|
47
|
+
function estimateStringMapSize(map) {
|
|
48
|
+
if (!map) return 0;
|
|
49
|
+
let size = 0;
|
|
50
|
+
for (const [key, value] of Object.entries(map)) {
|
|
51
|
+
size += key.length;
|
|
52
|
+
if (Array.isArray(value)) for (const item of value) size += item.length;
|
|
53
|
+
else size += value.length;
|
|
54
|
+
}
|
|
55
|
+
return size;
|
|
56
|
+
}
|
|
57
|
+
function estimateIncrementalCacheValueSize(value) {
|
|
58
|
+
if (value === null) return 25;
|
|
59
|
+
switch (value.kind) {
|
|
60
|
+
case "FETCH": return JSON.stringify(value.data ?? "").length;
|
|
61
|
+
case "PAGES": return value.html.length + JSON.stringify(value.pageData ?? {}).length + estimateStringMapSize(value.headers);
|
|
62
|
+
case "APP_PAGE": return value.html.length + (value.rscData?.byteLength ?? 0) + (value.postponed?.length ?? 0) + estimateStringMapSize(value.headers);
|
|
63
|
+
case "APP_ROUTE": return value.body.byteLength + estimateStringMapSize(value.headers);
|
|
64
|
+
case "REDIRECT": return JSON.stringify(value.props ?? {}).length;
|
|
65
|
+
case "IMAGE": return value.buffer.byteLength + value.extension.length + value.etag.length;
|
|
66
|
+
default: return JSON.stringify(value).length;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function resolveMemoryCacheMaxSize(options) {
|
|
70
|
+
if (typeof options === "number") return options;
|
|
71
|
+
if (typeof options?.cacheMaxMemorySize === "number") return options.cacheMaxMemorySize;
|
|
72
|
+
if (typeof options?.maxMemoryCacheSize === "number") return options.maxMemoryCacheSize;
|
|
73
|
+
return DEFAULT_MEMORY_CACHE_MAX_SIZE;
|
|
74
|
+
}
|
|
45
75
|
function readStringArrayField(ctx, field) {
|
|
46
76
|
const value = ctx?.[field];
|
|
47
77
|
if (!Array.isArray(value)) return [];
|
|
@@ -50,13 +80,38 @@ function readStringArrayField(ctx, field) {
|
|
|
50
80
|
var MemoryCacheHandler = class {
|
|
51
81
|
store = /* @__PURE__ */ new Map();
|
|
52
82
|
tagRevalidatedAt = /* @__PURE__ */ new Map();
|
|
83
|
+
maxMemoryCacheSize;
|
|
84
|
+
currentMemoryCacheSize = 0;
|
|
85
|
+
constructor(options) {
|
|
86
|
+
this.maxMemoryCacheSize = resolveMemoryCacheMaxSize(options);
|
|
87
|
+
}
|
|
88
|
+
estimateEntrySize(entry) {
|
|
89
|
+
return estimateIncrementalCacheValueSize(entry.value) + entry.tags.reduce((sum, tag) => sum + tag.length, 0) + 64;
|
|
90
|
+
}
|
|
91
|
+
deleteEntry(key) {
|
|
92
|
+
const existing = this.store.get(key);
|
|
93
|
+
if (!existing) return;
|
|
94
|
+
this.currentMemoryCacheSize -= this.estimateEntrySize(existing);
|
|
95
|
+
this.store.delete(key);
|
|
96
|
+
}
|
|
97
|
+
touchEntry(key, entry) {
|
|
98
|
+
this.store.delete(key);
|
|
99
|
+
this.store.set(key, entry);
|
|
100
|
+
}
|
|
101
|
+
evictLeastRecentlyUsed() {
|
|
102
|
+
while (this.maxMemoryCacheSize > 0 && this.currentMemoryCacheSize > this.maxMemoryCacheSize) {
|
|
103
|
+
const oldestKey = this.store.keys().next().value;
|
|
104
|
+
if (oldestKey === void 0) return;
|
|
105
|
+
this.deleteEntry(oldestKey);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
53
108
|
async get(key, _ctx) {
|
|
54
109
|
const entry = this.store.get(key);
|
|
55
110
|
if (!entry) return null;
|
|
56
111
|
for (const tag of entry.tags) {
|
|
57
112
|
const revalidatedAt = this.tagRevalidatedAt.get(tag);
|
|
58
113
|
if (revalidatedAt && revalidatedAt >= entry.lastModified) {
|
|
59
|
-
this.
|
|
114
|
+
this.deleteEntry(key);
|
|
60
115
|
return null;
|
|
61
116
|
}
|
|
62
117
|
}
|
|
@@ -65,9 +120,10 @@ var MemoryCacheHandler = class {
|
|
|
65
120
|
if (revalidatedAt && revalidatedAt >= entry.lastModified) return null;
|
|
66
121
|
}
|
|
67
122
|
if (entry.expireAt !== null && Date.now() > entry.expireAt) {
|
|
68
|
-
this.
|
|
123
|
+
this.deleteEntry(key);
|
|
69
124
|
return null;
|
|
70
125
|
}
|
|
126
|
+
this.touchEntry(key, entry);
|
|
71
127
|
if (entry.revalidateAt !== null && Date.now() > entry.revalidateAt) return {
|
|
72
128
|
lastModified: entry.lastModified,
|
|
73
129
|
value: entry.value,
|
|
@@ -98,19 +154,36 @@ var MemoryCacheHandler = class {
|
|
|
98
154
|
revalidate: effectiveRevalidate,
|
|
99
155
|
expire: effectiveExpire
|
|
100
156
|
} : void 0;
|
|
101
|
-
this.
|
|
157
|
+
if (this.maxMemoryCacheSize === 0) return;
|
|
158
|
+
const entry = {
|
|
102
159
|
value: data,
|
|
103
160
|
tags,
|
|
104
161
|
lastModified: now,
|
|
105
162
|
revalidateAt,
|
|
106
163
|
expireAt,
|
|
107
164
|
cacheControl
|
|
108
|
-
}
|
|
165
|
+
};
|
|
166
|
+
const entrySize = this.estimateEntrySize(entry);
|
|
167
|
+
if (entrySize > this.maxMemoryCacheSize) {
|
|
168
|
+
this.deleteEntry(key);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.deleteEntry(key);
|
|
172
|
+
this.store.set(key, entry);
|
|
173
|
+
this.currentMemoryCacheSize += entrySize;
|
|
174
|
+
this.evictLeastRecentlyUsed();
|
|
109
175
|
}
|
|
110
176
|
async revalidateTag(tags, _durations) {
|
|
111
177
|
const tagList = Array.isArray(tags) ? tags : [tags];
|
|
112
178
|
const now = Date.now();
|
|
113
|
-
for (const tag of tagList)
|
|
179
|
+
for (const tag of tagList) {
|
|
180
|
+
this.tagRevalidatedAt.set(tag, now);
|
|
181
|
+
while (this.tagRevalidatedAt.size > MAX_REVALIDATED_TAG_ENTRIES) {
|
|
182
|
+
const oldest = this.tagRevalidatedAt.keys().next().value;
|
|
183
|
+
if (oldest === void 0) break;
|
|
184
|
+
this.tagRevalidatedAt.delete(oldest);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
114
187
|
}
|
|
115
188
|
resetRequestCache() {}
|
|
116
189
|
};
|
|
@@ -119,6 +192,11 @@ const _gHandler = globalThis;
|
|
|
119
192
|
function _getActiveHandler() {
|
|
120
193
|
return _gHandler[_HANDLER_KEY] ?? (_gHandler[_HANDLER_KEY] = new MemoryCacheHandler());
|
|
121
194
|
}
|
|
195
|
+
function configureMemoryCacheHandler(options) {
|
|
196
|
+
const current = _gHandler[_HANDLER_KEY];
|
|
197
|
+
if (current && !(current instanceof MemoryCacheHandler)) return;
|
|
198
|
+
_gHandler[_HANDLER_KEY] = new MemoryCacheHandler(options);
|
|
199
|
+
}
|
|
122
200
|
/**
|
|
123
201
|
* Set a custom CacheHandler. Call this during server startup to
|
|
124
202
|
* plug in Cloudflare KV, Redis, DynamoDB, or any other backend.
|
|
@@ -286,6 +364,7 @@ const _cacheAls = getOrCreateAls("vinext.cache.als");
|
|
|
286
364
|
const _cacheFallbackState = _g[_FALLBACK_KEY] ??= {
|
|
287
365
|
actionRevalidationKind: 0,
|
|
288
366
|
requestScopedCacheLife: null,
|
|
367
|
+
unstableCacheObservations: /* @__PURE__ */ new Map(),
|
|
289
368
|
unstableCacheRevalidation: "foreground"
|
|
290
369
|
};
|
|
291
370
|
const ACTION_DID_NOT_REVALIDATE = 0;
|
|
@@ -299,11 +378,13 @@ function _runWithCacheState(fn) {
|
|
|
299
378
|
if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((uCtx) => {
|
|
300
379
|
uCtx.actionRevalidationKind = ACTION_DID_NOT_REVALIDATE;
|
|
301
380
|
uCtx.requestScopedCacheLife = null;
|
|
381
|
+
uCtx.unstableCacheObservations = /* @__PURE__ */ new Map();
|
|
302
382
|
uCtx.unstableCacheRevalidation = "foreground";
|
|
303
383
|
}, fn);
|
|
304
384
|
const state = {
|
|
305
385
|
actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
|
|
306
386
|
requestScopedCacheLife: null,
|
|
387
|
+
unstableCacheObservations: /* @__PURE__ */ new Map(),
|
|
307
388
|
unstableCacheRevalidation: "foreground"
|
|
308
389
|
};
|
|
309
390
|
return _cacheAls.run(state, fn);
|
|
@@ -317,6 +398,7 @@ function _initRequestScopedCacheState() {
|
|
|
317
398
|
const state = _getCacheState();
|
|
318
399
|
state.actionRevalidationKind = ACTION_DID_NOT_REVALIDATE;
|
|
319
400
|
state.requestScopedCacheLife = null;
|
|
401
|
+
state.unstableCacheObservations = /* @__PURE__ */ new Map();
|
|
320
402
|
}
|
|
321
403
|
function markActionRevalidation(kind) {
|
|
322
404
|
if (getHeadersAccessPhase() !== "action") return;
|
|
@@ -363,6 +445,12 @@ function _consumeRequestScopedCacheLife() {
|
|
|
363
445
|
state.requestScopedCacheLife = null;
|
|
364
446
|
return config;
|
|
365
447
|
}
|
|
448
|
+
function recordUnstableCacheObservation(observation) {
|
|
449
|
+
_getCacheState().unstableCacheObservations.set(observation.keyHash, observation);
|
|
450
|
+
}
|
|
451
|
+
function _peekUnstableCacheObservations() {
|
|
452
|
+
return [..._getCacheState().unstableCacheObservations.values()].sort((a, b) => a.keyHash.localeCompare(b.keyHash));
|
|
453
|
+
}
|
|
366
454
|
/**
|
|
367
455
|
* Built-in cache life profiles matching Next.js 16.
|
|
368
456
|
*/
|
|
@@ -590,6 +678,13 @@ function unstable_cache(fn, keyParts, options) {
|
|
|
590
678
|
const revalidateSeconds = options?.revalidate;
|
|
591
679
|
const cachedFn = async (...args) => {
|
|
592
680
|
const cacheKey = `unstable_cache:${baseKey}:${JSON.stringify(args)}`;
|
|
681
|
+
recordUnstableCacheObservation({
|
|
682
|
+
kind: "unstable_cache",
|
|
683
|
+
keyHash: fnv1a64(cacheKey),
|
|
684
|
+
revalidate: typeof revalidateSeconds === "number" ? revalidateSeconds : revalidateSeconds === false ? false : null,
|
|
685
|
+
tagCount: tags.length,
|
|
686
|
+
tagHash: tags.length > 0 ? fnv1a64(JSON.stringify(tags)) : null
|
|
687
|
+
});
|
|
593
688
|
const existing = await _getActiveHandler().get(cacheKey, {
|
|
594
689
|
kind: "FETCH",
|
|
595
690
|
tags
|
|
@@ -608,6 +703,6 @@ function unstable_cache(fn, keyParts, options) {
|
|
|
608
703
|
return cachedFn;
|
|
609
704
|
}
|
|
610
705
|
//#endregion
|
|
611
|
-
export { MemoryCacheHandler, NoOpCacheHandler, _consumeRequestScopedCacheLife, _initRequestScopedCacheState, _peekRequestScopedCacheLife, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, getAndClearActionRevalidationKind, getCacheHandler, getRequestExecutionContext, io, isInsideUnstableCacheScope, unstable_noStore as noStore, unstable_noStore, refresh, revalidatePath, revalidateTag, runWithExecutionContext, setCacheHandler, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
|
|
706
|
+
export { MemoryCacheHandler, NoOpCacheHandler, _consumeRequestScopedCacheLife, _initRequestScopedCacheState, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getRequestExecutionContext, io, isInsideUnstableCacheScope, unstable_noStore as noStore, unstable_noStore, refresh, revalidatePath, revalidateTag, runWithExecutionContext, setCacheHandler, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
|
|
612
707
|
|
|
613
708
|
//# sourceMappingURL=cache.js.map
|