vinext 0.0.51 → 0.0.52
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/dist/build/precompress.d.ts +7 -7
- package/dist/build/precompress.js +18 -17
- package/dist/build/precompress.js.map +1 -1
- package/dist/build/prerender.d.ts +3 -14
- package/dist/build/prerender.js +40 -40
- package/dist/build/prerender.js.map +1 -1
- package/dist/check.js +4 -0
- package/dist/check.js.map +1 -1
- package/dist/cli-args.d.ts +1 -0
- package/dist/cli-args.js +5 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +47 -0
- package/dist/client/navigation-runtime.js +156 -0
- package/dist/client/navigation-runtime.js.map +1 -0
- package/dist/client/pages-router-link-navigation.d.ts +26 -0
- package/dist/client/pages-router-link-navigation.js +14 -0
- package/dist/client/pages-router-link-navigation.js.map +1 -0
- package/dist/client/vinext-next-data.d.ts +12 -2
- package/dist/client/vinext-next-data.js +50 -1
- package/dist/client/vinext-next-data.js.map +1 -0
- package/dist/cloudflare/kv-cache-handler.js +2 -1
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/config/config-matchers.d.ts +63 -16
- package/dist/config/config-matchers.js +143 -8
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +20 -2
- package/dist/config/next-config.js +11 -1
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +101 -39
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.js +9 -3
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +53 -13
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +1 -0
- package/dist/entries/app-rsc-manifest.js +53 -6
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/app-ssr-entry.d.ts +3 -3
- package/dist/entries/app-ssr-entry.js +4 -4
- package/dist/entries/app-ssr-entry.js.map +1 -1
- package/dist/entries/pages-client-entry.js +18 -2
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +58 -8
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +2 -1
- package/dist/entries/runtime-entry-module.js +9 -3
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +132 -40
- package/dist/index.js.map +1 -1
- package/dist/plugins/css-data-url.d.ts +7 -0
- package/dist/plugins/css-data-url.js +81 -0
- package/dist/plugins/css-data-url.js.map +1 -0
- package/dist/plugins/fonts.js +5 -3
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/middleware-server-only.d.ts +54 -0
- package/dist/plugins/middleware-server-only.js +91 -0
- package/dist/plugins/middleware-server-only.js.map +1 -0
- package/dist/plugins/optimize-imports.js +4 -4
- package/dist/plugins/optimize-imports.js.map +1 -1
- package/dist/plugins/strip-server-exports.js +5 -8
- package/dist/plugins/strip-server-exports.js.map +1 -1
- package/dist/routing/app-route-graph.d.ts +20 -1
- package/dist/routing/app-route-graph.js +58 -6
- package/dist/routing/app-route-graph.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -2
- package/dist/routing/app-router.js +2 -2
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/utils.d.ts +2 -1
- package/dist/routing/utils.js +4 -1
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.js +139 -37
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-entry.js +293 -149
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-interception-context.d.ts +24 -0
- package/dist/server/app-browser-interception-context.js +32 -0
- package/dist/server/app-browser-interception-context.js.map +1 -0
- package/dist/server/app-browser-navigation-controller.d.ts +3 -1
- package/dist/server/app-browser-navigation-controller.js +5 -1
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
- package/dist/server/app-browser-rsc-redirect.js +2 -2
- package/dist/server/app-browser-rsc-redirect.js.map +1 -1
- package/dist/server/app-browser-state.d.ts +18 -1
- package/dist/server/app-browser-state.js +19 -1
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-browser-stream.d.ts +5 -14
- package/dist/server/app-browser-stream.js +13 -7
- package/dist/server/app-browser-stream.js.map +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +2 -1
- package/dist/server/app-browser-visible-commit.js +1 -0
- package/dist/server/app-browser-visible-commit.js.map +1 -1
- package/dist/server/app-elements-wire.d.ts +10 -5
- package/dist/server/app-elements-wire.js +84 -2
- package/dist/server/app-elements-wire.js.map +1 -1
- package/dist/server/app-elements.d.ts +3 -2
- package/dist/server/app-elements.js +3 -2
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.js +5 -3
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-middleware.d.ts +13 -0
- package/dist/server/app-middleware.js +3 -1
- package/dist/server/app-middleware.js.map +1 -1
- package/dist/server/app-optimistic-routing.d.ts +54 -0
- package/dist/server/app-optimistic-routing.js +200 -0
- package/dist/server/app-optimistic-routing.js.map +1 -0
- package/dist/server/app-page-cache.d.ts +13 -1
- package/dist/server/app-page-cache.js +61 -6
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +2 -0
- package/dist/server/app-page-dispatch.js +28 -1
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.js +2 -1
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +28 -1
- package/dist/server/app-page-execution.js +89 -4
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.js +21 -2
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-probe.js +1 -1
- package/dist/server/app-page-render.d.ts +2 -0
- package/dist/server/app-page-render.js +2 -1
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.js +17 -10
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +3 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-prerender-static-params.d.ts +2 -1
- package/dist/server/app-prerender-static-params.js +44 -8
- package/dist/server/app-prerender-static-params.js.map +1 -1
- package/dist/server/app-route-handler-cache.d.ts +2 -2
- package/dist/server/app-route-handler-cache.js +3 -2
- package/dist/server/app-route-handler-cache.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.d.ts +6 -1
- package/dist/server/app-route-handler-dispatch.js +1 -1
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +17 -2
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-response.js +5 -4
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-router-entry.js +6 -2
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/app-rsc-handler.d.ts +9 -1
- package/dist/server/app-rsc-handler.js +32 -14
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-render-mode.d.ts +4 -3
- package/dist/server/app-rsc-render-mode.js +7 -1
- package/dist/server/app-rsc-render-mode.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.d.ts +4 -1
- package/dist/server/app-rsc-request-normalization.js +4 -1
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-rsc-response-finalizer.d.ts +8 -1
- package/dist/server/app-rsc-response-finalizer.js +10 -3
- package/dist/server/app-rsc-response-finalizer.js.map +1 -1
- package/dist/server/app-rsc-route-matching.js +2 -2
- package/dist/server/app-rsc-route-matching.js.map +1 -1
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +2 -0
- package/dist/server/app-ssr-entry.js +56 -55
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +17 -3
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +1 -1
- package/dist/server/artifact-compatibility.js.map +1 -1
- package/dist/server/cache-headers.d.ts +7 -0
- package/dist/server/cache-headers.js +19 -0
- package/dist/server/cache-headers.js.map +1 -0
- package/dist/server/cache-proof.d.ts +49 -3
- package/dist/server/cache-proof.js +78 -22
- package/dist/server/cache-proof.js.map +1 -1
- package/dist/server/client-reuse-manifest.d.ts +99 -0
- package/dist/server/client-reuse-manifest.js +212 -0
- package/dist/server/client-reuse-manifest.js.map +1 -0
- package/dist/server/default-global-error-module.d.ts +20 -0
- package/dist/server/default-global-error-module.js +20 -0
- package/dist/server/default-global-error-module.js.map +1 -0
- package/dist/server/dev-server.d.ts +9 -1
- package/dist/server/dev-server.js +76 -29
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/edge-api-runtime.d.ts +5 -0
- package/dist/server/edge-api-runtime.js +8 -0
- package/dist/server/edge-api-runtime.js.map +1 -0
- package/dist/server/headers.d.ts +18 -1
- package/dist/server/headers.js +18 -1
- package/dist/server/headers.js.map +1 -1
- package/dist/server/http-error-responses.d.ts +16 -1
- package/dist/server/http-error-responses.js +21 -1
- package/dist/server/http-error-responses.js.map +1 -1
- package/dist/server/isr-cache.d.ts +6 -2
- package/dist/server/isr-cache.js +20 -4
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/middleware-runtime.d.ts +15 -0
- package/dist/server/middleware-runtime.js +59 -7
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +4 -2
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/navigation-planner.d.ts +9 -3
- package/dist/server/navigation-planner.js +98 -25
- package/dist/server/navigation-planner.js.map +1 -1
- package/dist/server/navigation-trace.d.ts +2 -1
- package/dist/server/navigation-trace.js +1 -0
- package/dist/server/navigation-trace.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +27 -1
- package/dist/server/pages-api-route.js +24 -3
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-data-route.d.ts +77 -0
- package/dist/server/pages-data-route.js +97 -0
- package/dist/server/pages-data-route.js.map +1 -0
- package/dist/server/pages-i18n.d.ts +51 -1
- package/dist/server/pages-i18n.js +61 -1
- package/dist/server/pages-i18n.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +29 -2
- package/dist/server/pages-page-data.js +31 -17
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +11 -1
- package/dist/server/pages-page-response.js +5 -3
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.d.ts +13 -15
- package/dist/server/prod-server.js +109 -56
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-pipeline.d.ts +11 -2
- package/dist/server/request-pipeline.js +28 -11
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/seed-cache.d.ts +12 -31
- package/dist/server/seed-cache.js +22 -35
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/server-action-not-found.js +8 -3
- package/dist/server/server-action-not-found.js.map +1 -1
- package/dist/server/skip-cache-proof.d.ts +41 -0
- package/dist/server/skip-cache-proof.js +101 -0
- package/dist/server/skip-cache-proof.js.map +1 -0
- package/dist/server/static-file-cache.d.ts +1 -1
- package/dist/server/static-file-cache.js +7 -6
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/shims/client-locale.d.ts +15 -0
- package/dist/shims/client-locale.js +13 -0
- package/dist/shims/client-locale.js.map +1 -0
- package/dist/shims/default-global-error.d.ts +32 -0
- package/dist/shims/default-global-error.js +181 -0
- package/dist/shims/default-global-error.js.map +1 -0
- package/dist/shims/document.d.ts +59 -3
- package/dist/shims/document.js +36 -5
- package/dist/shims/document.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +2 -2
- package/dist/shims/form.js +13 -6
- package/dist/shims/form.js.map +1 -1
- package/dist/shims/link.d.ts +21 -3
- package/dist/shims/link.js +131 -22
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.js +4 -4
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +8 -2
- package/dist/shims/navigation.js +36 -15
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/og.d.ts +18 -2
- package/dist/shims/og.js +49 -1
- package/dist/shims/og.js.map +1 -0
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/root-params.d.ts +3 -1
- package/dist/shims/root-params.js +11 -3
- package/dist/shims/root-params.js.map +1 -1
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts +12 -5
- package/dist/shims/router.js +172 -22
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/server.d.ts +21 -4
- package/dist/shims/server.js +29 -9
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.js +5 -1
- package/dist/shims/slot.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/url-safety.d.ts +23 -1
- package/dist/shims/url-safety.js +29 -2
- package/dist/shims/url-safety.js.map +1 -1
- package/dist/typegen.d.ts +10 -0
- package/dist/typegen.js +242 -0
- package/dist/typegen.js.map +1 -0
- package/dist/utils/asset-prefix.d.ts +33 -5
- package/dist/utils/asset-prefix.js +39 -6
- package/dist/utils/asset-prefix.js.map +1 -1
- package/dist/utils/cache-control-metadata.d.ts +2 -1
- package/dist/utils/cache-control-metadata.js +1 -3
- package/dist/utils/cache-control-metadata.js.map +1 -1
- package/dist/utils/domain-locale.d.ts +2 -1
- package/dist/utils/domain-locale.js +9 -1
- package/dist/utils/domain-locale.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +1 -1
- package/dist/utils/lazy-chunks.js +1 -1
- package/dist/utils/lazy-chunks.js.map +1 -1
- package/dist/utils/prerender-output-paths.d.ts +15 -0
- package/dist/utils/prerender-output-paths.js +24 -0
- package/dist/utils/prerender-output-paths.js.map +1 -0
- package/dist/utils/query.d.ts +17 -1
- package/dist/utils/query.js +36 -1
- package/dist/utils/query.js.map +1 -1
- package/dist/utils/record.d.ts +5 -0
- package/dist/utils/record.js +8 -0
- package/dist/utils/record.js.map +1 -0
- package/package.json +11 -3
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { evaluateArtifactCompatibility } from "./artifact-compatibility.js";
|
|
2
|
+
import { createClientReusePayloadHash } from "./client-reuse-manifest.js";
|
|
3
|
+
//#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
|
+
function createDisabledSkipDisposition() {
|
|
14
|
+
return {
|
|
15
|
+
code: "SKIP_MODEL_DISABLED",
|
|
16
|
+
enabled: false,
|
|
17
|
+
mode: "renderAndSend"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function rejectSkipCacheCrossCheck(entry, code, fields = {}) {
|
|
21
|
+
return {
|
|
22
|
+
kind: "rejected",
|
|
23
|
+
rejection: {
|
|
24
|
+
code,
|
|
25
|
+
entryId: entry.id,
|
|
26
|
+
fields
|
|
27
|
+
},
|
|
28
|
+
skipDisposition: createDisabledSkipDisposition()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function collectArtifactCompatibilityProofMismatches(artifactCompatibility, proofCompatibility) {
|
|
32
|
+
const mismatchedFields = [];
|
|
33
|
+
for (const field of ARTIFACT_COMPATIBILITY_PROOF_FIELDS) if (artifactCompatibility[field] !== proofCompatibility[field]) mismatchedFields.push(field);
|
|
34
|
+
return mismatchedFields;
|
|
35
|
+
}
|
|
36
|
+
function assertNever(value) {
|
|
37
|
+
throw new Error(`Unhandled skip/cache proof state: ${String(value)}`);
|
|
38
|
+
}
|
|
39
|
+
function crossCheckInvalidationProof(entry, invalidation) {
|
|
40
|
+
switch (invalidation.kind) {
|
|
41
|
+
case "valid": return null;
|
|
42
|
+
case "unknown": return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_INVALIDATION_UNKNOWN");
|
|
43
|
+
case "invalidated": return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_INVALIDATED", { invalidationEpoch: invalidation.invalidationEpoch });
|
|
44
|
+
default: return assertNever(invalidation);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function crossCheckClientReuseManifestEntryWithCache(input) {
|
|
48
|
+
const { cacheDecision, entry } = input;
|
|
49
|
+
if (cacheDecision === null) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PROOF_MISSING");
|
|
50
|
+
if (cacheDecision.kind === "fallback") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PROOF_REJECTED", {
|
|
51
|
+
cacheProofCode: cacheDecision.fallback.code,
|
|
52
|
+
cacheProofMode: cacheDecision.fallback.mode,
|
|
53
|
+
cacheProofScope: cacheDecision.fallback.scope
|
|
54
|
+
});
|
|
55
|
+
const { proof } = cacheDecision;
|
|
56
|
+
if (entry.kind !== "layout" || proof.reuseClass !== "static-layout") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_REUSE_CLASS_UNSUPPORTED", {
|
|
57
|
+
entryKind: entry.kind,
|
|
58
|
+
reuseClass: proof.reuseClass
|
|
59
|
+
});
|
|
60
|
+
if (entry.id !== proof.candidateOutput.layoutId) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ENTRY_ID_MISMATCH", {
|
|
61
|
+
cacheEntryId: proof.candidateOutput.layoutId,
|
|
62
|
+
manifestEntryId: entry.id
|
|
63
|
+
});
|
|
64
|
+
const artifactProofMismatches = collectArtifactCompatibilityProofMismatches(input.artifact.compatibility, proof.candidateArtifactCompatibility);
|
|
65
|
+
if (artifactProofMismatches.length > 0) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ARTIFACT_PROOF_MISMATCH", { mismatchedFields: artifactProofMismatches });
|
|
66
|
+
const artifactCompatibility = evaluateArtifactCompatibility(input.artifact.compatibility, entry.artifactCompatibility, { compatibilityMap: input.compatibilityMap });
|
|
67
|
+
if (artifactCompatibility.kind === "unknown") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ARTIFACT_COMPATIBILITY_UNKNOWN", {
|
|
68
|
+
compatibilityFallback: artifactCompatibility.fallback,
|
|
69
|
+
reason: artifactCompatibility.reason
|
|
70
|
+
});
|
|
71
|
+
if (artifactCompatibility.kind === "incompatible") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ARTIFACT_COMPATIBILITY_INCOMPATIBLE", {
|
|
72
|
+
compatibilityFallback: artifactCompatibility.fallback,
|
|
73
|
+
reason: artifactCompatibility.reason
|
|
74
|
+
});
|
|
75
|
+
if (entry.variantCacheKey !== proof.variant.cacheKey) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_VARIANT_MISMATCH", {
|
|
76
|
+
cacheVariantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),
|
|
77
|
+
entryVariantCacheKeyHash: createClientReusePayloadHash(entry.variantCacheKey)
|
|
78
|
+
});
|
|
79
|
+
if (input.artifact.payloadHash === null) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PAYLOAD_HASH_MISSING");
|
|
80
|
+
if (entry.payloadHash !== input.artifact.payloadHash) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PAYLOAD_HASH_MISMATCH", {
|
|
81
|
+
cachePayloadHash: input.artifact.payloadHash,
|
|
82
|
+
entryPayloadHash: entry.payloadHash
|
|
83
|
+
});
|
|
84
|
+
const invalidationRejection = crossCheckInvalidationProof(entry, input.artifact.invalidation);
|
|
85
|
+
if (invalidationRejection) return invalidationRejection;
|
|
86
|
+
return {
|
|
87
|
+
kind: "verified",
|
|
88
|
+
code: "SKIP_CACHE_CROSS_CHECK_PASSED",
|
|
89
|
+
entryId: entry.id,
|
|
90
|
+
fields: {
|
|
91
|
+
entryKind: entry.kind,
|
|
92
|
+
reuseClass: proof.reuseClass,
|
|
93
|
+
variantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey)
|
|
94
|
+
},
|
|
95
|
+
skipDisposition: createDisabledSkipDisposition()
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { crossCheckClientReuseManifestEntryWithCache };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=skip-cache-proof.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -21,7 +21,7 @@ type StaticFileEntry = {
|
|
|
21
21
|
*
|
|
22
22
|
* Usage:
|
|
23
23
|
* const cache = await StaticFileCache.create(clientDir);
|
|
24
|
-
* const entry = cache.lookup("/
|
|
24
|
+
* const entry = cache.lookup("/_next/static/app-abc123.js");
|
|
25
25
|
* // entry.br?.headers, entry.original.headers, etc.
|
|
26
26
|
*/
|
|
27
27
|
declare class StaticFileCache {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import "../utils/asset-prefix.js";
|
|
1
2
|
import path from "node:path";
|
|
2
|
-
import
|
|
3
|
+
import fs from "node:fs/promises";
|
|
3
4
|
//#region src/server/static-file-cache.ts
|
|
4
5
|
/**
|
|
5
6
|
* Startup metadata cache for static file serving.
|
|
@@ -48,7 +49,7 @@ const BUFFER_THRESHOLD = 64 * 1024;
|
|
|
48
49
|
*
|
|
49
50
|
* Usage:
|
|
50
51
|
* const cache = await StaticFileCache.create(clientDir);
|
|
51
|
-
* const entry = cache.lookup("/
|
|
52
|
+
* const entry = cache.lookup("/_next/static/app-abc123.js");
|
|
52
53
|
* // entry.br?.headers, entry.original.headers, etc.
|
|
53
54
|
*/
|
|
54
55
|
var StaticFileCache = class StaticFileCache {
|
|
@@ -74,7 +75,7 @@ var StaticFileCache = class StaticFileCache {
|
|
|
74
75
|
if (relativePath.startsWith(".vite/") || relativePath === ".vite") continue;
|
|
75
76
|
const ext = path.extname(relativePath);
|
|
76
77
|
const contentType = CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
77
|
-
const isHashed = relativePath.startsWith(
|
|
78
|
+
const isHashed = relativePath.startsWith(`_next/static/`) || relativePath.includes(`/_next/static/`);
|
|
78
79
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
|
|
79
80
|
const etag = isHashed && etagFromFilenameHash(relativePath, ext) || `W/"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1e3)}"`;
|
|
80
81
|
const baseHeaders = {
|
|
@@ -134,7 +135,7 @@ var StaticFileCache = class StaticFileCache {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
for (let i = 0; i < toBuffer.length; i += 64) await Promise.all(toBuffer.slice(i, i + 64).map(async (v) => {
|
|
137
|
-
v.buffer = await
|
|
138
|
+
v.buffer = await fs.readFile(v.path);
|
|
138
139
|
}));
|
|
139
140
|
return new StaticFileCache(entries);
|
|
140
141
|
}
|
|
@@ -190,7 +191,7 @@ const STAT_BATCH_SIZE = 64;
|
|
|
190
191
|
async function* walkFilesWithStats(dir, base = dir) {
|
|
191
192
|
let entries;
|
|
192
193
|
try {
|
|
193
|
-
entries = await
|
|
194
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
194
195
|
} catch {
|
|
195
196
|
return;
|
|
196
197
|
}
|
|
@@ -202,7 +203,7 @@ async function* walkFilesWithStats(dir, base = dir) {
|
|
|
202
203
|
}
|
|
203
204
|
for (let i = 0; i < files.length; i += STAT_BATCH_SIZE) {
|
|
204
205
|
const batch = files.slice(i, i + STAT_BATCH_SIZE);
|
|
205
|
-
const stats = await Promise.all(batch.map((f) =>
|
|
206
|
+
const stats = await Promise.all(batch.map((f) => fs.stat(f)));
|
|
206
207
|
for (let j = 0; j < batch.length; j++) yield {
|
|
207
208
|
relativePath: path.relative(base, batch[j]),
|
|
208
209
|
fullPath: batch[j],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-file-cache.js","names":[],"sources":["../../src/server/static-file-cache.ts"],"sourcesContent":["/**\n * Startup metadata cache for static file serving.\n *\n * Walks dist/client/ once at server boot, pre-computes response headers for\n * every file variant (original, brotli, gzip, zstd), and caches everything\n * in memory. The per-request hot path is just: Map.get() → string compare\n * (ETag) → writeHead(precomputed) → pipe.\n *\n * Modeled after sirv's production mode. Key insight from sirv: pre-compute\n * ALL response headers at startup — Content-Type, Content-Length, ETag,\n * Cache-Control, Content-Encoding, Vary — as reusable objects. The common\n * per-request path (no extraHeaders) does zero object allocation for headers.\n */\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Content-type lookup for static assets. Shared with prod-server.ts. */\nexport const CONTENT_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".html\": \"text/html\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".map\": \"application/json\",\n \".rsc\": \"text/x-component\",\n};\n\n/**\n * Files below this size are buffered in memory at startup for zero-syscall\n * serving via res.end(buffer). Above this, files stream via createReadStream.\n * 64KB covers virtually all precompressed assets (a 200KB JS bundle compresses\n * to ~50KB with brotli q5).\n */\nconst BUFFER_THRESHOLD = 64 * 1024;\n\n/** A servable file variant with pre-computed response headers. */\ntype FileVariant = {\n /** Absolute file path (used for streaming large files). */\n path: string;\n /** Uncompressed or encoded byte size for buffer-threshold decisions. */\n size: number;\n /** Pre-computed response headers. */\n headers: Record<string, string>;\n /** In-memory buffer for small files (below BUFFER_THRESHOLD). */\n buffer?: Buffer;\n};\n\ntype StaticFileEntry = {\n /** Weak ETag for conditional request matching. */\n etag: string;\n /** Pre-computed headers for 304 Not Modified response. */\n notModifiedHeaders: Record<string, string>;\n /** Original file variant (uncompressed). */\n original: FileVariant;\n /** Brotli precompressed variant, if .br file exists. */\n br?: FileVariant;\n /** Gzip precompressed variant, if .gz file exists. */\n gz?: FileVariant;\n /** Zstandard precompressed variant, if .zst file exists. */\n zst?: FileVariant;\n};\n\n/**\n * In-memory cache of static file metadata, populated once at server startup.\n *\n * Usage:\n * const cache = await StaticFileCache.create(clientDir);\n * const entry = cache.lookup(\"/assets/app-abc123.js\");\n * // entry.br?.headers, entry.original.headers, etc.\n */\nexport class StaticFileCache {\n private readonly entries: Map<string, StaticFileEntry>;\n\n private constructor(entries: Map<string, StaticFileEntry>) {\n this.entries = entries;\n }\n\n /**\n * Scan the client directory and build the cache.\n *\n * Gracefully handles non-existent directories (returns an empty cache).\n */\n static async create(clientDir: string): Promise<StaticFileCache> {\n const entries = new Map<string, StaticFileEntry>();\n\n // First pass: collect all regular files with their metadata\n const allFiles = new Map<string, { fullPath: string; size: number; mtimeMs: number }>();\n\n for await (const { relativePath, fullPath, stat } of walkFilesWithStats(clientDir)) {\n allFiles.set(relativePath, { fullPath, size: stat.size, mtimeMs: stat.mtimeMs });\n }\n\n // Second pass: build cache entries with pre-computed headers per variant\n for (const [relativePath, fileInfo] of allFiles) {\n // Skip precompressed variants — they're linked to their originals\n if (\n relativePath.endsWith(\".br\") ||\n relativePath.endsWith(\".gz\") ||\n relativePath.endsWith(\".zst\")\n )\n continue;\n\n // Skip .vite/ internal directory\n if (relativePath.startsWith(\".vite/\") || relativePath === \".vite\") continue;\n\n const ext = path.extname(relativePath);\n const contentType = CONTENT_TYPES[ext] ?? \"application/octet-stream\";\n // Files under Vite's `assetsDir` are content-hashed. The historical\n // default is `assets/`; when `assetPrefix` is configured the layout\n // becomes `<prefix>/_next/static/...` (path-prefix) or `_next/static/...`\n // (absolute-URL prefix). All three forms get long-lived `immutable`\n // cache headers — the hash in the filename invalidates safely.\n //\n // `relativePath` is the path relative to `clientDir`, with no leading\n // slash. Because of that, `startsWith(\"_next/static/\")` and\n // `includes(\"/_next/static/\")` are NOT equivalent — the former covers\n // the absolute-URL prefix layout (no parent directory), the latter\n // covers the path-prefix layout (under an arbitrary parent like `cdn/`).\n const isHashed =\n relativePath.startsWith(\"assets/\") ||\n relativePath.startsWith(\"_next/static/\") ||\n relativePath.includes(\"/_next/static/\");\n const cacheControl = isHashed\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\";\n const etag =\n (isHashed && etagFromFilenameHash(relativePath, ext)) ||\n `W/\"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1000)}\"`;\n\n // Base headers shared by all variants (Content-Type, Cache-Control, ETag)\n const baseHeaders = {\n \"Content-Type\": contentType,\n \"Cache-Control\": cacheControl,\n ETag: etag,\n };\n\n // Pre-compute original variant headers\n const original: FileVariant = {\n path: fileInfo.fullPath,\n size: fileInfo.size,\n headers: { ...baseHeaders, \"Content-Length\": String(fileInfo.size) },\n };\n\n const entry: StaticFileEntry = {\n etag,\n notModifiedHeaders: { ETag: etag, \"Cache-Control\": cacheControl },\n original,\n };\n\n // Pre-compute compressed variant headers (with Content-Encoding, Vary, correct Content-Length)\n const brInfo = allFiles.get(relativePath + \".br\");\n if (brInfo) {\n entry.br = buildVariant(brInfo, baseHeaders, \"br\");\n }\n\n const gzInfo = allFiles.get(relativePath + \".gz\");\n if (gzInfo) {\n entry.gz = buildVariant(gzInfo, baseHeaders, \"gzip\");\n }\n\n const zstInfo = allFiles.get(relativePath + \".zst\");\n if (zstInfo) {\n entry.zst = buildVariant(zstInfo, baseHeaders, \"zstd\");\n }\n\n // When compressed variants exist, the original needs Vary too so\n // shared caches don't serve uncompressed to compression-capable clients.\n if (entry.br || entry.gz || entry.zst) {\n original.headers[\"Vary\"] = \"Accept-Encoding\";\n entry.notModifiedHeaders[\"Vary\"] = \"Accept-Encoding\";\n }\n\n // Register under the URL pathname (leading /)\n // NOTE: aliases below share the same entry by reference, so all header\n // mutations (e.g. Vary above) must happen before registration.\n const pathname = \"/\" + relativePath;\n entries.set(pathname, entry);\n\n // Register HTML fallback aliases (same entry object — no duplication)\n if (ext === \".html\") {\n if (relativePath.endsWith(\"/index.html\")) {\n const dirPath = \"/\" + relativePath.slice(0, -\"/index.html\".length);\n if (dirPath !== \"/\") {\n entries.set(dirPath, entry);\n }\n } else {\n const withoutExt = \"/\" + relativePath.slice(0, -ext.length);\n entries.set(withoutExt, entry);\n }\n }\n }\n\n // Third pass: buffer small files in memory for zero-syscall serving.\n // For small compressed variants (e.g. a 50KB JS bundle → ~15KB brotli),\n // res.end(buffer) is ~2x faster than createReadStream().pipe() because\n // it skips fd open/close and stream plumbing overhead.\n // Reads are chunked at 64 concurrent to avoid fd exhaustion on large projects.\n // Deduplicate at the entry level first: HTML aliases share the same\n // StaticFileEntry by reference, so entries.values() yields duplicates for\n // paths like /about and /about.html. Deduping entries avoids iterating\n // their variants multiple times on sites with many HTML pages.\n const toBuffer: FileVariant[] = [];\n const seenEntries = new Set<StaticFileEntry>();\n for (const entry of entries.values()) {\n if (seenEntries.has(entry)) continue;\n seenEntries.add(entry);\n for (const variant of [entry.original, entry.br, entry.gz, entry.zst]) {\n if (!variant || variant.size > BUFFER_THRESHOLD) continue;\n toBuffer.push(variant);\n }\n }\n for (let i = 0; i < toBuffer.length; i += 64) {\n await Promise.all(\n toBuffer.slice(i, i + 64).map(async (v) => {\n v.buffer = await fsp.readFile(v.path);\n }),\n );\n }\n\n return new StaticFileCache(entries);\n }\n\n /**\n * Look up cached metadata for a URL pathname.\n *\n * Returns undefined if the file is not in the cache. The root path \"/\"\n * always returns undefined — index.html is served by SSR/RSC.\n */\n lookup(pathname: string): StaticFileEntry | undefined {\n if (pathname === \"/\") return undefined;\n\n // Block .vite/ access (including encoded variants that were decoded before lookup)\n if (pathname.startsWith(\"/.vite/\") || pathname === \"/.vite\") return undefined;\n\n return this.entries.get(pathname);\n }\n}\n\n/**\n * Extract a stable weak ETag from a Vite hashed filename (e.g. `app-DqZc3R4n.js`).\n * The hash is a content hash computed by the bundler — deterministic across\n * identical builds regardless of filesystem timestamps.\n *\n * Must be a weak validator (W/) because the same tag is shared across\n * content-encoded variants (original, .br, .gz, .zst) which are byte-different.\n * Returns null if the filename doesn't contain a recognizable hash suffix,\n * so the caller can fall back to mtime-based ETags.\n */\nexport function etagFromFilenameHash(relativePath: string, ext: string): string | null {\n const basename = path.basename(relativePath, ext);\n const lastDash = basename.lastIndexOf(\"-\");\n if (lastDash === -1 || lastDash === basename.length - 1) return null;\n const suffix = basename.slice(lastDash + 1);\n // Vite emits 8-char base64url hashes; allow 6-12 for other bundlers.\n // If Rolldown changes its hash length, update this range.\n return suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)\n ? `W/\"${suffix}\"`\n : null;\n}\n\nfunction buildVariant(\n info: { fullPath: string; size: number },\n baseHeaders: Record<string, string>,\n encoding: string,\n): FileVariant {\n return {\n path: info.fullPath,\n size: info.size,\n headers: {\n ...baseHeaders,\n \"Content-Encoding\": encoding,\n \"Content-Length\": String(info.size),\n Vary: \"Accept-Encoding\",\n },\n };\n}\n\n/** Batch size for concurrent stat() calls during directory walk. */\nconst STAT_BATCH_SIZE = 64;\n\n/**\n * Walk a directory recursively, yielding file paths and stats.\n *\n * Batches stat() calls per directory to avoid sequential syscall overhead\n * for large dist/client/ directories.\n */\nasync function* walkFilesWithStats(\n dir: string,\n base: string = dir,\n): AsyncGenerator<{\n relativePath: string;\n fullPath: string;\n stat: { size: number; mtimeMs: number };\n}> {\n let entries;\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // directory doesn't exist or unreadable\n }\n\n // Recurse into subdirectories first (they yield their own batched stats)\n const files: string[] = [];\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkFilesWithStats(fullPath, base);\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n\n // Batch stat() calls for files in this directory\n for (let i = 0; i < files.length; i += STAT_BATCH_SIZE) {\n const batch = files.slice(i, i + STAT_BATCH_SIZE);\n const stats = await Promise.all(batch.map((f) => fsp.stat(f)));\n for (let j = 0; j < batch.length; j++) {\n yield {\n relativePath: path.relative(base, batch[j]),\n fullPath: batch[j],\n stat: { size: stats[j].size, mtimeMs: stats[j].mtimeMs },\n };\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"static-file-cache.js","names":["fsp"],"sources":["../../src/server/static-file-cache.ts"],"sourcesContent":["/**\n * Startup metadata cache for static file serving.\n *\n * Walks dist/client/ once at server boot, pre-computes response headers for\n * every file variant (original, brotli, gzip, zstd), and caches everything\n * in memory. The per-request hot path is just: Map.get() → string compare\n * (ETag) → writeHead(precomputed) → pipe.\n *\n * Modeled after sirv's production mode. Key insight from sirv: pre-compute\n * ALL response headers at startup — Content-Type, Content-Length, ETag,\n * Cache-Control, Content-Encoding, Vary — as reusable objects. The common\n * per-request path (no extraHeaders) does zero object allocation for headers.\n */\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ASSET_PREFIX_URL_DIR } from \"../utils/asset-prefix.js\";\n\n/** Content-type lookup for static assets. Shared with prod-server.ts. */\nexport const CONTENT_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".html\": \"text/html\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".map\": \"application/json\",\n \".rsc\": \"text/x-component\",\n};\n\n/**\n * Files below this size are buffered in memory at startup for zero-syscall\n * serving via res.end(buffer). Above this, files stream via createReadStream.\n * 64KB covers virtually all precompressed assets (a 200KB JS bundle compresses\n * to ~50KB with brotli q5).\n */\nconst BUFFER_THRESHOLD = 64 * 1024;\n\n/** A servable file variant with pre-computed response headers. */\ntype FileVariant = {\n /** Absolute file path (used for streaming large files). */\n path: string;\n /** Uncompressed or encoded byte size for buffer-threshold decisions. */\n size: number;\n /** Pre-computed response headers. */\n headers: Record<string, string>;\n /** In-memory buffer for small files (below BUFFER_THRESHOLD). */\n buffer?: Buffer;\n};\n\ntype StaticFileEntry = {\n /** Weak ETag for conditional request matching. */\n etag: string;\n /** Pre-computed headers for 304 Not Modified response. */\n notModifiedHeaders: Record<string, string>;\n /** Original file variant (uncompressed). */\n original: FileVariant;\n /** Brotli precompressed variant, if .br file exists. */\n br?: FileVariant;\n /** Gzip precompressed variant, if .gz file exists. */\n gz?: FileVariant;\n /** Zstandard precompressed variant, if .zst file exists. */\n zst?: FileVariant;\n};\n\n/**\n * In-memory cache of static file metadata, populated once at server startup.\n *\n * Usage:\n * const cache = await StaticFileCache.create(clientDir);\n * const entry = cache.lookup(\"/_next/static/app-abc123.js\");\n * // entry.br?.headers, entry.original.headers, etc.\n */\nexport class StaticFileCache {\n private readonly entries: Map<string, StaticFileEntry>;\n\n private constructor(entries: Map<string, StaticFileEntry>) {\n this.entries = entries;\n }\n\n /**\n * Scan the client directory and build the cache.\n *\n * Gracefully handles non-existent directories (returns an empty cache).\n */\n static async create(clientDir: string): Promise<StaticFileCache> {\n const entries = new Map<string, StaticFileEntry>();\n\n // First pass: collect all regular files with their metadata\n const allFiles = new Map<string, { fullPath: string; size: number; mtimeMs: number }>();\n\n for await (const { relativePath, fullPath, stat } of walkFilesWithStats(clientDir)) {\n allFiles.set(relativePath, { fullPath, size: stat.size, mtimeMs: stat.mtimeMs });\n }\n\n // Second pass: build cache entries with pre-computed headers per variant\n for (const [relativePath, fileInfo] of allFiles) {\n // Skip precompressed variants — they're linked to their originals\n if (\n relativePath.endsWith(\".br\") ||\n relativePath.endsWith(\".gz\") ||\n relativePath.endsWith(\".zst\")\n )\n continue;\n\n // Skip .vite/ internal directory\n if (relativePath.startsWith(\".vite/\") || relativePath === \".vite\") continue;\n\n const ext = path.extname(relativePath);\n const contentType = CONTENT_TYPES[ext] ?? \"application/octet-stream\";\n // Files under Vite's `assetsDir` are content-hashed. The default\n // layout writes to `<ASSET_PREFIX_URL_DIR>/` (Next.js's canonical\n // convention); when `assetPrefix` is a path prefix the layout\n // becomes `<prefix>/<ASSET_PREFIX_URL_DIR>/...`. Both forms get\n // long-lived `immutable` cache headers — the hash in the filename\n // invalidates safely.\n //\n // `relativePath` is the path relative to `clientDir`, with no\n // leading slash. Because of that, `startsWith(\"<dir>/\")` and\n // `includes(\"/<dir>/\")` are NOT equivalent — the former covers the\n // default and absolute-URL prefix layouts (no parent directory),\n // the latter covers the path-prefix layout (under an arbitrary\n // parent like `cdn/`).\n const isHashed =\n relativePath.startsWith(`${ASSET_PREFIX_URL_DIR}/`) ||\n relativePath.includes(`/${ASSET_PREFIX_URL_DIR}/`);\n const cacheControl = isHashed\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\";\n const etag =\n (isHashed && etagFromFilenameHash(relativePath, ext)) ||\n `W/\"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1000)}\"`;\n\n // Base headers shared by all variants (Content-Type, Cache-Control, ETag)\n const baseHeaders = {\n \"Content-Type\": contentType,\n \"Cache-Control\": cacheControl,\n ETag: etag,\n };\n\n // Pre-compute original variant headers\n const original: FileVariant = {\n path: fileInfo.fullPath,\n size: fileInfo.size,\n headers: { ...baseHeaders, \"Content-Length\": String(fileInfo.size) },\n };\n\n const entry: StaticFileEntry = {\n etag,\n notModifiedHeaders: { ETag: etag, \"Cache-Control\": cacheControl },\n original,\n };\n\n // Pre-compute compressed variant headers (with Content-Encoding, Vary, correct Content-Length)\n const brInfo = allFiles.get(relativePath + \".br\");\n if (brInfo) {\n entry.br = buildVariant(brInfo, baseHeaders, \"br\");\n }\n\n const gzInfo = allFiles.get(relativePath + \".gz\");\n if (gzInfo) {\n entry.gz = buildVariant(gzInfo, baseHeaders, \"gzip\");\n }\n\n const zstInfo = allFiles.get(relativePath + \".zst\");\n if (zstInfo) {\n entry.zst = buildVariant(zstInfo, baseHeaders, \"zstd\");\n }\n\n // When compressed variants exist, the original needs Vary too so\n // shared caches don't serve uncompressed to compression-capable clients.\n if (entry.br || entry.gz || entry.zst) {\n original.headers[\"Vary\"] = \"Accept-Encoding\";\n entry.notModifiedHeaders[\"Vary\"] = \"Accept-Encoding\";\n }\n\n // Register under the URL pathname (leading /)\n // NOTE: aliases below share the same entry by reference, so all header\n // mutations (e.g. Vary above) must happen before registration.\n const pathname = \"/\" + relativePath;\n entries.set(pathname, entry);\n\n // Register HTML fallback aliases (same entry object — no duplication)\n if (ext === \".html\") {\n if (relativePath.endsWith(\"/index.html\")) {\n const dirPath = \"/\" + relativePath.slice(0, -\"/index.html\".length);\n if (dirPath !== \"/\") {\n entries.set(dirPath, entry);\n }\n } else {\n const withoutExt = \"/\" + relativePath.slice(0, -ext.length);\n entries.set(withoutExt, entry);\n }\n }\n }\n\n // Third pass: buffer small files in memory for zero-syscall serving.\n // For small compressed variants (e.g. a 50KB JS bundle → ~15KB brotli),\n // res.end(buffer) is ~2x faster than createReadStream().pipe() because\n // it skips fd open/close and stream plumbing overhead.\n // Reads are chunked at 64 concurrent to avoid fd exhaustion on large projects.\n // Deduplicate at the entry level first: HTML aliases share the same\n // StaticFileEntry by reference, so entries.values() yields duplicates for\n // paths like /about and /about.html. Deduping entries avoids iterating\n // their variants multiple times on sites with many HTML pages.\n const toBuffer: FileVariant[] = [];\n const seenEntries = new Set<StaticFileEntry>();\n for (const entry of entries.values()) {\n if (seenEntries.has(entry)) continue;\n seenEntries.add(entry);\n for (const variant of [entry.original, entry.br, entry.gz, entry.zst]) {\n if (!variant || variant.size > BUFFER_THRESHOLD) continue;\n toBuffer.push(variant);\n }\n }\n for (let i = 0; i < toBuffer.length; i += 64) {\n await Promise.all(\n toBuffer.slice(i, i + 64).map(async (v) => {\n v.buffer = await fsp.readFile(v.path);\n }),\n );\n }\n\n return new StaticFileCache(entries);\n }\n\n /**\n * Look up cached metadata for a URL pathname.\n *\n * Returns undefined if the file is not in the cache. The root path \"/\"\n * always returns undefined — index.html is served by SSR/RSC.\n */\n lookup(pathname: string): StaticFileEntry | undefined {\n if (pathname === \"/\") return undefined;\n\n // Block .vite/ access (including encoded variants that were decoded before lookup)\n if (pathname.startsWith(\"/.vite/\") || pathname === \"/.vite\") return undefined;\n\n return this.entries.get(pathname);\n }\n}\n\n/**\n * Extract a stable weak ETag from a Vite hashed filename (e.g. `app-DqZc3R4n.js`).\n * The hash is a content hash computed by the bundler — deterministic across\n * identical builds regardless of filesystem timestamps.\n *\n * Must be a weak validator (W/) because the same tag is shared across\n * content-encoded variants (original, .br, .gz, .zst) which are byte-different.\n * Returns null if the filename doesn't contain a recognizable hash suffix,\n * so the caller can fall back to mtime-based ETags.\n */\nexport function etagFromFilenameHash(relativePath: string, ext: string): string | null {\n const basename = path.basename(relativePath, ext);\n const lastDash = basename.lastIndexOf(\"-\");\n if (lastDash === -1 || lastDash === basename.length - 1) return null;\n const suffix = basename.slice(lastDash + 1);\n // Vite emits 8-char base64url hashes; allow 6-12 for other bundlers.\n // If Rolldown changes its hash length, update this range.\n return suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)\n ? `W/\"${suffix}\"`\n : null;\n}\n\nfunction buildVariant(\n info: { fullPath: string; size: number },\n baseHeaders: Record<string, string>,\n encoding: string,\n): FileVariant {\n return {\n path: info.fullPath,\n size: info.size,\n headers: {\n ...baseHeaders,\n \"Content-Encoding\": encoding,\n \"Content-Length\": String(info.size),\n Vary: \"Accept-Encoding\",\n },\n };\n}\n\n/** Batch size for concurrent stat() calls during directory walk. */\nconst STAT_BATCH_SIZE = 64;\n\n/**\n * Walk a directory recursively, yielding file paths and stats.\n *\n * Batches stat() calls per directory to avoid sequential syscall overhead\n * for large dist/client/ directories.\n */\nasync function* walkFilesWithStats(\n dir: string,\n base: string = dir,\n): AsyncGenerator<{\n relativePath: string;\n fullPath: string;\n stat: { size: number; mtimeMs: number };\n}> {\n let entries;\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // directory doesn't exist or unreadable\n }\n\n // Recurse into subdirectories first (they yield their own batched stats)\n const files: string[] = [];\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkFilesWithStats(fullPath, base);\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n\n // Batch stat() calls for files in this directory\n for (let i = 0; i < files.length; i += STAT_BATCH_SIZE) {\n const batch = files.slice(i, i + STAT_BATCH_SIZE);\n const stats = await Promise.all(batch.map((f) => fsp.stat(f)));\n for (let j = 0; j < batch.length; j++) {\n yield {\n relativePath: path.relative(base, batch[j]),\n fullPath: batch[j],\n stat: { size: stats[j].size, mtimeMs: stats[j].mtimeMs },\n };\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DomainLocale } from "../utils/domain-locale.js";
|
|
2
|
+
|
|
3
|
+
//#region src/shims/client-locale.d.ts
|
|
4
|
+
declare function getCurrentBrowserLocale({
|
|
5
|
+
basePath,
|
|
6
|
+
domainLocales,
|
|
7
|
+
hostname
|
|
8
|
+
}: {
|
|
9
|
+
basePath: string;
|
|
10
|
+
domainLocales: readonly DomainLocale[] | undefined;
|
|
11
|
+
hostname: string | null | undefined;
|
|
12
|
+
}): string | undefined;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { getCurrentBrowserLocale };
|
|
15
|
+
//# sourceMappingURL=client-locale.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { stripBasePath } from "../utils/base-path.js";
|
|
2
|
+
import { detectDomainLocale, getLocalePathPrefix } from "../utils/domain-locale.js";
|
|
3
|
+
//#region src/shims/client-locale.ts
|
|
4
|
+
function getCurrentBrowserLocale({ basePath, domainLocales, hostname }) {
|
|
5
|
+
if (typeof window === "undefined") return void 0;
|
|
6
|
+
const pathnameLocale = getLocalePathPrefix(stripBasePath(window.location.pathname, basePath), window.__VINEXT_LOCALES__);
|
|
7
|
+
if (pathnameLocale) return pathnameLocale;
|
|
8
|
+
return detectDomainLocale(domainLocales, hostname ?? void 0)?.defaultLocale ?? window.__VINEXT_DEFAULT_LOCALE__ ?? window.__VINEXT_LOCALE__;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getCurrentBrowserLocale };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=client-locale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-locale.js","names":[],"sources":["../../src/shims/client-locale.ts"],"sourcesContent":["import { stripBasePath } from \"../utils/base-path.js\";\nimport {\n detectDomainLocale,\n getLocalePathPrefix,\n type DomainLocale,\n} from \"../utils/domain-locale.js\";\n\nexport function getCurrentBrowserLocale({\n basePath,\n domainLocales,\n hostname,\n}: {\n basePath: string;\n domainLocales: readonly DomainLocale[] | undefined;\n hostname: string | null | undefined;\n}): string | undefined {\n if (typeof window === \"undefined\") return undefined;\n\n const pathnameLocale = getLocalePathPrefix(\n stripBasePath(window.location.pathname, basePath),\n window.__VINEXT_LOCALES__,\n );\n if (pathnameLocale) return pathnameLocale;\n\n return (\n detectDomainLocale(domainLocales, hostname ?? undefined)?.defaultLocale ??\n window.__VINEXT_DEFAULT_LOCALE__ ??\n window.__VINEXT_LOCALE__\n );\n}\n"],"mappings":";;;AAOA,SAAgB,wBAAwB,EACtC,UACA,eACA,YAKqB;CACrB,IAAI,OAAO,WAAW,aAAa,OAAO,KAAA;CAE1C,MAAM,iBAAiB,oBACrB,cAAc,OAAO,SAAS,UAAU,SAAS,EACjD,OAAO,mBACR;CACD,IAAI,gBAAgB,OAAO;CAE3B,OACE,mBAAmB,eAAe,YAAY,KAAA,EAAU,EAAE,iBAC1D,OAAO,6BACP,OAAO"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/shims/default-global-error.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Ported from Next.js's built-in default global error component:
|
|
6
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/builtin/global-error.tsx
|
|
7
|
+
*
|
|
8
|
+
* Rendered when an unhandled error reaches the root error boundary and the
|
|
9
|
+
* user has not supplied their own `app/global-error.tsx`. Matches the
|
|
10
|
+
* markup, inline styles, and theme CSS that Next.js's
|
|
11
|
+
* `test/e2e/app-dir/default-error-page-ui/default-error-page-ui.test.ts`
|
|
12
|
+
* exercises:
|
|
13
|
+
* - `<h1>` reads "This page couldn't load"
|
|
14
|
+
* - `<p>` contains "Reload to try again, or go back" (client error) or
|
|
15
|
+
* "A server error occurred. Reload to try again." (server error)
|
|
16
|
+
* - First `<button>` is "Reload" (form submit triggers a page reload)
|
|
17
|
+
* - Second `<button>` is "Back" (only for client errors)
|
|
18
|
+
* - Server errors render an "ERROR <digest>" footer
|
|
19
|
+
* - SVG warning icon is 32x32
|
|
20
|
+
*/
|
|
21
|
+
type DefaultGlobalErrorProps = {
|
|
22
|
+
error: {
|
|
23
|
+
digest?: string;
|
|
24
|
+
} | null | undefined;
|
|
25
|
+
reset?: () => void;
|
|
26
|
+
};
|
|
27
|
+
declare function DefaultGlobalError({
|
|
28
|
+
error
|
|
29
|
+
}: DefaultGlobalErrorProps): _$react_jsx_runtime0.JSX.Element;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DefaultGlobalError as default };
|
|
32
|
+
//# sourceMappingURL=default-global-error.d.ts.map
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/shims/default-global-error.tsx
|
|
5
|
+
const errorStyles = {
|
|
6
|
+
container: {
|
|
7
|
+
fontFamily: "system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"",
|
|
8
|
+
height: "100vh",
|
|
9
|
+
display: "flex",
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
justifyContent: "center"
|
|
12
|
+
},
|
|
13
|
+
card: {
|
|
14
|
+
marginTop: "-32px",
|
|
15
|
+
maxWidth: "325px",
|
|
16
|
+
padding: "32px 28px",
|
|
17
|
+
textAlign: "left"
|
|
18
|
+
},
|
|
19
|
+
icon: { marginBottom: "24px" },
|
|
20
|
+
title: {
|
|
21
|
+
fontSize: "24px",
|
|
22
|
+
fontWeight: 500,
|
|
23
|
+
letterSpacing: "-0.02em",
|
|
24
|
+
lineHeight: "32px",
|
|
25
|
+
margin: "0 0 12px 0",
|
|
26
|
+
color: "var(--next-error-title)"
|
|
27
|
+
},
|
|
28
|
+
message: {
|
|
29
|
+
fontSize: "14px",
|
|
30
|
+
fontWeight: 400,
|
|
31
|
+
lineHeight: "21px",
|
|
32
|
+
margin: "0 0 20px 0",
|
|
33
|
+
color: "var(--next-error-message)"
|
|
34
|
+
},
|
|
35
|
+
form: { margin: 0 },
|
|
36
|
+
buttonGroup: {
|
|
37
|
+
display: "flex",
|
|
38
|
+
gap: "8px",
|
|
39
|
+
alignItems: "center"
|
|
40
|
+
},
|
|
41
|
+
button: {
|
|
42
|
+
display: "inline-flex",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
height: "32px",
|
|
46
|
+
padding: "0 12px",
|
|
47
|
+
fontSize: "14px",
|
|
48
|
+
fontWeight: 500,
|
|
49
|
+
lineHeight: "20px",
|
|
50
|
+
borderRadius: "6px",
|
|
51
|
+
cursor: "pointer",
|
|
52
|
+
color: "var(--next-error-btn-text)",
|
|
53
|
+
background: "var(--next-error-btn-bg)",
|
|
54
|
+
border: "var(--next-error-btn-border)"
|
|
55
|
+
},
|
|
56
|
+
buttonSecondary: {
|
|
57
|
+
display: "inline-flex",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
justifyContent: "center",
|
|
60
|
+
height: "32px",
|
|
61
|
+
padding: "0 12px",
|
|
62
|
+
fontSize: "14px",
|
|
63
|
+
fontWeight: 500,
|
|
64
|
+
lineHeight: "20px",
|
|
65
|
+
borderRadius: "6px",
|
|
66
|
+
cursor: "pointer",
|
|
67
|
+
color: "var(--next-error-btn-secondary-text)",
|
|
68
|
+
background: "var(--next-error-btn-secondary-bg)",
|
|
69
|
+
border: "var(--next-error-btn-secondary-border)"
|
|
70
|
+
},
|
|
71
|
+
digestFooter: {
|
|
72
|
+
position: "fixed",
|
|
73
|
+
bottom: "32px",
|
|
74
|
+
left: "0",
|
|
75
|
+
right: "0",
|
|
76
|
+
textAlign: "center",
|
|
77
|
+
fontFamily: "ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,monospace",
|
|
78
|
+
fontSize: "12px",
|
|
79
|
+
lineHeight: "18px",
|
|
80
|
+
fontWeight: 400,
|
|
81
|
+
margin: "0",
|
|
82
|
+
color: "var(--next-error-digest)"
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const errorThemeCss = `
|
|
86
|
+
:root {
|
|
87
|
+
--next-error-bg: #fff;
|
|
88
|
+
--next-error-text: #171717;
|
|
89
|
+
--next-error-title: #171717;
|
|
90
|
+
--next-error-message: #171717;
|
|
91
|
+
--next-error-digest: #666666;
|
|
92
|
+
--next-error-btn-text: #fff;
|
|
93
|
+
--next-error-btn-bg: #171717;
|
|
94
|
+
--next-error-btn-border: none;
|
|
95
|
+
--next-error-btn-secondary-text: #171717;
|
|
96
|
+
--next-error-btn-secondary-bg: transparent;
|
|
97
|
+
--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);
|
|
98
|
+
}
|
|
99
|
+
@media (prefers-color-scheme: dark) {
|
|
100
|
+
:root {
|
|
101
|
+
--next-error-bg: #0a0a0a;
|
|
102
|
+
--next-error-text: #ededed;
|
|
103
|
+
--next-error-title: #ededed;
|
|
104
|
+
--next-error-message: #ededed;
|
|
105
|
+
--next-error-digest: #a0a0a0;
|
|
106
|
+
--next-error-btn-text: #0a0a0a;
|
|
107
|
+
--next-error-btn-bg: #ededed;
|
|
108
|
+
--next-error-btn-border: none;
|
|
109
|
+
--next-error-btn-secondary-text: #ededed;
|
|
110
|
+
--next-error-btn-secondary-bg: transparent;
|
|
111
|
+
--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }
|
|
115
|
+
`.replace(/\n\s*/g, "");
|
|
116
|
+
function WarningIcon() {
|
|
117
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
118
|
+
width: "32",
|
|
119
|
+
height: "32",
|
|
120
|
+
viewBox: "-0.2 -1.5 32 32",
|
|
121
|
+
fill: "none",
|
|
122
|
+
style: errorStyles.icon,
|
|
123
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
124
|
+
d: "M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z",
|
|
125
|
+
fill: "var(--next-error-title)"
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function handleBackClick() {
|
|
130
|
+
if (typeof window === "undefined") return;
|
|
131
|
+
if (window.history.length > 1) window.history.back();
|
|
132
|
+
else window.location.href = "/";
|
|
133
|
+
}
|
|
134
|
+
function DefaultGlobalError({ error }) {
|
|
135
|
+
const digest = error?.digest;
|
|
136
|
+
const isServerError = !!digest;
|
|
137
|
+
const message = isServerError ? "A server error occurred. Reload to try again." : "Reload to try again, or go back.";
|
|
138
|
+
return /* @__PURE__ */ jsxs("html", {
|
|
139
|
+
id: "__next_error__",
|
|
140
|
+
children: [/* @__PURE__ */ jsx("head", { children: /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: errorThemeCss } }) }), /* @__PURE__ */ jsxs("body", { children: [/* @__PURE__ */ jsx("div", {
|
|
141
|
+
style: errorStyles.container,
|
|
142
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
143
|
+
style: errorStyles.card,
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ jsx(WarningIcon, {}),
|
|
146
|
+
/* @__PURE__ */ jsx("h1", {
|
|
147
|
+
style: errorStyles.title,
|
|
148
|
+
children: "This page couldn’t load"
|
|
149
|
+
}),
|
|
150
|
+
/* @__PURE__ */ jsx("p", {
|
|
151
|
+
style: errorStyles.message,
|
|
152
|
+
children: message
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ jsxs("div", {
|
|
155
|
+
style: errorStyles.buttonGroup,
|
|
156
|
+
children: [/* @__PURE__ */ jsx("form", {
|
|
157
|
+
style: errorStyles.form,
|
|
158
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
159
|
+
type: "submit",
|
|
160
|
+
style: errorStyles.button,
|
|
161
|
+
children: "Reload"
|
|
162
|
+
})
|
|
163
|
+
}), !isServerError && /* @__PURE__ */ jsx("button", {
|
|
164
|
+
type: "button",
|
|
165
|
+
style: errorStyles.buttonSecondary,
|
|
166
|
+
onClick: handleBackClick,
|
|
167
|
+
children: "Back"
|
|
168
|
+
})]
|
|
169
|
+
})
|
|
170
|
+
]
|
|
171
|
+
})
|
|
172
|
+
}), digest && /* @__PURE__ */ jsxs("p", {
|
|
173
|
+
style: errorStyles.digestFooter,
|
|
174
|
+
children: ["ERROR ", digest]
|
|
175
|
+
})] })]
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { DefaultGlobalError as default };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=default-global-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-global-error.js","names":[],"sources":["../../src/shims/default-global-error.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\n\n/**\n * Ported from Next.js's built-in default global error component:\n * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/builtin/global-error.tsx\n *\n * Rendered when an unhandled error reaches the root error boundary and the\n * user has not supplied their own `app/global-error.tsx`. Matches the\n * markup, inline styles, and theme CSS that Next.js's\n * `test/e2e/app-dir/default-error-page-ui/default-error-page-ui.test.ts`\n * exercises:\n * - `<h1>` reads \"This page couldn't load\"\n * - `<p>` contains \"Reload to try again, or go back\" (client error) or\n * \"A server error occurred. Reload to try again.\" (server error)\n * - First `<button>` is \"Reload\" (form submit triggers a page reload)\n * - Second `<button>` is \"Back\" (only for client errors)\n * - Server errors render an \"ERROR <digest>\" footer\n * - SVG warning icon is 32x32\n */\n\ntype DefaultGlobalErrorProps = {\n error: { digest?: string } | null | undefined;\n reset?: () => void;\n};\n\nconst errorStyles = {\n container: {\n fontFamily:\n 'system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"',\n height: \"100vh\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n card: {\n marginTop: \"-32px\",\n maxWidth: \"325px\",\n padding: \"32px 28px\",\n textAlign: \"left\" as const,\n },\n icon: {\n marginBottom: \"24px\",\n },\n title: {\n fontSize: \"24px\",\n fontWeight: 500,\n letterSpacing: \"-0.02em\",\n lineHeight: \"32px\",\n margin: \"0 0 12px 0\",\n color: \"var(--next-error-title)\",\n },\n message: {\n fontSize: \"14px\",\n fontWeight: 400,\n lineHeight: \"21px\",\n margin: \"0 0 20px 0\",\n color: \"var(--next-error-message)\",\n },\n form: {\n margin: 0,\n },\n buttonGroup: {\n display: \"flex\",\n gap: \"8px\",\n alignItems: \"center\",\n },\n button: {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: \"32px\",\n padding: \"0 12px\",\n fontSize: \"14px\",\n fontWeight: 500,\n lineHeight: \"20px\",\n borderRadius: \"6px\",\n cursor: \"pointer\",\n color: \"var(--next-error-btn-text)\",\n background: \"var(--next-error-btn-bg)\",\n border: \"var(--next-error-btn-border)\",\n },\n buttonSecondary: {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: \"32px\",\n padding: \"0 12px\",\n fontSize: \"14px\",\n fontWeight: 500,\n lineHeight: \"20px\",\n borderRadius: \"6px\",\n cursor: \"pointer\",\n color: \"var(--next-error-btn-secondary-text)\",\n background: \"var(--next-error-btn-secondary-bg)\",\n border: \"var(--next-error-btn-secondary-border)\",\n },\n digestFooter: {\n position: \"fixed\" as const,\n bottom: \"32px\",\n left: \"0\",\n right: \"0\",\n textAlign: \"center\" as const,\n fontFamily: 'ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,monospace',\n fontSize: \"12px\",\n lineHeight: \"18px\",\n fontWeight: 400,\n margin: \"0\",\n color: \"var(--next-error-digest)\",\n },\n} as const;\n\nconst errorThemeCss = `\n:root {\n --next-error-bg: #fff;\n --next-error-text: #171717;\n --next-error-title: #171717;\n --next-error-message: #171717;\n --next-error-digest: #666666;\n --next-error-btn-text: #fff;\n --next-error-btn-bg: #171717;\n --next-error-btn-border: none;\n --next-error-btn-secondary-text: #171717;\n --next-error-btn-secondary-bg: transparent;\n --next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);\n}\n@media (prefers-color-scheme: dark) {\n :root {\n --next-error-bg: #0a0a0a;\n --next-error-text: #ededed;\n --next-error-title: #ededed;\n --next-error-message: #ededed;\n --next-error-digest: #a0a0a0;\n --next-error-btn-text: #0a0a0a;\n --next-error-btn-bg: #ededed;\n --next-error-btn-border: none;\n --next-error-btn-secondary-text: #ededed;\n --next-error-btn-secondary-bg: transparent;\n --next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);\n }\n}\nbody { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\n`.replace(/\\n\\s*/g, \"\");\n\nfunction WarningIcon() {\n return (\n <svg width=\"32\" height=\"32\" viewBox=\"-0.2 -1.5 32 32\" fill=\"none\" style={errorStyles.icon}>\n <path\n d=\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\"\n fill=\"var(--next-error-title)\"\n />\n </svg>\n );\n}\n\nfunction handleBackClick() {\n if (typeof window === \"undefined\") return;\n if (window.history.length > 1) {\n window.history.back();\n } else {\n window.location.href = \"/\";\n }\n}\n\nfunction DefaultGlobalError({ error }: DefaultGlobalErrorProps) {\n const digest: string | undefined = error?.digest;\n const isServerError = !!digest;\n\n const message = isServerError\n ? \"A server error occurred. Reload to try again.\"\n : \"Reload to try again, or go back.\";\n\n return (\n <html id=\"__next_error__\">\n <head>\n <style dangerouslySetInnerHTML={{ __html: errorThemeCss }} />\n </head>\n <body>\n <div style={errorStyles.container}>\n <div style={errorStyles.card}>\n <WarningIcon />\n <h1 style={errorStyles.title}>This page couldn’t load</h1>\n <p style={errorStyles.message}>{message}</p>\n <div style={errorStyles.buttonGroup}>\n <form style={errorStyles.form}>\n <button type=\"submit\" style={errorStyles.button}>\n Reload\n </button>\n </form>\n {!isServerError && (\n <button type=\"button\" style={errorStyles.buttonSecondary} onClick={handleBackClick}>\n Back\n </button>\n )}\n </div>\n </div>\n </div>\n {digest && <p style={errorStyles.digestFooter}>ERROR {digest}</p>}\n </body>\n </html>\n );\n}\n\nexport default DefaultGlobalError;\n"],"mappings":";;;;AA2BA,MAAM,cAAc;CAClB,WAAW;EACT,YACE;EACF,QAAQ;EACR,SAAS;EACT,YAAY;EACZ,gBAAgB;EACjB;CACD,MAAM;EACJ,WAAW;EACX,UAAU;EACV,SAAS;EACT,WAAW;EACZ;CACD,MAAM,EACJ,cAAc,QACf;CACD,OAAO;EACL,UAAU;EACV,YAAY;EACZ,eAAe;EACf,YAAY;EACZ,QAAQ;EACR,OAAO;EACR;CACD,SAAS;EACP,UAAU;EACV,YAAY;EACZ,YAAY;EACZ,QAAQ;EACR,OAAO;EACR;CACD,MAAM,EACJ,QAAQ,GACT;CACD,aAAa;EACX,SAAS;EACT,KAAK;EACL,YAAY;EACb;CACD,QAAQ;EACN,SAAS;EACT,YAAY;EACZ,gBAAgB;EAChB,QAAQ;EACR,SAAS;EACT,UAAU;EACV,YAAY;EACZ,YAAY;EACZ,cAAc;EACd,QAAQ;EACR,OAAO;EACP,YAAY;EACZ,QAAQ;EACT;CACD,iBAAiB;EACf,SAAS;EACT,YAAY;EACZ,gBAAgB;EAChB,QAAQ;EACR,SAAS;EACT,UAAU;EACV,YAAY;EACZ,YAAY;EACZ,cAAc;EACd,QAAQ;EACR,OAAO;EACP,YAAY;EACZ,QAAQ;EACT;CACD,cAAc;EACZ,UAAU;EACV,QAAQ;EACR,MAAM;EACN,OAAO;EACP,WAAW;EACX,YAAY;EACZ,UAAU;EACV,YAAY;EACZ,YAAY;EACZ,QAAQ;EACR,OAAO;EACR;CACF;AAED,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BpB,QAAQ,UAAU,GAAG;AAEvB,SAAS,cAAc;CACrB,OACE,oBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAkB,MAAK;EAAO,OAAO,YAAY;YACnF,oBAAC,QAAD;GACE,GAAE;GACF,MAAK;GACL,CAAA;EACE,CAAA;;AAIV,SAAS,kBAAkB;CACzB,IAAI,OAAO,WAAW,aAAa;CACnC,IAAI,OAAO,QAAQ,SAAS,GAC1B,OAAO,QAAQ,MAAM;MAErB,OAAO,SAAS,OAAO;;AAI3B,SAAS,mBAAmB,EAAE,SAAkC;CAC9D,MAAM,SAA6B,OAAO;CAC1C,MAAM,gBAAgB,CAAC,CAAC;CAExB,MAAM,UAAU,gBACZ,kDACA;CAEJ,OACE,qBAAC,QAAD;EAAM,IAAG;YAAT,CACE,oBAAC,QAAD,EAAA,UACE,oBAAC,SAAD,EAAO,yBAAyB,EAAE,QAAQ,eAAe,EAAI,CAAA,EACxD,CAAA,EACP,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,OAAD;GAAK,OAAO,YAAY;aACtB,qBAAC,OAAD;IAAK,OAAO,YAAY;cAAxB;KACE,oBAAC,aAAD,EAAe,CAAA;KACf,oBAAC,MAAD;MAAI,OAAO,YAAY;gBAAO;MAAmC,CAAA;KACjE,oBAAC,KAAD;MAAG,OAAO,YAAY;gBAAU;MAAY,CAAA;KAC5C,qBAAC,OAAD;MAAK,OAAO,YAAY;gBAAxB,CACE,oBAAC,QAAD;OAAM,OAAO,YAAY;iBACvB,oBAAC,UAAD;QAAQ,MAAK;QAAS,OAAO,YAAY;kBAAQ;QAExC,CAAA;OACJ,CAAA,EACN,CAAC,iBACA,oBAAC,UAAD;OAAQ,MAAK;OAAS,OAAO,YAAY;OAAiB,SAAS;iBAAiB;OAE3E,CAAA,CAEP;;KACF;;GACF,CAAA,EACL,UAAU,qBAAC,KAAD;GAAG,OAAO,YAAY;aAAtB,CAAoC,UAAO,OAAW;KAC5D,EAAA,CAAA,CACF"}
|
package/dist/shims/document.d.ts
CHANGED
|
@@ -29,9 +29,65 @@ declare function Main(): _$react_jsx_runtime0.JSX.Element;
|
|
|
29
29
|
*/
|
|
30
30
|
declare function NextScript(): _$react_jsx_runtime0.JSX.Element;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Loose stand-ins for Next.js's `DocumentContext` / `DocumentInitialProps`.
|
|
33
|
+
* The shim doesn't currently invoke `getInitialProps` on user `_document.tsx`
|
|
34
|
+
* files (separate gap), but the signatures here match Next.js's so subclasses
|
|
35
|
+
* that delegate via `await Document.getInitialProps(ctx)` typecheck against
|
|
36
|
+
* the same shape they'd see under real Next.js.
|
|
37
|
+
*
|
|
38
|
+
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/utils.ts
|
|
33
39
|
*/
|
|
34
|
-
|
|
40
|
+
type DocumentContext = {
|
|
41
|
+
renderPage?: (options?: {
|
|
42
|
+
enhanceApp?: (App: React.ComponentType<{
|
|
43
|
+
children?: React.ReactNode;
|
|
44
|
+
}>) => unknown;
|
|
45
|
+
enhanceComponent?: (Comp: React.ComponentType<unknown>) => unknown;
|
|
46
|
+
}) => {
|
|
47
|
+
html: string;
|
|
48
|
+
head?: ReadonlyArray<React.ReactElement>;
|
|
49
|
+
};
|
|
50
|
+
defaultGetInitialProps?: (ctx: DocumentContext, options?: {
|
|
51
|
+
nonce?: string;
|
|
52
|
+
}) => Promise<DocumentInitialProps>;
|
|
53
|
+
pathname?: string;
|
|
54
|
+
query?: Record<string, string | string[] | undefined>;
|
|
55
|
+
asPath?: string;
|
|
56
|
+
err?: any;
|
|
57
|
+
};
|
|
58
|
+
type DocumentInitialProps = {
|
|
59
|
+
html: string;
|
|
60
|
+
head?: ReadonlyArray<React.ReactElement>;
|
|
61
|
+
styles?: React.ReactElement[] | Iterable<React.ReactNode> | React.ReactElement;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Default Document component — also the base class user `_document.tsx` files
|
|
65
|
+
* `extend`. Must be a class (not a function) to match Next.js's `next/document`
|
|
66
|
+
* default export so `class MyDocument extends Document` produces a constructible
|
|
67
|
+
* class that React can instantiate during SSR. Returning a function here breaks
|
|
68
|
+
* any user `_document.tsx` that uses the class-based form because `extends`
|
|
69
|
+
* against a non-constructor produces a class that can only be called without
|
|
70
|
+
* `new`, which React refuses to do.
|
|
71
|
+
*
|
|
72
|
+
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/pages/_document.tsx
|
|
73
|
+
* Ported behavior: Next.js's default `Document` is a `class Document extends
|
|
74
|
+
* React.Component`. Custom documents extend it and override `getInitialProps`
|
|
75
|
+
* and `render`. Generic default matches Next.js (`P = {}`).
|
|
76
|
+
*/
|
|
77
|
+
declare class Document<P = {}> extends React.Component<P & {
|
|
78
|
+
children?: React.ReactNode;
|
|
79
|
+
}> {
|
|
80
|
+
/**
|
|
81
|
+
* `getInitialProps` is invoked by the SSR pipeline. The default implementation
|
|
82
|
+
* is a stub: vinext does not yet plumb the Pages Router `renderPage` /
|
|
83
|
+
* `defaultGetInitialProps` chain into the SSR entry, so subclasses that
|
|
84
|
+
* delegate via `await Document.getInitialProps(ctx)` receive an empty shell
|
|
85
|
+
* (`html: ""`). This matches the runtime contract user code expects without
|
|
86
|
+
* pretending the chain is wired up.
|
|
87
|
+
*/
|
|
88
|
+
static getInitialProps(_ctx: DocumentContext): Promise<DocumentInitialProps>;
|
|
89
|
+
render(): React.ReactNode;
|
|
90
|
+
}
|
|
35
91
|
//#endregion
|
|
36
|
-
export { Head, Html, Main, NextScript, Document as default };
|
|
92
|
+
export { DocumentContext, DocumentInitialProps, Head, Html, Main, NextScript, Document as default };
|
|
37
93
|
//# sourceMappingURL=document.d.ts.map
|