vinext 0.0.52 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/build/clean-output.d.ts +14 -0
- package/dist/build/clean-output.js +36 -0
- package/dist/build/clean-output.js.map +1 -0
- package/dist/build/inline-css.d.ts +7 -0
- package/dist/build/inline-css.js +50 -0
- package/dist/build/inline-css.js.map +1 -0
- package/dist/build/prerender.d.ts +6 -2
- package/dist/build/prerender.js +51 -12
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/run-prerender.js +10 -1
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/static-export.d.ts +5 -0
- package/dist/build/static-export.js +8 -3
- package/dist/build/static-export.js.map +1 -1
- package/dist/check.js +4 -0
- package/dist/check.js.map +1 -1
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/instrumentation-client-inject.d.ts +34 -0
- package/dist/client/instrumentation-client-inject.js +57 -0
- package/dist/client/instrumentation-client-inject.js.map +1 -0
- package/dist/client/navigation-runtime.d.ts +16 -2
- package/dist/client/navigation-runtime.js +16 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/client/vinext-next-data.js.map +1 -1
- package/dist/client/window-next.d.ts +17 -2
- package/dist/client/window-next.js.map +1 -1
- package/dist/cloudflare/tpr.js +1 -1
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.js +2 -1
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +95 -4
- package/dist/config/next-config.js +173 -14
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +42 -7
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.d.ts +11 -1
- package/dist/entries/app-browser-entry.js +16 -6
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +12 -3
- package/dist/entries/app-rsc-entry.js +41 -8
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +21 -1
- package/dist/entries/app-rsc-manifest.js +6 -4
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-client-entry.d.ts +4 -1
- package/dist/entries/pages-client-entry.js +40 -3
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +292 -34
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +1 -10
- package/dist/entries/runtime-entry-module.js +2 -12
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +91 -10
- package/dist/index.js.map +1 -1
- package/dist/plugins/fonts.js +25 -2
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/remove-console.d.ts +16 -0
- package/dist/plugins/remove-console.js +176 -0
- package/dist/plugins/remove-console.js.map +1 -0
- package/dist/routing/app-route-graph.d.ts +24 -1
- package/dist/routing/app-route-graph.js +52 -4
- 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/file-matcher.d.ts +21 -1
- package/dist/routing/file-matcher.js +39 -1
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.d.ts +1 -1
- package/dist/routing/pages-router.js +10 -3
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-trie.js +13 -18
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/routing/utils.d.ts +11 -1
- package/dist/routing/utils.js +15 -1
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.js +19 -10
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-action-result.d.ts +16 -1
- package/dist/server/app-browser-action-result.js +15 -1
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +47 -28
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-navigation-controller.d.ts +2 -0
- package/dist/server/app-browser-navigation-controller.js +4 -0
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-elements-wire.d.ts +13 -4
- package/dist/server/app-elements-wire.js +10 -1
- package/dist/server/app-elements-wire.js.map +1 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +2 -2
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.d.ts +27 -8
- package/dist/server/app-fallback-renderer.js +19 -8
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-history-state.js +6 -2
- package/dist/server/app-history-state.js.map +1 -1
- package/dist/server/app-inline-css-client.d.ts +7 -0
- package/dist/server/app-inline-css-client.js +37 -0
- package/dist/server/app-inline-css-client.js.map +1 -0
- package/dist/server/app-interception-context-header.d.ts +33 -0
- package/dist/server/app-interception-context-header.js +44 -0
- package/dist/server/app-interception-context-header.js.map +1 -0
- package/dist/server/app-mounted-slots-header.d.ts +19 -0
- package/dist/server/app-mounted-slots-header.js +40 -1
- package/dist/server/app-mounted-slots-header.js.map +1 -1
- package/dist/server/app-optimistic-routing.js +26 -18
- package/dist/server/app-optimistic-routing.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +1 -0
- package/dist/server/app-page-boundary-render.js +2 -0
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.d.ts +22 -1
- package/dist/server/app-page-boundary.js +30 -3
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +9 -3
- package/dist/server/app-page-cache.js +14 -8
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +13 -1
- package/dist/server/app-page-dispatch.js +136 -82
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.d.ts +2 -1
- package/dist/server/app-page-element-builder.js +17 -30
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +1 -0
- package/dist/server/app-page-execution.js +2 -0
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.d.ts +1 -0
- package/dist/server/app-page-head.js +8 -0
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-render-identity.d.ts +22 -0
- package/dist/server/app-page-render-identity.js +42 -0
- package/dist/server/app-page-render-identity.js.map +1 -0
- package/dist/server/app-page-render-observation.js +1 -1
- package/dist/server/app-page-render.d.ts +9 -1
- package/dist/server/app-page-render.js +8 -2
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +6 -3
- package/dist/server/app-page-request.js +5 -2
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.d.ts +11 -1
- package/dist/server/app-page-response.js +16 -4
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +16 -0
- package/dist/server/app-page-route-wiring.js +25 -10
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +12 -0
- package/dist/server/app-page-stream.js +3 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +3 -0
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-response.js +38 -6
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-rsc-handler.d.ts +16 -3
- package/dist/server/app-rsc-handler.js +60 -11
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.d.ts +2 -1
- package/dist/server/app-rsc-request-normalization.js +6 -4
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-segment-config.d.ts +4 -1
- package/dist/server/app-segment-config.js +6 -1
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +22 -3
- package/dist/server/app-server-action-execution.js +46 -7
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +57 -6
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-error-meta.js +3 -3
- package/dist/server/app-ssr-error-meta.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +25 -1
- package/dist/server/app-ssr-stream.js +237 -19
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/app-static-generation.d.ts +1 -0
- package/dist/server/app-static-generation.js +2 -1
- package/dist/server/app-static-generation.js.map +1 -1
- package/dist/server/client-trace-metadata.d.ts +31 -0
- package/dist/server/client-trace-metadata.js +83 -0
- package/dist/server/client-trace-metadata.js.map +1 -0
- package/dist/server/cookie-utils.d.ts +13 -0
- package/dist/server/cookie-utils.js +20 -0
- package/dist/server/cookie-utils.js.map +1 -0
- package/dist/server/default-not-found-module.d.ts +20 -0
- package/dist/server/default-not-found-module.js +20 -0
- package/dist/server/default-not-found-module.js.map +1 -0
- package/dist/server/dev-server.d.ts +8 -1
- package/dist/server/dev-server.js +56 -11
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/headers.d.ts +5 -1
- package/dist/server/headers.js +5 -1
- package/dist/server/headers.js.map +1 -1
- package/dist/server/html.d.ts +2 -1
- package/dist/server/html.js +6 -1
- package/dist/server/html.js.map +1 -1
- package/dist/server/image-optimization.d.ts +13 -4
- package/dist/server/image-optimization.js +15 -4
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/isr-cache.d.ts +7 -5
- package/dist/server/isr-cache.js +17 -6
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/middleware-runtime.js +1 -2
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/middleware.js +1 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +18 -0
- package/dist/server/pages-api-route.js +3 -1
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-body-parser-config.d.ts +60 -0
- package/dist/server/pages-body-parser-config.js +79 -0
- package/dist/server/pages-body-parser-config.js.map +1 -0
- package/dist/server/pages-data-route.js +1 -0
- package/dist/server/pages-data-route.js.map +1 -1
- package/dist/server/pages-default-404.d.ts +31 -0
- package/dist/server/pages-default-404.js +40 -0
- package/dist/server/pages-default-404.js.map +1 -0
- package/dist/server/pages-document-initial-props.d.ts +7 -0
- package/dist/server/pages-document-initial-props.js +14 -0
- package/dist/server/pages-document-initial-props.js.map +1 -0
- package/dist/server/pages-node-compat.d.ts +10 -0
- package/dist/server/pages-node-compat.js +12 -1
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +40 -0
- package/dist/server/pages-page-data.js +19 -14
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-method.d.ts +48 -0
- package/dist/server/pages-page-method.js +19 -0
- package/dist/server/pages-page-method.js.map +1 -0
- package/dist/server/pages-page-response.d.ts +8 -0
- package/dist/server/pages-page-response.js +21 -11
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/pages-serializable-props.d.ts +25 -0
- package/dist/server/pages-serializable-props.js +69 -0
- package/dist/server/pages-serializable-props.js.map +1 -0
- package/dist/server/prerender-route-params.d.ts +14 -0
- package/dist/server/prerender-route-params.js +94 -0
- package/dist/server/prerender-route-params.js.map +1 -0
- package/dist/server/prod-server.d.ts +3 -23
- package/dist/server/prod-server.js +43 -57
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/proxy-trust.d.ts +41 -0
- package/dist/server/proxy-trust.js +70 -0
- package/dist/server/proxy-trust.js.map +1 -0
- package/dist/server/request-pipeline.d.ts +3 -3
- package/dist/server/request-pipeline.js +5 -4
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/seed-cache.js +12 -6
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/server-action-not-found.js +3 -2
- package/dist/server/server-action-not-found.js.map +1 -1
- package/dist/server/static-file-cache.js +2 -1
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/server/streaming-metadata.d.ts +5 -0
- package/dist/server/streaming-metadata.js +10 -0
- package/dist/server/streaming-metadata.js.map +1 -0
- package/dist/shims/app-router-scroll-state.d.ts +14 -0
- package/dist/shims/app-router-scroll-state.js +51 -0
- package/dist/shims/app-router-scroll-state.js.map +1 -0
- package/dist/shims/app-router-scroll.d.ts +28 -0
- package/dist/shims/app-router-scroll.js +115 -0
- package/dist/shims/app-router-scroll.js.map +1 -0
- package/dist/shims/before-interactive-context.d.ts +30 -0
- package/dist/shims/before-interactive-context.js +10 -0
- package/dist/shims/before-interactive-context.js.map +1 -0
- package/dist/shims/cache-runtime.d.ts +1 -1
- package/dist/shims/cache-runtime.js +14 -1
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +6 -0
- package/dist/shims/cache.js +7 -0
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/default-not-found.d.ts +12 -0
- package/dist/shims/default-not-found.js +61 -0
- package/dist/shims/default-not-found.js.map +1 -0
- package/dist/shims/error.js +3 -0
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/font-local.d.ts +5 -0
- package/dist/shims/font-local.js +6 -2
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/head.js +4 -4
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +13 -2
- package/dist/shims/headers.js +73 -22
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/image.d.ts +1 -1
- package/dist/shims/image.js +4 -4
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/app-route-detection.d.ts +37 -0
- package/dist/shims/internal/app-route-detection.js +69 -0
- package/dist/shims/internal/app-route-detection.js.map +1 -0
- package/dist/shims/internal/pages-data-target.d.ts +58 -0
- package/dist/shims/internal/pages-data-target.js +91 -0
- package/dist/shims/internal/pages-data-target.js.map +1 -0
- package/dist/shims/internal/pages-data-url.d.ts +42 -0
- package/dist/shims/internal/pages-data-url.js +73 -0
- package/dist/shims/internal/pages-data-url.js.map +1 -0
- package/dist/shims/link.d.ts +18 -2
- package/dist/shims/link.js +129 -15
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +9 -7
- package/dist/shims/metadata.js +70 -7
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +1 -2
- package/dist/shims/navigation.js +94 -20
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.d.ts +5 -0
- package/dist/shims/router.js +389 -80
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.d.ts +11 -1
- package/dist/shims/script.js +158 -15
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.js +1 -0
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/url-utils.d.ts +2 -1
- package/dist/shims/url-utils.js +15 -4
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/utils/html-limited-bots.d.ts +5 -0
- package/dist/utils/html-limited-bots.js +15 -0
- package/dist/utils/html-limited-bots.js.map +1 -0
- package/dist/utils/path.d.ts +13 -0
- package/dist/utils/path.js +16 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/query.d.ts +6 -0
- package/dist/utils/query.js +10 -1
- package/dist/utils/query.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-handler.js","names":["decodeQueryString"],"sources":["../../src/server/api-handler.ts"],"sourcesContent":["/**\n * API route handler for Pages Router (pages/api/*).\n *\n * Next.js API routes export a default handler function:\n * export default function handler(req, res) { ... }\n *\n * The req/res objects are Node.js IncomingMessage/ServerResponse with\n * Next.js extensions: req.query, req.body, res.json(), res.status(), etc.\n */\nimport \"./server-globals.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { decode as decodeQueryString } from \"node:querystring\";\nimport { Buffer } from \"node:buffer\";\nimport { type Route, matchRoute } from \"../routing/pages-router.js\";\nimport { reportRequestError, importModule, type ModuleImporter } from \"./instrumentation.js\";\nimport { mergeRouteParamsIntoQuery, parseQueryString } from \"../utils/query.js\";\nimport { PagesBodyParseError, getMediaType, isJsonMediaType } from \"./pages-media-type.js\";\nimport { isEdgeApiRuntime } from \"./edge-api-runtime.js\";\nimport { NextRequest } from \"vinext/shims/server\";\n\n/**\n * Extend the Node.js request with Next.js-style helpers.\n */\ntype NextApiRequest = {\n query: Record<string, string | string[]>;\n body: unknown;\n cookies: Record<string, string>;\n} & IncomingMessage;\n\n/**\n * Extend the Node.js response with Next.js-style helpers.\n */\ntype NextApiResponse = {\n status(code: number): NextApiResponse;\n json(data: unknown): void;\n send(data: unknown): void;\n redirect(statusOrUrl: number | string, url?: string): void;\n} & ServerResponse;\n\ntype EdgeApiRouteModule = {\n /**\n * `export const config = { runtime: 'edge' }` — historical Pages Router form.\n */\n config?: {\n runtime?: string;\n };\n /**\n * `export const runtime = 'edge'` — bare export form. Next.js resolves the\n * effective runtime as `config.runtime ?? config.config?.runtime`, so a\n * top-level `runtime` export takes precedence over the nested config form.\n */\n runtime?: string;\n default: (request: Request) => Response | Promise<Response>;\n};\n\n/**\n * Maximum request body size (1 MB). Matches Next.js default bodyParser sizeLimit.\n * @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config\n * Prevents denial-of-service via unbounded request body buffering.\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024;\n\n/**\n * Parse the request body based on content-type.\n * Enforces a size limit to prevent memory exhaustion attacks.\n */\nasync function parseBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let totalSize = 0;\n let settled = false;\n req.on(\"data\", (chunk: Buffer) => {\n totalSize += chunk.length;\n if (totalSize > MAX_BODY_SIZE) {\n settled = true;\n req.destroy();\n reject(new PagesBodyParseError(\"Request body too large\", 413));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"error\", (err) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n });\n req.on(\"end\", () => {\n if (settled) return;\n settled = true;\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n const mediaType = getMediaType(req.headers[\"content-type\"]);\n if (!raw) {\n resolve(\n isJsonMediaType(mediaType)\n ? {}\n : mediaType === \"application/x-www-form-urlencoded\"\n ? decodeQueryString(raw)\n : undefined,\n );\n return;\n }\n if (isJsonMediaType(mediaType)) {\n try {\n resolve(JSON.parse(raw));\n } catch {\n reject(new PagesBodyParseError(\"Invalid JSON\", 400));\n }\n } else if (mediaType === \"application/x-www-form-urlencoded\") {\n resolve(decodeQueryString(raw));\n } else {\n resolve(raw);\n }\n });\n });\n}\n\n/**\n * Parse cookies from the Cookie header.\n */\nfunction parseCookies(req: IncomingMessage): Record<string, string> {\n const header = req.headers.cookie ?? \"\";\n const cookies: Record<string, string> = {};\n for (const part of header.split(\";\")) {\n const [key, ...rest] = part.split(\"=\");\n if (key) {\n cookies[key.trim()] = rest.join(\"=\").trim();\n }\n }\n return cookies;\n}\n\nfunction isEdgeApiRouteModule(module: Record<string, unknown>): module is EdgeApiRouteModule {\n if (typeof module.default !== \"function\") return false;\n // Bare `export const runtime = 'edge'` takes precedence over the nested config\n // form, matching Next.js (`config.runtime ?? config.config?.runtime` in\n // packages/next/src/build/analysis/get-page-static-info.ts).\n const bare = module.runtime;\n if (typeof bare === \"string\") return isEdgeApiRuntime(bare);\n const config = module.config;\n if (!config || typeof config !== \"object\") return false;\n const runtime = \"runtime\" in config ? (config as { runtime?: unknown }).runtime : undefined;\n return typeof runtime === \"string\" && isEdgeApiRuntime(runtime);\n}\n\nfunction readEdgeRequestBody(req: IncomingMessage): ReadableStream<Uint8Array> | undefined {\n if (req.method === \"GET\" || req.method === \"HEAD\") return undefined;\n return new ReadableStream<Uint8Array>({\n start(controller) {\n req.on(\"data\", (chunk: Buffer | string) => {\n controller.enqueue(typeof chunk === \"string\" ? Buffer.from(chunk) : new Uint8Array(chunk));\n });\n req.on(\"end\", () => controller.close());\n req.on(\"error\", (error) => controller.error(error));\n },\n });\n}\n\nfunction createEdgeApiRequest(req: IncomingMessage, url: string): Request {\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n for (const item of value) headers.append(name, item);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n\n const rawProto = headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim();\n const forwardedProto = rawProto === \"https\" || rawProto === \"http\" ? rawProto : \"http\";\n const host = headers.get(\"host\") ?? \"localhost\";\n const requestUrl = new URL(url, `${forwardedProto}://${host}`);\n const body = readEdgeRequestBody(req);\n\n const init: RequestInit & { duplex?: \"half\" } = {\n headers,\n method: req.method,\n };\n\n if (body) {\n init.body = body;\n init.duplex = \"half\";\n }\n\n return new Request(requestUrl, init);\n}\n\nfunction waitForWritableDrain(res: ServerResponse): Promise<void> {\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n res.off(\"drain\", onDrain);\n res.off(\"error\", onError);\n res.off(\"close\", onClose);\n };\n const onDrain = () => {\n cleanup();\n resolve();\n };\n const onError = (error: Error) => {\n cleanup();\n reject(error);\n };\n const onClose = () => {\n cleanup();\n reject(new Error(\"Response closed before writable drain\"));\n };\n res.once(\"drain\", onDrain);\n res.once(\"error\", onError);\n res.once(\"close\", onClose);\n });\n}\n\nasync function writeEdgeApiResponseBody(\n res: ServerResponse,\n body: ReadableStream<Uint8Array> | null,\n): Promise<void> {\n if (!body) {\n res.end();\n return;\n }\n\n const reader = body.getReader();\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n if (result.value.byteLength === 0) continue;\n if (!res.write(Buffer.from(result.value))) {\n await waitForWritableDrain(res);\n }\n }\n res.end();\n } catch (error) {\n res.destroy(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Enhance a Node.js req/res pair with Next.js API route helpers.\n */\nfunction enhanceApiObjects(\n req: IncomingMessage,\n res: ServerResponse,\n query: Record<string, string | string[]>,\n body: unknown,\n): { apiReq: NextApiRequest; apiRes: NextApiResponse } {\n const apiReq: NextApiRequest = Object.assign(req, {\n body,\n cookies: parseCookies(req),\n query,\n });\n\n const apiRes: NextApiResponse = Object.assign(res, {\n status(this: NextApiResponse, code: number) {\n this.statusCode = code;\n return this;\n },\n\n json(this: NextApiResponse, data: unknown) {\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(JSON.stringify(data));\n },\n\n send(this: NextApiResponse, data: unknown) {\n if (Buffer.isBuffer(data)) {\n if (!this.getHeader(\"Content-Type\")) {\n this.setHeader(\"Content-Type\", \"application/octet-stream\");\n }\n this.setHeader(\"Content-Length\", String(data.length));\n this.end(data);\n return;\n }\n\n if (typeof data === \"object\" && data !== null) {\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(JSON.stringify(data));\n } else {\n if (!this.getHeader(\"Content-Type\")) {\n this.setHeader(\"Content-Type\", \"text/plain\");\n }\n this.end(String(data));\n }\n },\n\n redirect(this: NextApiResponse, statusOrUrl: number | string, url?: string) {\n if (typeof statusOrUrl === \"string\") {\n this.writeHead(307, { Location: statusOrUrl });\n } else {\n this.writeHead(statusOrUrl, { Location: url ?? \"\" });\n }\n this.end();\n },\n });\n\n return { apiReq, apiRes };\n}\n\n/**\n * Handle an API route request.\n * Returns true if the request was handled, false if no API route matched.\n */\nexport async function handleApiRoute(\n runner: ModuleImporter,\n req: IncomingMessage,\n res: ServerResponse,\n url: string,\n apiRoutes: Route[],\n): Promise<boolean> {\n const match = matchRoute(url, apiRoutes);\n if (!match) return false;\n\n const { route, params } = match;\n\n try {\n // Load the API route module through the ModuleRunner\n const apiModule = await importModule(runner, route.filePath);\n if (isEdgeApiRouteModule(apiModule)) {\n // Next.js wraps the incoming Request in a NextRequest before invoking\n // edge API handlers, so handlers can use `req.nextUrl.searchParams`,\n // `req.cookies`, etc. (Cf. NextRequestHint in next/src/server/web/adapter.ts.)\n const nextRequest = new NextRequest(createEdgeApiRequest(req, url));\n const response = await apiModule.default(nextRequest);\n if (!(response instanceof Response)) {\n throw new Error(\"Edge API route did not return a Response\");\n }\n\n res.statusCode = response.status;\n res.statusMessage = response.statusText;\n const setCookieHeaders = response.headers.getSetCookie();\n response.headers.forEach((value, name) => {\n if (name !== \"set-cookie\") res.setHeader(name, value);\n });\n if (setCookieHeaders.length) {\n res.setHeader(\"set-cookie\", setCookieHeaders);\n }\n await writeEdgeApiResponseBody(res, response.body);\n return true;\n }\n\n const handler = apiModule.default;\n if (typeof handler !== \"function\") {\n console.error(`[vinext] API route ${route.filePath} does not export a default function`);\n res.statusCode = 500;\n res.end(\"API route does not export a default function\");\n return true;\n }\n\n // Parse query from URL + route params. Path params win over same-key search\n // params so a query string cannot change the dynamic route value.\n const query = mergeRouteParamsIntoQuery(parseQueryString(url), params);\n\n // Parse body\n const body = await parseBody(req);\n\n // Enhance req/res with Next.js helpers\n const { apiReq, apiRes } = enhanceApiObjects(req, res, query, body);\n\n // Call the handler\n await handler(apiReq, apiRes);\n return true;\n } catch (e) {\n if (e instanceof PagesBodyParseError) {\n res.statusCode = e.statusCode;\n res.statusMessage = e.message;\n res.end(e.message);\n return true;\n }\n\n // ssrFixStacktrace() is specific to ssrLoadModule and is not applicable\n // when using ModuleRunner — no stack trace fixup is needed here.\n console.error(e);\n void reportRequestError(\n e instanceof Error ? e : new Error(String(e)),\n {\n path: url,\n method: req.method ?? \"GET\",\n headers: Object.fromEntries(\n Object.entries(req.headers).map(([k, v]) => [\n k,\n Array.isArray(v) ? v.join(\", \") : String(v ?? \"\"),\n ]),\n ),\n },\n { routerKind: \"Pages Router\", routePath: match.route.pattern, routeType: \"route\" },\n );\n if (!res.headersSent) {\n res.statusCode = 500;\n res.end(\"Internal Server Error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n return true;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-handler.js","names":["decodeQueryString"],"sources":["../../src/server/api-handler.ts"],"sourcesContent":["/**\n * API route handler for Pages Router (pages/api/*).\n *\n * Next.js API routes export a default handler function:\n * export default function handler(req, res) { ... }\n *\n * The req/res objects are Node.js IncomingMessage/ServerResponse with\n * Next.js extensions: req.query, req.body, res.json(), res.status(), etc.\n */\nimport \"./server-globals.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { decode as decodeQueryString } from \"node:querystring\";\nimport { Buffer } from \"node:buffer\";\nimport { type Route, matchRoute } from \"../routing/pages-router.js\";\nimport { reportRequestError, importModule, type ModuleImporter } from \"./instrumentation.js\";\nimport { mergeRouteParamsIntoQuery, parseQueryString } from \"../utils/query.js\";\nimport { PagesBodyParseError, getMediaType, isJsonMediaType } from \"./pages-media-type.js\";\nimport { isEdgeApiRuntime } from \"./edge-api-runtime.js\";\nimport {\n DEFAULT_PAGES_API_BODY_SIZE_LIMIT,\n resolveBodyParserConfig,\n} from \"./pages-body-parser-config.js\";\nimport { resolveRequestProtocol, resolveRequestHost } from \"./proxy-trust.js\";\nimport { NextRequest } from \"vinext/shims/server\";\n\n/**\n * Extend the Node.js request with Next.js-style helpers.\n */\ntype NextApiRequest = {\n query: Record<string, string | string[]>;\n body: unknown;\n cookies: Record<string, string>;\n} & IncomingMessage;\n\n/**\n * Extend the Node.js response with Next.js-style helpers.\n */\ntype NextApiResponse = {\n status(code: number): NextApiResponse;\n json(data: unknown): void;\n send(data: unknown): void;\n redirect(statusOrUrl: number | string, url?: string): void;\n} & ServerResponse;\n\ntype EdgeApiRouteModule = {\n /**\n * `export const config = { runtime: 'edge' }` — historical Pages Router form.\n */\n config?: {\n runtime?: string;\n };\n /**\n * `export const runtime = 'edge'` — bare export form. Next.js resolves the\n * effective runtime as `config.runtime ?? config.config?.runtime`, so a\n * top-level `runtime` export takes precedence over the nested config form.\n */\n runtime?: string;\n default: (request: Request) => Response | Promise<Response>;\n};\n\n/**\n * Default request body size (1 MB). Matches Next.js default bodyParser sizeLimit.\n * @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config\n * Prevents denial-of-service via unbounded request body buffering.\n */\nconst MAX_BODY_SIZE = DEFAULT_PAGES_API_BODY_SIZE_LIMIT;\n\n/**\n * Parse the request body based on content-type.\n * Enforces a size limit to prevent memory exhaustion attacks.\n *\n * The `sizeLimit` argument honours `export const config = { api: { bodyParser:\n * { sizeLimit: '4mb' } } }` on the route module. To opt out of parsing\n * entirely (`bodyParser: false`), callers must skip this function so the\n * underlying readable stream stays intact on `req` (critical for webhook\n * HMAC signature verification).\n */\nasync function parseBody(\n req: IncomingMessage,\n sizeLimit: number = MAX_BODY_SIZE,\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let totalSize = 0;\n let settled = false;\n req.on(\"data\", (chunk: Buffer) => {\n totalSize += chunk.length;\n if (totalSize > sizeLimit) {\n settled = true;\n req.destroy();\n reject(new PagesBodyParseError(\"Request body too large\", 413));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"error\", (err) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n });\n req.on(\"end\", () => {\n if (settled) return;\n settled = true;\n const raw = Buffer.concat(chunks).toString(\"utf-8\");\n const mediaType = getMediaType(req.headers[\"content-type\"]);\n if (!raw) {\n resolve(\n isJsonMediaType(mediaType)\n ? {}\n : mediaType === \"application/x-www-form-urlencoded\"\n ? decodeQueryString(raw)\n : undefined,\n );\n return;\n }\n if (isJsonMediaType(mediaType)) {\n try {\n resolve(JSON.parse(raw));\n } catch {\n reject(new PagesBodyParseError(\"Invalid JSON\", 400));\n }\n } else if (mediaType === \"application/x-www-form-urlencoded\") {\n resolve(decodeQueryString(raw));\n } else {\n resolve(raw);\n }\n });\n });\n}\n\n/**\n * Parse cookies from the Cookie header.\n */\nfunction parseCookies(req: IncomingMessage): Record<string, string> {\n const header = req.headers.cookie ?? \"\";\n const cookies: Record<string, string> = {};\n for (const part of header.split(\";\")) {\n const [key, ...rest] = part.split(\"=\");\n if (key) {\n cookies[key.trim()] = rest.join(\"=\").trim();\n }\n }\n return cookies;\n}\n\nfunction isEdgeApiRouteModule(module: Record<string, unknown>): module is EdgeApiRouteModule {\n if (typeof module.default !== \"function\") return false;\n // Bare `export const runtime = 'edge'` takes precedence over the nested config\n // form, matching Next.js (`config.runtime ?? config.config?.runtime` in\n // packages/next/src/build/analysis/get-page-static-info.ts).\n const bare = module.runtime;\n if (typeof bare === \"string\") return isEdgeApiRuntime(bare);\n const config = module.config;\n if (!config || typeof config !== \"object\") return false;\n const runtime = \"runtime\" in config ? (config as { runtime?: unknown }).runtime : undefined;\n return typeof runtime === \"string\" && isEdgeApiRuntime(runtime);\n}\n\nfunction readEdgeRequestBody(req: IncomingMessage): ReadableStream<Uint8Array> | undefined {\n if (req.method === \"GET\" || req.method === \"HEAD\") return undefined;\n return new ReadableStream<Uint8Array>({\n start(controller) {\n req.on(\"data\", (chunk: Buffer | string) => {\n controller.enqueue(typeof chunk === \"string\" ? Buffer.from(chunk) : new Uint8Array(chunk));\n });\n req.on(\"end\", () => controller.close());\n req.on(\"error\", (error) => controller.error(error));\n },\n });\n}\n\nfunction createEdgeApiRequest(req: IncomingMessage, url: string): Request {\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n for (const item of value) headers.append(name, item);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n\n // Honor `X-Forwarded-Proto` / `X-Forwarded-Host` only when running behind\n // a trusted proxy (gated on `VINEXT_TRUST_PROXY` / `VINEXT_TRUSTED_HOSTS`).\n // Without this gate a client could send `X-Forwarded-Proto: https` and\n // trick edge API handlers that check `request.url.startsWith(\"https\")`\n // (e.g. to gate Secure-cookie issuance) into believing the request was\n // TLS-terminated. See: Finding F-PROD-7 in SECURITY-AUDIT-2026-05.md.\n const proto = resolveRequestProtocol(req);\n const host = resolveRequestHost(req, \"localhost\");\n const requestUrl = new URL(url, `${proto}://${host}`);\n const body = readEdgeRequestBody(req);\n\n const init: RequestInit & { duplex?: \"half\" } = {\n headers,\n method: req.method,\n };\n\n if (body) {\n init.body = body;\n init.duplex = \"half\";\n }\n\n return new Request(requestUrl, init);\n}\n\nfunction waitForWritableDrain(res: ServerResponse): Promise<void> {\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n res.off(\"drain\", onDrain);\n res.off(\"error\", onError);\n res.off(\"close\", onClose);\n };\n const onDrain = () => {\n cleanup();\n resolve();\n };\n const onError = (error: Error) => {\n cleanup();\n reject(error);\n };\n const onClose = () => {\n cleanup();\n reject(new Error(\"Response closed before writable drain\"));\n };\n res.once(\"drain\", onDrain);\n res.once(\"error\", onError);\n res.once(\"close\", onClose);\n });\n}\n\nasync function writeEdgeApiResponseBody(\n res: ServerResponse,\n body: ReadableStream<Uint8Array> | null,\n): Promise<void> {\n if (!body) {\n res.end();\n return;\n }\n\n const reader = body.getReader();\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n if (result.value.byteLength === 0) continue;\n if (!res.write(Buffer.from(result.value))) {\n await waitForWritableDrain(res);\n }\n }\n res.end();\n } catch (error) {\n res.destroy(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Enhance a Node.js req/res pair with Next.js API route helpers.\n */\nfunction enhanceApiObjects(\n req: IncomingMessage,\n res: ServerResponse,\n query: Record<string, string | string[]>,\n body: unknown,\n): { apiReq: NextApiRequest; apiRes: NextApiResponse } {\n const apiReq: NextApiRequest = Object.assign(req, {\n body,\n cookies: parseCookies(req),\n query,\n });\n\n const apiRes: NextApiResponse = Object.assign(res, {\n status(this: NextApiResponse, code: number) {\n this.statusCode = code;\n return this;\n },\n\n json(this: NextApiResponse, data: unknown) {\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(JSON.stringify(data));\n },\n\n send(this: NextApiResponse, data: unknown) {\n if (Buffer.isBuffer(data)) {\n if (!this.getHeader(\"Content-Type\")) {\n this.setHeader(\"Content-Type\", \"application/octet-stream\");\n }\n this.setHeader(\"Content-Length\", String(data.length));\n this.end(data);\n return;\n }\n\n if (typeof data === \"object\" && data !== null) {\n this.setHeader(\"Content-Type\", \"application/json\");\n this.end(JSON.stringify(data));\n } else {\n if (!this.getHeader(\"Content-Type\")) {\n this.setHeader(\"Content-Type\", \"text/plain\");\n }\n this.end(String(data));\n }\n },\n\n redirect(this: NextApiResponse, statusOrUrl: number | string, url?: string) {\n if (typeof statusOrUrl === \"string\") {\n this.writeHead(307, { Location: statusOrUrl });\n } else {\n this.writeHead(statusOrUrl, { Location: url ?? \"\" });\n }\n this.end();\n },\n });\n\n return { apiReq, apiRes };\n}\n\n/**\n * Handle an API route request.\n * Returns true if the request was handled, false if no API route matched.\n */\nexport async function handleApiRoute(\n runner: ModuleImporter,\n req: IncomingMessage,\n res: ServerResponse,\n url: string,\n apiRoutes: Route[],\n): Promise<boolean> {\n const match = matchRoute(url, apiRoutes);\n if (!match) return false;\n\n const { route, params } = match;\n\n try {\n // Load the API route module through the ModuleRunner\n const apiModule = await importModule(runner, route.filePath);\n if (isEdgeApiRouteModule(apiModule)) {\n // Next.js wraps the incoming Request in a NextRequest before invoking\n // edge API handlers, so handlers can use `req.nextUrl.searchParams`,\n // `req.cookies`, etc. (Cf. NextRequestHint in next/src/server/web/adapter.ts.)\n const nextRequest = new NextRequest(createEdgeApiRequest(req, url));\n const response = await apiModule.default(nextRequest);\n if (!(response instanceof Response)) {\n throw new Error(\"Edge API route did not return a Response\");\n }\n\n res.statusCode = response.status;\n res.statusMessage = response.statusText;\n const setCookieHeaders = response.headers.getSetCookie();\n response.headers.forEach((value, name) => {\n if (name !== \"set-cookie\") res.setHeader(name, value);\n });\n if (setCookieHeaders.length) {\n res.setHeader(\"set-cookie\", setCookieHeaders);\n }\n await writeEdgeApiResponseBody(res, response.body);\n return true;\n }\n\n const handler = apiModule.default;\n if (typeof handler !== \"function\") {\n console.error(`[vinext] API route ${route.filePath} does not export a default function`);\n res.statusCode = 500;\n res.end(\"API route does not export a default function\");\n return true;\n }\n\n // Parse query from URL + route params. Path params win over same-key search\n // params so a query string cannot change the dynamic route value.\n const query = mergeRouteParamsIntoQuery(parseQueryString(url), params);\n\n // Honour `export const config = { api: { bodyParser: ... } }` on the\n // route module. When the handler opts out (`bodyParser: false`) we must\n // not consume the stream — leave `req` intact so user code (e.g. a\n // Stripe/GitHub webhook) can read the raw bytes for HMAC verification.\n const bodyParserConfig = resolveBodyParserConfig(\n (apiModule as { config?: { api?: { bodyParser?: unknown } } }).config as\n | { api?: { bodyParser?: boolean | { sizeLimit?: string | number } } }\n | undefined,\n );\n\n const body = bodyParserConfig.enabled\n ? await parseBody(req, bodyParserConfig.sizeLimit)\n : undefined;\n\n // Enhance req/res with Next.js helpers\n const { apiReq, apiRes } = enhanceApiObjects(req, res, query, body);\n\n // Call the handler\n await handler(apiReq, apiRes);\n return true;\n } catch (e) {\n if (e instanceof PagesBodyParseError) {\n res.statusCode = e.statusCode;\n res.statusMessage = e.message;\n res.end(e.message);\n return true;\n }\n\n // ssrFixStacktrace() is specific to ssrLoadModule and is not applicable\n // when using ModuleRunner — no stack trace fixup is needed here.\n console.error(e);\n void reportRequestError(\n e instanceof Error ? e : new Error(String(e)),\n {\n path: url,\n method: req.method ?? \"GET\",\n headers: Object.fromEntries(\n Object.entries(req.headers).map(([k, v]) => [\n k,\n Array.isArray(v) ? v.join(\", \") : String(v ?? \"\"),\n ]),\n ),\n },\n { routerKind: \"Pages Router\", routePath: match.route.pattern, routeType: \"route\" },\n );\n if (!res.headersSent) {\n res.statusCode = 500;\n res.end(\"Internal Server Error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n return true;\n }\n}\n"],"mappings":"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"}
|
|
@@ -22,6 +22,21 @@ type ServerActionInitiationSnapshot<TRouterState> = {
|
|
|
22
22
|
declare function isServerActionResult<TRoot>(value: unknown): value is AppBrowserServerActionResult<TRoot>;
|
|
23
23
|
declare function shouldClearClientNavigationCachesForServerActionResult<TRoot>(result: AppBrowserServerActionResult<TRoot> | TRoot, revalidation?: ServerActionRevalidationKind): boolean;
|
|
24
24
|
declare function parseServerActionRevalidationHeader(headers: Pick<Headers, "get">): ServerActionRevalidationKind;
|
|
25
|
+
type ServerActionRedirectLocation = {
|
|
26
|
+
href: string;
|
|
27
|
+
internal: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a server-action redirect target against the URL that initiated the
|
|
31
|
+
* action. Dot-relative redirects intentionally resolve as if the current route
|
|
32
|
+
* pathname were a directory, matching Next.js' `assignLocation()` behavior:
|
|
33
|
+
* `./subpage` from `/subdir` lands on `/subdir/subpage`, not `/subpage`.
|
|
34
|
+
*/
|
|
35
|
+
declare function resolveServerActionRedirectLocation(options: {
|
|
36
|
+
currentHref: string;
|
|
37
|
+
location: string;
|
|
38
|
+
origin: string;
|
|
39
|
+
}): ServerActionRedirectLocation;
|
|
25
40
|
declare function shouldScheduleRefreshForDiscardedServerAction(revalidation: ServerActionRevalidationKind): boolean;
|
|
26
41
|
declare function createServerActionInitiationSnapshot<TRouterState>(options: {
|
|
27
42
|
href: string;
|
|
@@ -40,5 +55,5 @@ type DiscardedServerActionRefreshSchedulerOptions = {
|
|
|
40
55
|
};
|
|
41
56
|
declare function createDiscardedServerActionRefreshScheduler(options: DiscardedServerActionRefreshSchedulerOptions): DiscardedServerActionRefreshScheduler;
|
|
42
57
|
//#endregion
|
|
43
|
-
export { AppBrowserServerActionResult, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
|
|
58
|
+
export { AppBrowserServerActionResult, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, resolveServerActionRedirectLocation, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
|
|
44
59
|
//# sourceMappingURL=app-browser-action-result.d.ts.map
|
|
@@ -31,6 +31,20 @@ function parseServerActionRevalidationHeader(headers) {
|
|
|
31
31
|
default: return "none";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a server-action redirect target against the URL that initiated the
|
|
36
|
+
* action. Dot-relative redirects intentionally resolve as if the current route
|
|
37
|
+
* pathname were a directory, matching Next.js' `assignLocation()` behavior:
|
|
38
|
+
* `./subpage` from `/subdir` lands on `/subdir/subpage`, not `/subpage`.
|
|
39
|
+
*/
|
|
40
|
+
function resolveServerActionRedirectLocation(options) {
|
|
41
|
+
const currentUrl = new URL(options.currentHref, options.origin);
|
|
42
|
+
const redirectUrl = options.location.startsWith(".") ? new URL(options.location, `${currentUrl.origin}${currentUrl.pathname.endsWith("/") ? currentUrl.pathname : `${currentUrl.pathname}/`}`) : new URL(options.location, currentUrl.href);
|
|
43
|
+
return {
|
|
44
|
+
href: redirectUrl.href,
|
|
45
|
+
internal: redirectUrl.origin === currentUrl.origin
|
|
46
|
+
};
|
|
47
|
+
}
|
|
34
48
|
function shouldScheduleRefreshForDiscardedServerAction(revalidation) {
|
|
35
49
|
return revalidation !== "none";
|
|
36
50
|
}
|
|
@@ -74,6 +88,6 @@ function createDiscardedServerActionRefreshScheduler(options) {
|
|
|
74
88
|
};
|
|
75
89
|
}
|
|
76
90
|
//#endregion
|
|
77
|
-
export { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
|
|
91
|
+
export { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, resolveServerActionRedirectLocation, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
|
|
78
92
|
|
|
79
93
|
//# sourceMappingURL=app-browser-action-result.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-browser-action-result.js","names":[],"sources":["../../src/server/app-browser-action-result.ts"],"sourcesContent":["import { ACTION_REVALIDATED_HEADER } from \"./headers.js\";\n\nexport type AppBrowserServerActionResult<TRoot> = {\n root?: TRoot;\n returnValue?: {\n ok: boolean;\n data: unknown;\n };\n};\n\nexport type ServerActionRevalidationKind = \"dynamicOnly\" | \"none\" | \"staticAndDynamic\";\n\nconst ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;\nconst ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;\n\ntype ServerActionInitiationSnapshot<TRouterState> = {\n href: string;\n navigationId: number;\n path: string;\n routerState: TRouterState;\n};\n\n/**\n * Structural discriminator: matches on `\"returnValue\"` or `\"root\"` keys.\n * This is safe because {@link AppWireElements} keys are prefixed (`route:`,\n * `slot:`, `__route`, etc.) and will never collide with these property names.\n * If the wire format ever adds a `\"root\"` key, this guard must be updated.\n */\nexport function isServerActionResult<TRoot>(\n value: unknown,\n): value is AppBrowserServerActionResult<TRoot> {\n return !!value && typeof value === \"object\" && (\"returnValue\" in value || \"root\" in value);\n}\n\nexport function shouldClearClientNavigationCachesForServerActionResult<TRoot>(\n result: AppBrowserServerActionResult<TRoot> | TRoot,\n revalidation: ServerActionRevalidationKind = \"none\",\n): boolean {\n if (revalidation !== \"none\") {\n return true;\n }\n\n if (!isServerActionResult<TRoot>(result)) {\n return true;\n }\n\n return result.root !== undefined;\n}\n\nexport function parseServerActionRevalidationHeader(\n headers: Pick<Headers, \"get\">,\n): ServerActionRevalidationKind {\n const value = headers.get(ACTION_REVALIDATED_HEADER);\n if (!value) return \"none\";\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return \"none\";\n }\n\n switch (parsed) {\n case ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC:\n return \"staticAndDynamic\";\n case ACTION_DID_REVALIDATE_DYNAMIC_ONLY:\n return \"dynamicOnly\";\n default:\n return \"none\";\n }\n}\n\nexport function shouldScheduleRefreshForDiscardedServerAction(\n revalidation: ServerActionRevalidationKind,\n): boolean {\n return revalidation !== \"none\";\n}\n\nexport function createServerActionInitiationSnapshot<TRouterState>(options: {\n href: string;\n navigationId: number;\n origin?: string;\n routerState: TRouterState;\n}): ServerActionInitiationSnapshot<TRouterState> {\n const url =\n options.origin === undefined ? new URL(options.href) : new URL(options.href, options.origin);\n return {\n href: url.href,\n navigationId: options.navigationId,\n path: url.pathname + url.search,\n routerState: options.routerState,\n };\n}\n\ntype DiscardedServerActionRefreshScheduler = {\n markNavigationSettled(): void;\n markNavigationStart(): void;\n schedule(): void;\n};\n\ntype DiscardedServerActionRefreshSchedulerOptions = {\n queueTask?: (callback: () => void) => void;\n runRefresh: () => void;\n};\n\nexport function createDiscardedServerActionRefreshScheduler(\n options: DiscardedServerActionRefreshSchedulerOptions,\n): DiscardedServerActionRefreshScheduler {\n const queueTask = options.queueTask ?? queueMicrotask;\n let activeNavigationCount = 0;\n let flushQueued = false;\n let refreshPending = false;\n\n function flush(): void {\n flushQueued = false;\n if (!refreshPending || activeNavigationCount > 0) return;\n\n refreshPending = false;\n options.runRefresh();\n }\n\n function queueFlush(): void {\n if (flushQueued) return;\n flushQueued = true;\n queueTask(flush);\n }\n\n return {\n markNavigationSettled() {\n if (activeNavigationCount > 0) {\n activeNavigationCount -= 1;\n }\n queueFlush();\n },\n markNavigationStart() {\n activeNavigationCount += 1;\n },\n schedule() {\n refreshPending = true;\n queueFlush();\n },\n };\n}\n"],"mappings":";;AAYA,MAAM,2CAA2C;AACjD,MAAM,qCAAqC;;;;;;;AAe3C,SAAgB,qBACd,OAC8C;CAC9C,OAAO,CAAC,CAAC,SAAS,OAAO,UAAU,aAAa,iBAAiB,SAAS,UAAU;;AAGtF,SAAgB,uDACd,QACA,eAA6C,QACpC;CACT,IAAI,iBAAiB,QACnB,OAAO;CAGT,IAAI,CAAC,qBAA4B,OAAO,EACtC,OAAO;CAGT,OAAO,OAAO,SAAS,KAAA;;AAGzB,SAAgB,oCACd,SAC8B;CAC9B,MAAM,QAAQ,QAAQ,IAAI,0BAA0B;CACpD,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,MAAM;SACpB;EACN,OAAO;;CAGT,QAAQ,QAAR;EACE,KAAK,0CACH,OAAO;EACT,KAAK,oCACH,OAAO;EACT,SACE,OAAO
|
|
1
|
+
{"version":3,"file":"app-browser-action-result.js","names":[],"sources":["../../src/server/app-browser-action-result.ts"],"sourcesContent":["import { ACTION_REVALIDATED_HEADER } from \"./headers.js\";\n\nexport type AppBrowserServerActionResult<TRoot> = {\n root?: TRoot;\n returnValue?: {\n ok: boolean;\n data: unknown;\n };\n};\n\nexport type ServerActionRevalidationKind = \"dynamicOnly\" | \"none\" | \"staticAndDynamic\";\n\nconst ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;\nconst ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;\n\ntype ServerActionInitiationSnapshot<TRouterState> = {\n href: string;\n navigationId: number;\n path: string;\n routerState: TRouterState;\n};\n\n/**\n * Structural discriminator: matches on `\"returnValue\"` or `\"root\"` keys.\n * This is safe because {@link AppWireElements} keys are prefixed (`route:`,\n * `slot:`, `__route`, etc.) and will never collide with these property names.\n * If the wire format ever adds a `\"root\"` key, this guard must be updated.\n */\nexport function isServerActionResult<TRoot>(\n value: unknown,\n): value is AppBrowserServerActionResult<TRoot> {\n return !!value && typeof value === \"object\" && (\"returnValue\" in value || \"root\" in value);\n}\n\nexport function shouldClearClientNavigationCachesForServerActionResult<TRoot>(\n result: AppBrowserServerActionResult<TRoot> | TRoot,\n revalidation: ServerActionRevalidationKind = \"none\",\n): boolean {\n if (revalidation !== \"none\") {\n return true;\n }\n\n if (!isServerActionResult<TRoot>(result)) {\n return true;\n }\n\n return result.root !== undefined;\n}\n\nexport function parseServerActionRevalidationHeader(\n headers: Pick<Headers, \"get\">,\n): ServerActionRevalidationKind {\n const value = headers.get(ACTION_REVALIDATED_HEADER);\n if (!value) return \"none\";\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return \"none\";\n }\n\n switch (parsed) {\n case ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC:\n return \"staticAndDynamic\";\n case ACTION_DID_REVALIDATE_DYNAMIC_ONLY:\n return \"dynamicOnly\";\n default:\n return \"none\";\n }\n}\n\ntype ServerActionRedirectLocation = {\n href: string;\n internal: boolean;\n};\n\n/**\n * Resolve a server-action redirect target against the URL that initiated the\n * action. Dot-relative redirects intentionally resolve as if the current route\n * pathname were a directory, matching Next.js' `assignLocation()` behavior:\n * `./subpage` from `/subdir` lands on `/subdir/subpage`, not `/subpage`.\n */\nexport function resolveServerActionRedirectLocation(options: {\n currentHref: string;\n location: string;\n origin: string;\n}): ServerActionRedirectLocation {\n const currentUrl = new URL(options.currentHref, options.origin);\n const redirectUrl = options.location.startsWith(\".\")\n ? new URL(\n options.location,\n `${currentUrl.origin}${currentUrl.pathname.endsWith(\"/\") ? currentUrl.pathname : `${currentUrl.pathname}/`}`,\n )\n : new URL(options.location, currentUrl.href);\n\n return {\n href: redirectUrl.href,\n internal: redirectUrl.origin === currentUrl.origin,\n };\n}\n\nexport function shouldScheduleRefreshForDiscardedServerAction(\n revalidation: ServerActionRevalidationKind,\n): boolean {\n return revalidation !== \"none\";\n}\n\nexport function createServerActionInitiationSnapshot<TRouterState>(options: {\n href: string;\n navigationId: number;\n origin?: string;\n routerState: TRouterState;\n}): ServerActionInitiationSnapshot<TRouterState> {\n const url =\n options.origin === undefined ? new URL(options.href) : new URL(options.href, options.origin);\n return {\n href: url.href,\n navigationId: options.navigationId,\n path: url.pathname + url.search,\n routerState: options.routerState,\n };\n}\n\ntype DiscardedServerActionRefreshScheduler = {\n markNavigationSettled(): void;\n markNavigationStart(): void;\n schedule(): void;\n};\n\ntype DiscardedServerActionRefreshSchedulerOptions = {\n queueTask?: (callback: () => void) => void;\n runRefresh: () => void;\n};\n\nexport function createDiscardedServerActionRefreshScheduler(\n options: DiscardedServerActionRefreshSchedulerOptions,\n): DiscardedServerActionRefreshScheduler {\n const queueTask = options.queueTask ?? queueMicrotask;\n let activeNavigationCount = 0;\n let flushQueued = false;\n let refreshPending = false;\n\n function flush(): void {\n flushQueued = false;\n if (!refreshPending || activeNavigationCount > 0) return;\n\n refreshPending = false;\n options.runRefresh();\n }\n\n function queueFlush(): void {\n if (flushQueued) return;\n flushQueued = true;\n queueTask(flush);\n }\n\n return {\n markNavigationSettled() {\n if (activeNavigationCount > 0) {\n activeNavigationCount -= 1;\n }\n queueFlush();\n },\n markNavigationStart() {\n activeNavigationCount += 1;\n },\n schedule() {\n refreshPending = true;\n queueFlush();\n },\n };\n}\n"],"mappings":";;AAYA,MAAM,2CAA2C;AACjD,MAAM,qCAAqC;;;;;;;AAe3C,SAAgB,qBACd,OAC8C;CAC9C,OAAO,CAAC,CAAC,SAAS,OAAO,UAAU,aAAa,iBAAiB,SAAS,UAAU;;AAGtF,SAAgB,uDACd,QACA,eAA6C,QACpC;CACT,IAAI,iBAAiB,QACnB,OAAO;CAGT,IAAI,CAAC,qBAA4B,OAAO,EACtC,OAAO;CAGT,OAAO,OAAO,SAAS,KAAA;;AAGzB,SAAgB,oCACd,SAC8B;CAC9B,MAAM,QAAQ,QAAQ,IAAI,0BAA0B;CACpD,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,MAAM;SACpB;EACN,OAAO;;CAGT,QAAQ,QAAR;EACE,KAAK,0CACH,OAAO;EACT,KAAK,oCACH,OAAO;EACT,SACE,OAAO;;;;;;;;;AAeb,SAAgB,oCAAoC,SAInB;CAC/B,MAAM,aAAa,IAAI,IAAI,QAAQ,aAAa,QAAQ,OAAO;CAC/D,MAAM,cAAc,QAAQ,SAAS,WAAW,IAAI,GAChD,IAAI,IACF,QAAQ,UACR,GAAG,WAAW,SAAS,WAAW,SAAS,SAAS,IAAI,GAAG,WAAW,WAAW,GAAG,WAAW,SAAS,KACzG,GACD,IAAI,IAAI,QAAQ,UAAU,WAAW,KAAK;CAE9C,OAAO;EACL,MAAM,YAAY;EAClB,UAAU,YAAY,WAAW,WAAW;EAC7C;;AAGH,SAAgB,8CACd,cACS;CACT,OAAO,iBAAiB;;AAG1B,SAAgB,qCAAmD,SAKlB;CAC/C,MAAM,MACJ,QAAQ,WAAW,KAAA,IAAY,IAAI,IAAI,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ,MAAM,QAAQ,OAAO;CAC9F,OAAO;EACL,MAAM,IAAI;EACV,cAAc,QAAQ;EACtB,MAAM,IAAI,WAAW,IAAI;EACzB,aAAa,QAAQ;EACtB;;AAcH,SAAgB,4CACd,SACuC;CACvC,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,wBAAwB;CAC5B,IAAI,cAAc;CAClB,IAAI,iBAAiB;CAErB,SAAS,QAAc;EACrB,cAAc;EACd,IAAI,CAAC,kBAAkB,wBAAwB,GAAG;EAElD,iBAAiB;EACjB,QAAQ,YAAY;;CAGtB,SAAS,aAAmB;EAC1B,IAAI,aAAa;EACjB,cAAc;EACd,UAAU,MAAM;;CAGlB,OAAO;EACL,wBAAwB;GACtB,IAAI,wBAAwB,GAC1B,yBAAyB;GAE3B,YAAY;;EAEd,sBAAsB;GACpB,yBAAyB;;EAE3B,WAAW;GACT,iBAAiB;GACjB,YAAY;;EAEf"}
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { stripBasePath } from "../utils/base-path.js";
|
|
2
2
|
import { ACTION_REDIRECT_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RSC_REDIRECT_HEADER } from "./headers.js";
|
|
3
3
|
import { DANGEROUS_URL_BLOCK_MESSAGE, isDangerousScheme } from "../shims/url-safety.js";
|
|
4
|
+
import { AppElementsWire } from "./app-elements-wire.js";
|
|
4
5
|
import { APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI } from "./app-rsc-render-mode.js";
|
|
6
|
+
import { getMountedSlotIdsHeader, resolveVisitedResponseInterceptionContext } from "./app-elements.js";
|
|
5
7
|
import { installWindowNext } from "../client/window-next.js";
|
|
6
8
|
import { scrollToHashTargetOnNextFrame } from "../shims/hash-scroll.js";
|
|
7
9
|
import { getNavigationRuntime, registerNavigationRuntimeBootstrap, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
|
|
8
10
|
import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
|
|
9
|
-
import { AppElementsWire } from "./app-elements-wire.js";
|
|
10
|
-
import { getMountedSlotIdsHeader, resolveVisitedResponseInterceptionContext } from "./app-elements.js";
|
|
11
11
|
import { resolveManifestNavigationInterceptionContext } from "./app-browser-interception-context.js";
|
|
12
12
|
import { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
|
|
13
13
|
import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, createRscRequestHeaders, createRscRequestUrl, getVinextRscCompatibilityId, resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
|
|
14
14
|
import { AppRouterContext } from "../shims/internal/app-router-context.js";
|
|
15
|
-
import {
|
|
15
|
+
import { consumeAppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
|
|
16
|
+
import { __basePath, appRouterInstance, commitClientNavigationState, consumePrefetchResponse, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, getClientNavigationRenderContext, getPrefetchCache, invalidatePrefetchCache, navigateClientSide, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, restoreRscResponse, setClientParams, setMountedSlotsHeader, setNavigationContext, setPendingPathname } from "../shims/navigation.js";
|
|
16
17
|
import { DevRecoveryBoundary, RedirectBoundary } from "../shims/error-boundary.js";
|
|
18
|
+
import { AppRouterScrollCommitProvider } from "../shims/app-router-scroll.js";
|
|
17
19
|
import { ElementsContext, Slot } from "../shims/slot.js";
|
|
18
20
|
import "../client/instrumentation-client.js";
|
|
19
|
-
import { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, shouldClearClientNavigationCachesForServerActionResult } from "./app-browser-action-result.js";
|
|
21
|
+
import { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, resolveServerActionRedirectLocation, shouldClearClientNavigationCachesForServerActionResult } from "./app-browser-action-result.js";
|
|
20
22
|
import { chunksToReadableStream, createProgressiveRscStream, getVinextBrowserGlobal } from "./app-browser-stream.js";
|
|
21
23
|
import { FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, isCacheRestorableAppPayloadMetadata, resolveInterceptionContextFromPreviousNextUrl, resolveServerActionRequestState } from "./app-browser-state.js";
|
|
22
24
|
import { clearHardNavigationLoopGuard, createAppBrowserNavigationController } from "./app-browser-navigation-controller.js";
|
|
@@ -28,6 +30,7 @@ import { devOnCaughtError, devOnUncaughtError, installDevErrorOverlay } from "./
|
|
|
28
30
|
import { throwOnServerActionNotFound } from "./server-action-not-found.js";
|
|
29
31
|
import { resolveRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
|
|
30
32
|
import { createOptimisticRouteTemplate, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, resolveOptimisticNavigationPayload } from "./app-optimistic-routing.js";
|
|
33
|
+
import { removeStylesheetLinksCoveredByInlineCss } from "./app-inline-css-client.js";
|
|
31
34
|
import { createElement, startTransition, use, useLayoutEffect, useRef, useState } from "react";
|
|
32
35
|
import { createFromFetch, createFromReadableStream, createTemporaryReferenceSet, encodeReply, setServerCallback } from "@vitejs/plugin-rsc/browser";
|
|
33
36
|
import { hydrateRoot } from "react-dom/client";
|
|
@@ -267,7 +270,7 @@ function createNavigationCommitEffect(options) {
|
|
|
267
270
|
commitClientNavigationState(navId);
|
|
268
271
|
};
|
|
269
272
|
}
|
|
270
|
-
async function renderNavigationPayload(payload, navigationSnapshot, targetHref, navId, historyUpdateMode, params, previousNextUrl, pendingRouterState, payloadOrigin, actionType = "navigate", operationLane = "navigation", traversalIntent = null) {
|
|
273
|
+
async function renderNavigationPayload(payload, navigationSnapshot, targetHref, navId, historyUpdateMode, params, previousNextUrl, pendingRouterState, payloadOrigin, actionType = "navigate", operationLane = "navigation", traversalIntent = null, scrollIntent = null) {
|
|
271
274
|
try {
|
|
272
275
|
return await browserNavigationController.renderNavigationPayload({
|
|
273
276
|
actionType,
|
|
@@ -283,6 +286,7 @@ async function renderNavigationPayload(payload, navigationSnapshot, targetHref,
|
|
|
283
286
|
params,
|
|
284
287
|
pendingRouterState,
|
|
285
288
|
previousNextUrl,
|
|
289
|
+
scrollIntent,
|
|
286
290
|
targetHistoryIndex: traversalIntent === null ? void 0 : traversalIntent.targetHistoryIndex,
|
|
287
291
|
targetHref,
|
|
288
292
|
navId
|
|
@@ -434,6 +438,7 @@ function BrowserRoot({ initialElements, initialNavigationSnapshot }) {
|
|
|
434
438
|
}, [setTreeStateValue]);
|
|
435
439
|
useLayoutEffect(() => {
|
|
436
440
|
setMountedSlotsHeader(getMountedSlotIdsHeader(stateRef.current.elements));
|
|
441
|
+
removeStylesheetLinksCoveredByInlineCss();
|
|
437
442
|
getNavigationRuntime()?.functions.pingVisibleLinks?.();
|
|
438
443
|
}, [treeState.elements]);
|
|
439
444
|
useLayoutEffect(() => {
|
|
@@ -449,9 +454,10 @@ function BrowserRoot({ initialElements, initialNavigationSnapshot }) {
|
|
|
449
454
|
resetKey: treeState.renderId,
|
|
450
455
|
onCatch: handleDevRecoveryBoundaryCatch
|
|
451
456
|
}, innerTree) : innerTree;
|
|
457
|
+
const scrollScopedTree = createElement(AppRouterScrollCommitProvider, { commitId: treeState.renderId }, committedTree);
|
|
452
458
|
const ClientNavigationRenderContext = getClientNavigationRenderContext();
|
|
453
|
-
if (!ClientNavigationRenderContext) return
|
|
454
|
-
return createElement(ClientNavigationRenderContext.Provider, { value: treeState.navigationSnapshot },
|
|
459
|
+
if (!ClientNavigationRenderContext) return scrollScopedTree;
|
|
460
|
+
return createElement(ClientNavigationRenderContext.Provider, { value: treeState.navigationSnapshot }, scrollScopedTree);
|
|
455
461
|
}
|
|
456
462
|
function restoreHydrationNavigationContext(pathname, searchParams, params) {
|
|
457
463
|
setNavigationContext({
|
|
@@ -582,15 +588,24 @@ function registerServerActionCallback() {
|
|
|
582
588
|
console.error(DANGEROUS_URL_BLOCK_MESSAGE);
|
|
583
589
|
return;
|
|
584
590
|
}
|
|
591
|
+
const historyUpdateMode = (fetchResponse.headers.get("x-action-redirect-type") ?? "push") === "push" ? "push" : "replace";
|
|
592
|
+
const hardNavigationMode = historyUpdateMode === "push" ? "assign" : "replace";
|
|
593
|
+
let redirectLocation;
|
|
585
594
|
try {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
595
|
+
redirectLocation = resolveServerActionRedirectLocation({
|
|
596
|
+
currentHref: actionInitiation.href,
|
|
597
|
+
location: actionRedirect,
|
|
598
|
+
origin: window.location.origin
|
|
599
|
+
});
|
|
600
|
+
} catch {
|
|
601
|
+
clearClientNavigationCaches();
|
|
602
|
+
browserNavigationController.performHardNavigation(actionRedirect, hardNavigationMode);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
591
605
|
clearClientNavigationCaches();
|
|
592
|
-
|
|
593
|
-
|
|
606
|
+
startTransition(() => {
|
|
607
|
+
navigateClientSide(redirectLocation.href, historyUpdateMode, true, true);
|
|
608
|
+
});
|
|
594
609
|
return;
|
|
595
610
|
}
|
|
596
611
|
if (resolveRscCompatibilityNavigationDecision({
|
|
@@ -654,7 +669,7 @@ function bootstrapHydration(rscStream) {
|
|
|
654
669
|
registerNavigationRuntimeFunctions({
|
|
655
670
|
clearNavigationCaches: clearClientNavigationCaches,
|
|
656
671
|
commitHashNavigation: commitHashOnlyNavigation,
|
|
657
|
-
navigate: async function navigateRsc(href, redirectDepth = 0, navigationKind = "navigate", historyUpdateMode, previousNextUrlOverride, programmaticTransition = false, traversalIntent) {
|
|
672
|
+
navigate: async function navigateRsc(href, redirectDepth = 0, navigationKind = "navigate", historyUpdateMode, previousNextUrlOverride, programmaticTransition = false, traversalIntent, scrollIntent) {
|
|
658
673
|
let pendingRouterState = null;
|
|
659
674
|
const navId = browserNavigationController.beginNavigation();
|
|
660
675
|
discardedServerActionRefreshScheduler.markNavigationStart();
|
|
@@ -667,6 +682,10 @@ function bootstrapHydration(rscStream) {
|
|
|
667
682
|
currentHistoryIndex: currentHistoryTraversalIndex,
|
|
668
683
|
historyState: window.history.state
|
|
669
684
|
}) : null;
|
|
685
|
+
const performHardNavigationForScrollIntent = (targetHref) => {
|
|
686
|
+
consumeAppRouterScrollIntent(scrollIntent ?? null);
|
|
687
|
+
return browserNavigationController.performHardNavigation(targetHref);
|
|
688
|
+
};
|
|
670
689
|
try {
|
|
671
690
|
const shouldUsePendingRouterState = programmaticTransition;
|
|
672
691
|
if (shouldUsePendingRouterState && hasBrowserRouterState()) pendingRouterState = beginPendingBrowserRouterState();
|
|
@@ -700,7 +719,7 @@ function bootstrapHydration(rscStream) {
|
|
|
700
719
|
responseUrl: cachedRoute.response.url
|
|
701
720
|
});
|
|
702
721
|
if (compatibilityDecision.kind === "hard-navigate") {
|
|
703
|
-
|
|
722
|
+
performHardNavigationForScrollIntent(compatibilityDecision.hardNavigationTarget);
|
|
704
723
|
return;
|
|
705
724
|
}
|
|
706
725
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
@@ -708,7 +727,7 @@ function bootstrapHydration(rscStream) {
|
|
|
708
727
|
const cachedNavigationSnapshot = createClientNavigationRenderSnapshot(currentHref, cachedParams);
|
|
709
728
|
const cachedPayload = decodeAppElementsPromise(createFromFetch(Promise.resolve(restoreRscResponse(cachedRoute.response))));
|
|
710
729
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
711
|
-
await renderNavigationPayload(cachedPayload, cachedNavigationSnapshot, currentHref, navId, currentHistoryMode, cachedParams, requestPreviousNextUrl, detachedNavigationCommits ? null : pendingRouterState, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, toActionType(navigationKind), toOperationLane(navigationKind), activeTraversalIntent);
|
|
730
|
+
await renderNavigationPayload(cachedPayload, cachedNavigationSnapshot, currentHref, navId, currentHistoryMode, cachedParams, requestPreviousNextUrl, detachedNavigationCommits ? null : pendingRouterState, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, toActionType(navigationKind), toOperationLane(navigationKind), activeTraversalIntent, scrollIntent);
|
|
712
731
|
return;
|
|
713
732
|
}
|
|
714
733
|
let navResponse;
|
|
@@ -740,7 +759,7 @@ function bootstrapHydration(rscStream) {
|
|
|
740
759
|
if (optimisticPayload !== null) {
|
|
741
760
|
detachedNavigationCommits = true;
|
|
742
761
|
const optimisticNavigationSnapshot = createClientNavigationRenderSnapshot(currentHref, optimisticPayload.params);
|
|
743
|
-
renderNavigationPayload(Promise.resolve(optimisticPayload.elements), optimisticNavigationSnapshot, currentHref, navId, currentHistoryMode, optimisticPayload.params, requestPreviousNextUrl, null, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, toActionType(navigationKind), toOperationLane(navigationKind), activeTraversalIntent).catch((error) => {
|
|
762
|
+
renderNavigationPayload(Promise.resolve(optimisticPayload.elements), optimisticNavigationSnapshot, currentHref, navId, currentHistoryMode, optimisticPayload.params, requestPreviousNextUrl, null, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, toActionType(navigationKind), toOperationLane(navigationKind), activeTraversalIntent, scrollIntent).catch((error) => {
|
|
744
763
|
if (browserNavigationController.isCurrentNavigation(navId)) console.error("[vinext] Optimistic RSC navigation error:", error);
|
|
745
764
|
});
|
|
746
765
|
}
|
|
@@ -753,8 +772,7 @@ function bootstrapHydration(rscStream) {
|
|
|
753
772
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
754
773
|
const isRscResponse = (navResponse.headers.get("content-type") ?? "").startsWith("text/x-component");
|
|
755
774
|
if (!navResponse.ok || !isRscResponse || !navResponse.body) {
|
|
756
|
-
|
|
757
|
-
browserNavigationController.performHardNavigation(resolveHardNavigationTargetFromRscResponse(responseUrl, currentHref, window.location.origin));
|
|
775
|
+
performHardNavigationForScrollIntent(resolveHardNavigationTargetFromRscResponse(navResponseUrl ?? navResponse.url, currentHref, window.location.origin));
|
|
758
776
|
return;
|
|
759
777
|
}
|
|
760
778
|
const compatibilityDecision = resolveRscCompatibilityNavigationDecision({
|
|
@@ -765,7 +783,7 @@ function bootstrapHydration(rscStream) {
|
|
|
765
783
|
responseUrl: navResponseUrl ?? navResponse.url
|
|
766
784
|
});
|
|
767
785
|
if (compatibilityDecision.kind === "hard-navigate") {
|
|
768
|
-
|
|
786
|
+
performHardNavigationForScrollIntent(compatibilityDecision.hardNavigationTarget);
|
|
769
787
|
return;
|
|
770
788
|
}
|
|
771
789
|
const redirectDecision = resolveRscRedirectLifecycleHop({
|
|
@@ -778,7 +796,7 @@ function bootstrapHydration(rscStream) {
|
|
|
778
796
|
});
|
|
779
797
|
if (redirectDecision.kind === "terminal-hard-navigation") {
|
|
780
798
|
if (redirectDecision.reason === "maxRedirectsExceeded") console.error("[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.");
|
|
781
|
-
|
|
799
|
+
performHardNavigationForScrollIntent(redirectDecision.href);
|
|
782
800
|
return;
|
|
783
801
|
}
|
|
784
802
|
if (redirectDecision.kind === "follow") {
|
|
@@ -793,12 +811,12 @@ function bootstrapHydration(rscStream) {
|
|
|
793
811
|
navResponse.body?.cancel().catch(() => {});
|
|
794
812
|
const resolvedTarget = new URL(flightRedirectTarget, window.location.origin);
|
|
795
813
|
if (resolvedTarget.origin !== window.location.origin) {
|
|
796
|
-
|
|
814
|
+
performHardNavigationForScrollIntent(resolvedTarget.href);
|
|
797
815
|
return;
|
|
798
816
|
}
|
|
799
817
|
if (redirectCount >= 10) {
|
|
800
818
|
console.error("[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.");
|
|
801
|
-
|
|
819
|
+
performHardNavigationForScrollIntent(resolvedTarget.href);
|
|
802
820
|
return;
|
|
803
821
|
}
|
|
804
822
|
currentHref = `${resolvedTarget.pathname}${resolvedTarget.search}${resolvedTarget.hash}`;
|
|
@@ -818,7 +836,7 @@ function bootstrapHydration(rscStream) {
|
|
|
818
836
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
819
837
|
const rscPayload = decodeAppElementsPromise(createFromFetch(Promise.resolve(reactResponse)));
|
|
820
838
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
821
|
-
if (await renderNavigationPayload(rscPayload, navigationSnapshot, currentHref, navId, currentHistoryMode, navParams, requestPreviousNextUrl, detachedNavigationCommits ? null : pendingRouterState, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, toActionType(navigationKind), toOperationLane(navigationKind), activeTraversalIntent) !== "committed") return;
|
|
839
|
+
if (await renderNavigationPayload(rscPayload, navigationSnapshot, currentHref, navId, currentHistoryMode, navParams, requestPreviousNextUrl, detachedNavigationCommits ? null : pendingRouterState, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, toActionType(navigationKind), toOperationLane(navigationKind), activeTraversalIntent, scrollIntent) !== "committed") return;
|
|
822
840
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
823
841
|
const resolvedElements = await rscPayload;
|
|
824
842
|
const metadata = AppElementsWire.readMetadata(resolvedElements);
|
|
@@ -826,14 +844,15 @@ function bootstrapHydration(rscStream) {
|
|
|
826
844
|
cacheBufferPromise.catch(() => {});
|
|
827
845
|
return;
|
|
828
846
|
}
|
|
829
|
-
|
|
830
|
-
|
|
847
|
+
cacheBufferPromise.then((cacheBuffer) => {
|
|
848
|
+
storeVisitedResponseSnapshot(rscUrl, resolveVisitedResponseInterceptionContext(requestInterceptionContext, metadata.interceptionContext), createCachedRscResponseSnapshot(navResponse, cacheBuffer, navResponseUrl), navParams);
|
|
849
|
+
}).catch(() => {});
|
|
831
850
|
return;
|
|
832
851
|
}
|
|
833
852
|
} catch (error) {
|
|
834
853
|
if (!browserNavigationController.isCurrentNavigation(navId)) return;
|
|
835
854
|
if (!isPageUnloading) console.error("[vinext] RSC navigation error:", error);
|
|
836
|
-
|
|
855
|
+
performHardNavigationForScrollIntent(currentHref);
|
|
837
856
|
} finally {
|
|
838
857
|
browserNavigationController.finalizeNavigation(navId, pendingRouterState);
|
|
839
858
|
discardedServerActionRefreshScheduler.markNavigationSettled();
|