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.
Files changed (135) hide show
  1. package/README.md +1 -0
  2. package/dist/check.js +15 -3
  3. package/dist/check.js.map +1 -1
  4. package/dist/client/navigation-runtime.d.ts +1 -0
  5. package/dist/client/navigation-runtime.js +1 -1
  6. package/dist/client/navigation-runtime.js.map +1 -1
  7. package/dist/config/next-config.d.ts +14 -1
  8. package/dist/config/next-config.js +24 -4
  9. package/dist/config/next-config.js.map +1 -1
  10. package/dist/config/tsconfig-paths.d.ts +12 -3
  11. package/dist/config/tsconfig-paths.js +55 -24
  12. package/dist/config/tsconfig-paths.js.map +1 -1
  13. package/dist/entries/app-rsc-entry.d.ts +2 -1
  14. package/dist/entries/app-rsc-entry.js +12 -0
  15. package/dist/entries/app-rsc-entry.js.map +1 -1
  16. package/dist/entries/app-rsc-manifest.js +22 -5
  17. package/dist/entries/app-rsc-manifest.js.map +1 -1
  18. package/dist/entries/pages-server-entry.js +41 -4
  19. package/dist/entries/pages-server-entry.js.map +1 -1
  20. package/dist/index.js +81 -39
  21. package/dist/index.js.map +1 -1
  22. package/dist/plugins/import-meta-url.d.ts +16 -0
  23. package/dist/plugins/import-meta-url.js +193 -0
  24. package/dist/plugins/import-meta-url.js.map +1 -0
  25. package/dist/server/app-browser-action-result.d.ts +9 -16
  26. package/dist/server/app-browser-action-result.js +25 -14
  27. package/dist/server/app-browser-action-result.js.map +1 -1
  28. package/dist/server/app-browser-entry.js +171 -45
  29. package/dist/server/app-browser-entry.js.map +1 -1
  30. package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
  31. package/dist/server/app-browser-mpa-navigation.js +36 -0
  32. package/dist/server/app-browser-mpa-navigation.js.map +1 -0
  33. package/dist/server/app-browser-popstate.d.ts +3 -1
  34. package/dist/server/app-browser-popstate.js +15 -1
  35. package/dist/server/app-browser-popstate.js.map +1 -1
  36. package/dist/server/app-browser-state.js +2 -1
  37. package/dist/server/app-browser-state.js.map +1 -1
  38. package/dist/server/app-layout-param-observation.d.ts +30 -0
  39. package/dist/server/app-layout-param-observation.js +130 -0
  40. package/dist/server/app-layout-param-observation.js.map +1 -0
  41. package/dist/server/app-page-boundary-render.js +2 -2
  42. package/dist/server/app-page-boundary-render.js.map +1 -1
  43. package/dist/server/app-page-dispatch.js +1 -1
  44. package/dist/server/app-page-params.d.ts +2 -1
  45. package/dist/server/app-page-params.js +14 -1
  46. package/dist/server/app-page-params.js.map +1 -1
  47. package/dist/server/app-page-probe.d.ts +12 -1
  48. package/dist/server/app-page-probe.js +116 -1
  49. package/dist/server/app-page-probe.js.map +1 -1
  50. package/dist/server/app-route-handler-response.js +1 -1
  51. package/dist/server/app-route-handler-response.js.map +1 -1
  52. package/dist/server/app-rsc-cache-busting.d.ts +3 -2
  53. package/dist/server/app-rsc-cache-busting.js +9 -7
  54. package/dist/server/app-rsc-cache-busting.js.map +1 -1
  55. package/dist/server/app-rsc-handler.js +11 -1
  56. package/dist/server/app-rsc-handler.js.map +1 -1
  57. package/dist/server/app-segment-config.d.ts +1 -1
  58. package/dist/server/app-segment-config.js +4 -1
  59. package/dist/server/app-segment-config.js.map +1 -1
  60. package/dist/server/app-server-action-execution.d.ts +5 -0
  61. package/dist/server/app-server-action-execution.js +198 -22
  62. package/dist/server/app-server-action-execution.js.map +1 -1
  63. package/dist/server/artifact-compatibility.d.ts +2 -1
  64. package/dist/server/artifact-compatibility.js +10 -1
  65. package/dist/server/artifact-compatibility.js.map +1 -1
  66. package/dist/server/client-reuse-manifest.d.ts +9 -4
  67. package/dist/server/client-reuse-manifest.js +2 -1
  68. package/dist/server/client-reuse-manifest.js.map +1 -1
  69. package/dist/server/dev-server.js +52 -10
  70. package/dist/server/dev-server.js.map +1 -1
  71. package/dist/server/document-initial-head.d.ts +7 -0
  72. package/dist/server/document-initial-head.js +35 -0
  73. package/dist/server/document-initial-head.js.map +1 -0
  74. package/dist/server/pages-document-initial-props.d.ts +84 -2
  75. package/dist/server/pages-document-initial-props.js +127 -1
  76. package/dist/server/pages-document-initial-props.js.map +1 -1
  77. package/dist/server/pages-node-compat.js +1 -1
  78. package/dist/server/pages-page-response.d.ts +14 -0
  79. package/dist/server/pages-page-response.js +31 -8
  80. package/dist/server/pages-page-response.js.map +1 -1
  81. package/dist/server/prod-server.js +13 -6
  82. package/dist/server/prod-server.js.map +1 -1
  83. package/dist/server/skip-cache-proof.d.ts +23 -2
  84. package/dist/server/skip-cache-proof.js +81 -12
  85. package/dist/server/skip-cache-proof.js.map +1 -1
  86. package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
  87. package/dist/server/static-layout-client-reuse-proof.js +35 -0
  88. package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
  89. package/dist/shims/cache.d.ts +21 -1
  90. package/dist/shims/cache.js +101 -6
  91. package/dist/shims/cache.js.map +1 -1
  92. package/dist/shims/document.d.ts +6 -0
  93. package/dist/shims/document.js +7 -8
  94. package/dist/shims/document.js.map +1 -1
  95. package/dist/shims/error-boundary.d.ts +4 -4
  96. package/dist/shims/error-boundary.js +27 -28
  97. package/dist/shims/error-boundary.js.map +1 -1
  98. package/dist/shims/fetch-cache.d.ts +3 -1
  99. package/dist/shims/fetch-cache.js +16 -5
  100. package/dist/shims/fetch-cache.js.map +1 -1
  101. package/dist/shims/hash-scroll.d.ts +4 -1
  102. package/dist/shims/hash-scroll.js +13 -1
  103. package/dist/shims/hash-scroll.js.map +1 -1
  104. package/dist/shims/head-state.d.ts +1 -0
  105. package/dist/shims/head-state.js +18 -3
  106. package/dist/shims/head-state.js.map +1 -1
  107. package/dist/shims/head.d.ts +35 -1
  108. package/dist/shims/head.js +113 -14
  109. package/dist/shims/head.js.map +1 -1
  110. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
  111. package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
  112. package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
  113. package/dist/shims/link.js +28 -2
  114. package/dist/shims/link.js.map +1 -1
  115. package/dist/shims/navigation.d.ts +39 -1
  116. package/dist/shims/navigation.js +61 -13
  117. package/dist/shims/navigation.js.map +1 -1
  118. package/dist/shims/router.js +37 -17
  119. package/dist/shims/router.js.map +1 -1
  120. package/dist/shims/thenable-params.d.ts +5 -2
  121. package/dist/shims/thenable-params.js +25 -1
  122. package/dist/shims/thenable-params.js.map +1 -1
  123. package/dist/shims/unified-request-context.js +3 -0
  124. package/dist/shims/unified-request-context.js.map +1 -1
  125. package/dist/utils/client-build-manifest.d.ts +15 -0
  126. package/dist/utils/client-build-manifest.js +54 -0
  127. package/dist/utils/client-build-manifest.js.map +1 -0
  128. package/dist/utils/hash.js +1 -1
  129. package/dist/utils/hash.js.map +1 -1
  130. package/dist/utils/lazy-chunks.d.ts +1 -1
  131. package/dist/utils/lazy-chunks.js.map +1 -1
  132. package/dist/utils/vite-version.d.ts +11 -0
  133. package/dist/utils/vite-version.js +36 -0
  134. package/dist/utils/vite-version.js.map +1 -0
  135. 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: createDisabledSkipDisposition()
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":";;;;AAkFA,SAAS,gCAAoE;CAC3E,OAAO;EACL,MAAM;EACN,SAAS;EACT,MAAM;EACP;;AAGH,SAAS,kCACP,iBACoC;CACpC,OAAO;EACL,MAAM;EACN,SAAS;EACT,MAAM;EACN,iBAAiB,CAAC,GAAG,gBAAgB;EACtC;;AAGH,SAAS,0BACP,OACA,MACA,SAAyC,EAAE,EACd;CAC7B,OAAO;EACL,MAAM;EACN,WAAW;GACT;GACA,SAAS,MAAM;GACf;GACD;EACD,iBAAiB,+BAA+B;EACjD;;AAGH,SAAS,4CACP,uBACA,oBACmB;CACnB,MAAM,mBAA6B,EAAE;CACrC,KAAK,MAAM,SAAS,qCAClB,IAAI,sBAAsB,WAAW,mBAAmB,QACtD,iBAAiB,KAAK,MAAM;CAGhC,OAAO;;AAGT,SAAS,6BACP,uBACA,oBACS;CACT,OACE,4CAA4C,uBAAuB,mBAAmB,CACnF,WAAW;;AAIlB,SAAS,YAAY,OAAqB;CACxC,MAAM,IAAI,MAAM,qCAAqC,OAAO,MAAM,GAAG;;AAGvE,SAAS,wBAAwB,SAIA;CAC/B,OAAO;EACL,MAAM;EACN,iBAAiB,QAAQ,mBAAmB,EAAE;EAC9C,GAAI,QAAQ,oBAAoB,EAAE,mBAAmB,QAAQ,mBAAmB,GAAG,EAAE;EACrF,iBAAiB,+BAA+B;EAChD,wBAAwB,QAAQ,0BAA0B,EAAE;EAC5D,iBAAiB,EAAE;EACpB;;AAGH,SAAS,0CACP,kBACA,wBAC8B;CAC9B,OAAO;EACL,MAAM;EACN,QAAQ;GAAE;GAAkB;GAAwB;EACrD;;AAGH,SAAS,4BACP,OACA,cACoC;CACpC,QAAQ,aAAa,MAArB;EACE,KAAK,SACH,OAAO;EACT,KAAK,WACH,OAAO,0BAA0B,OAAO,kCAAkC;EAC5E,KAAK,eACH,OAAO,0BAA0B,OAAO,0BAA0B,EAChE,mBAAmB,aAAa,mBACjC,CAAC;EACJ,SACE,OAAO,YAAY,aAAa;;;AAItC,SAAgB,4CACd,OAC2B;CAC3B,MAAM,EAAE,eAAe,UAAU;CACjC,IAAI,kBAAkB,MACpB,OAAO,0BAA0B,OAAO,2BAA2B;CAErE,IAAI,cAAc,SAAS,YACzB,OAAO,0BAA0B,OAAO,6BAA6B;EACnE,gBAAgB,cAAc,SAAS;EACvC,gBAAgB,cAAc,SAAS;EACvC,iBAAiB,cAAc,SAAS;EACzC,CAAC;CAGJ,MAAM,EAAE,UAAU;CAClB,IAAI,MAAM,SAAS,YAAY,MAAM,eAAe,iBAClD,OAAO,0BAA0B,OAAO,sCAAsC;EAC5E,WAAW,MAAM;EACjB,YAAY,MAAM;EACnB,CAAC;CAGJ,IAAI,MAAM,OAAO,MAAM,gBAAgB,UACrC,OAAO,0BAA0B,OAAO,gCAAgC;EACtE,cAAc,MAAM,gBAAgB;EACpC,iBAAiB,MAAM;EACxB,CAAC;CAGJ,MAAM,0BAA0B,4CAC9B,MAAM,SAAS,eACf,MAAM,+BACP;CACD,IAAI,wBAAwB,SAAS,GACnC,OAAO,0BAA0B,OAAO,sCAAsC,EAC5E,kBAAkB,yBACnB,CAAC;CAGJ,MAAM,wBAAwB,8BAC5B,MAAM,SAAS,eACf,MAAM,uBACN,EAAE,kBAAkB,MAAM,kBAAkB,CAC7C;CACD,IAAI,sBAAsB,SAAS,WACjC,OAAO,0BAA0B,OAAO,6CAA6C;EACnF,uBAAuB,sBAAsB;EAC7C,QAAQ,sBAAsB;EAC/B,CAAC;CAEJ,IAAI,sBAAsB,SAAS,gBACjC,OAAO,0BAA0B,OAAO,kDAAkD;EACxF,uBAAuB,sBAAsB;EAC7C,QAAQ,sBAAsB;EAC/B,CAAC;CAGJ,IAAI,MAAM,oBAAoB,MAAM,QAAQ,UAC1C,OAAO,0BAA0B,OAAO,+BAA+B;EACrE,0BAA0B,6BAA6B,MAAM,QAAQ,SAAS;EAC9E,0BAA0B,6BAA6B,MAAM,gBAAgB;EAC9E,CAAC;CAGJ,IAAI,MAAM,SAAS,gBAAgB,MACjC,OAAO,0BAA0B,OAAO,kCAAkC;CAG5E,IAAI,MAAM,gBAAgB,MAAM,SAAS,aACvC,OAAO,0BAA0B,OAAO,oCAAoC;EAC1E,kBAAkB,MAAM,SAAS;EACjC,kBAAkB,MAAM;EACzB,CAAC;CAGJ,MAAM,wBAAwB,4BAA4B,OAAO,MAAM,SAAS,aAAa;CAC7F,IAAI,uBAAuB,OAAO;CAUlC,MAAM,kBAAkB,6BACtB,MAAM,SAAS,eACf,MAAM,sBACP,GACG,kCAAkC,CAAC,MAAM,GAAG,CAAC,GAC7C,+BAA+B;CAEnC,OAAO;EACL,MAAM;EACN,MAAM;EACN,SAAS,MAAM;EACf,QAAQ;GACN,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,qBAAqB,6BAA6B,MAAM,QAAQ,SAAS;GAC1E;EACD;EACD;;AAGH,SAAgB,mCACd,OAC8B;CAC9B,MAAM,EAAE,aAAa;CACrB,IAAI,SAAS,SAAS,UACpB,OAAO,wBAAwB,EAAE,CAAC;CAEpC,IAAI,SAAS,SAAS,YACpB,OAAO,wBAAwB,EAAE,mBAAmB,SAAS,WAAW,CAAC;CAG3E,MAAM,yBACJ,MAAM,0BAAA;CACR,IAAI,CAAC,OAAO,cAAc,uBAAuB,IAAI,yBAAyB,GAC5E,MAAM,IAAI,WAAW,6DAA6D;CAGpF,MAAM,mBAAmB,SAAS,SAAS,QAAQ,SAAS,SAAS,gBAAgB;CACrF,IAAI,mBAAmB,wBACrB,OAAO,wBAAwB;EAC7B,iBAAiB,SAAS;EAC1B,mBAAmB,0CACjB,kBACA,uBACD;EACF,CAAC;CAGJ,MAAM,kBAA4B,EAAE;CACpC,MAAM,yBAAmC,EAAE;CAC3C,MAAM,kBAAuD,CAAC,GAAG,SAAS,gBAAgB;CAC1F,KAAK,MAAM,SAAS,SAAS,SAAS,SAAS;EAC7C,MAAM,eAAe,MAAM,YAAY,MAAM;EAC7C,IAAI,aAAa,SAAS,YAAY;GACpC,gBAAgB,KAAK,aAAa,UAAU;GAC5C;;EAMF,IAAI,aAAa,YAAY,MAAM,IAAI;GACrC,gBAAgB,KAAK;IACnB,MAAM;IACN,SAAS,MAAM;IACf,QAAQ;KACN,iBAAiB,aAAa;KAC9B,iBAAiB,MAAM;KACxB;IACF,CAAC;GACF;;EAGF,IAAI,aAAa,gBAAgB,SAC/B,gBAAgB,KAAK,MAAM,GAAG;OAE9B,uBAAuB,KAAK,MAAM,GAAG;;CAIzC,IAAI,gBAAgB,WAAW,GAC7B,OAAO,wBAAwB;EAAE;EAAiB;EAAwB,CAAC;CAG7E,OAAO;EACL,MAAM;EACN;EACA,iBAAiB,kCAAkC,gBAAgB;EACnE;EACA;EACD"}
@@ -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"}
@@ -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
@@ -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.store.delete(key);
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.store.delete(key);
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.store.set(key, {
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) this.tagRevalidatedAt.set(tag, now);
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