@wular/pnext 0.0.1
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 +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { isNotFoundError, isRedirectError } from '../api/navigation';
|
|
2
|
+
import { getWorkUnit, runWithRequest } from '../request/context';
|
|
3
|
+
import { nextResponseMeta, toNextRequest } from '../api/server';
|
|
4
|
+
import { reportRequestError } from '../extensions';
|
|
5
|
+
import type { NextRequest, RouteManifestEntry, RouteParamValue } from '../types';
|
|
6
|
+
|
|
7
|
+
const routeMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const;
|
|
8
|
+
|
|
9
|
+
type RouteMethod = (typeof routeMethods)[number];
|
|
10
|
+
|
|
11
|
+
// Methods that are valid HTTP verbs but may be unimplemented on a route (405),
|
|
12
|
+
// vs. tokens that are not HTTP methods at all (400). Next answers 400 for the
|
|
13
|
+
// latter (e.g. a `HEADER` request) and 405 for a real-but-missing verb.
|
|
14
|
+
const httpMethods = new Set([
|
|
15
|
+
'GET',
|
|
16
|
+
'HEAD',
|
|
17
|
+
'POST',
|
|
18
|
+
'PUT',
|
|
19
|
+
'PATCH',
|
|
20
|
+
'DELETE',
|
|
21
|
+
'OPTIONS',
|
|
22
|
+
'CONNECT',
|
|
23
|
+
'TRACE',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
interface RouteHandlerContext {
|
|
27
|
+
params?: Promise<Record<string, RouteParamValue>>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface HandleRouteOptions {
|
|
31
|
+
/** Absolute route file path, used in the "no response returned" CLI error. */
|
|
32
|
+
routeFile?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RouteHandlerModule {
|
|
36
|
+
params?: () => Record<string, RouteParamValue>[] | Promise<Record<string, RouteParamValue>[]>;
|
|
37
|
+
GET?: RouteHandler;
|
|
38
|
+
HEAD?: RouteHandler;
|
|
39
|
+
POST?: RouteHandler;
|
|
40
|
+
PUT?: RouteHandler;
|
|
41
|
+
PATCH?: RouteHandler;
|
|
42
|
+
DELETE?: RouteHandler;
|
|
43
|
+
OPTIONS?: RouteHandler;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type RouteHandler = (
|
|
47
|
+
request: NextRequest,
|
|
48
|
+
context: RouteHandlerContext,
|
|
49
|
+
) => Response | undefined | void | Promise<Response | undefined | void>;
|
|
50
|
+
|
|
51
|
+
export async function handleRouteModule(
|
|
52
|
+
module: RouteHandlerModule,
|
|
53
|
+
request: Request,
|
|
54
|
+
params: Record<string, RouteParamValue>,
|
|
55
|
+
options: HandleRouteOptions = {},
|
|
56
|
+
) {
|
|
57
|
+
// Response-headers channel for the handler's request scope: APIs like
|
|
58
|
+
// draftMode().enable() append set-cookie here while the handler runs, and
|
|
59
|
+
// the headers are merged onto whatever response goes out — including the
|
|
60
|
+
// synthesized redirect response when the handler throws redirect().
|
|
61
|
+
const responseHeaders = new Headers();
|
|
62
|
+
const method = request.method.toUpperCase();
|
|
63
|
+
try {
|
|
64
|
+
// A request method that is not a valid HTTP verb is a bad request, not an
|
|
65
|
+
// unimplemented one (Next returns 400 with an empty body).
|
|
66
|
+
if (!httpMethods.has(method)) return new Response(null, { status: 400 });
|
|
67
|
+
|
|
68
|
+
const handler = routeMethods.includes(method as RouteMethod)
|
|
69
|
+
? module[method as RouteMethod]
|
|
70
|
+
: undefined;
|
|
71
|
+
|
|
72
|
+
if (!handler) {
|
|
73
|
+
if (method === 'HEAD' && module.GET) {
|
|
74
|
+
// Auto-HEAD from GET: run GET, validate it, then drop the body.
|
|
75
|
+
const response = finalizeResponse(
|
|
76
|
+
await callHandler(module.GET, request, params, responseHeaders),
|
|
77
|
+
request,
|
|
78
|
+
responseHeaders,
|
|
79
|
+
options,
|
|
80
|
+
);
|
|
81
|
+
return new Response(null, { status: response.status, headers: response.headers });
|
|
82
|
+
}
|
|
83
|
+
return missingMethodResponse(module, method);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return finalizeResponse(
|
|
87
|
+
await callHandler(handler, request, params, responseHeaders),
|
|
88
|
+
request,
|
|
89
|
+
responseHeaders,
|
|
90
|
+
options,
|
|
91
|
+
);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (isRedirectError(error)) {
|
|
94
|
+
// POST submissions get a 303 (Next's behavior for redirect() and
|
|
95
|
+
// permanentRedirect() thrown in route handlers): the browser follows
|
|
96
|
+
// with a GET instead of replaying the POST against the target.
|
|
97
|
+
const status = method === 'POST' ? 303 : error.status;
|
|
98
|
+
return withResponseHeaders(
|
|
99
|
+
new Response(null, { status, headers: { location: error.location } }),
|
|
100
|
+
responseHeaders,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
// notFound() thrown inside a handler is a 404 with an empty body.
|
|
104
|
+
if (isNotFoundError(error)) {
|
|
105
|
+
return withResponseHeaders(new Response(null, { status: 404 }), responseHeaders);
|
|
106
|
+
}
|
|
107
|
+
// Any other throw is a 500 with an empty body. Report it through the error
|
|
108
|
+
// funnel (onRequestError) here since core no longer sees it as uncaught.
|
|
109
|
+
await reportRequestError(
|
|
110
|
+
error,
|
|
111
|
+
{ method, url: request.url, headers: request.headers },
|
|
112
|
+
{ phase: 'handler', routeKind: 'route-handler', ...(getWorkUnit()?.responseHints ?? {}) },
|
|
113
|
+
);
|
|
114
|
+
console.error(error);
|
|
115
|
+
return new Response(null, { status: 500 });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validate the handler's return value and apply the scope's collected headers.
|
|
121
|
+
* A handler must return a Response; `undefined` or a `NextResponse.next()`
|
|
122
|
+
* result is a 500 (with Next's contract-violation CLI error) rather than a
|
|
123
|
+
* passthrough.
|
|
124
|
+
*/
|
|
125
|
+
function finalizeResponse(
|
|
126
|
+
response: Response | undefined | void,
|
|
127
|
+
request: Request,
|
|
128
|
+
responseHeaders: Headers,
|
|
129
|
+
options: HandleRouteOptions,
|
|
130
|
+
) {
|
|
131
|
+
const method = request.method.toUpperCase();
|
|
132
|
+
if (!response) {
|
|
133
|
+
const file = options.routeFile ?? '<route>';
|
|
134
|
+
console.error(
|
|
135
|
+
`No response is returned from route handler '${file}'. Expected a Response object but ` +
|
|
136
|
+
`received '${typeof response}' (method: ${method}, url: ${request.url}). Ensure you ` +
|
|
137
|
+
'return a `Response` or a `NextResponse` in all branches of your handler.',
|
|
138
|
+
);
|
|
139
|
+
return new Response(null, { status: 500 });
|
|
140
|
+
}
|
|
141
|
+
if (nextResponseMeta(response)?.kind === 'next') {
|
|
142
|
+
console.error('https://nextjs.org/docs/messages/next-response-next-in-app-route-handler');
|
|
143
|
+
return new Response(null, { status: 500 });
|
|
144
|
+
}
|
|
145
|
+
return withResponseHeaders(response, responseHeaders);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function callHandler(
|
|
149
|
+
handler: RouteHandler,
|
|
150
|
+
request: Request,
|
|
151
|
+
params: Record<string, RouteParamValue>,
|
|
152
|
+
responseHeaders: Headers,
|
|
153
|
+
) {
|
|
154
|
+
const nextRequest = toNextRequest(request);
|
|
155
|
+
// Static routes (no dynamic segments) get no `params` in context — Next
|
|
156
|
+
// omits it, so `context.params` is `undefined` rather than a promise of `{}`.
|
|
157
|
+
const context: RouteHandlerContext =
|
|
158
|
+
Object.keys(params).length > 0 ? { params: Promise.resolve(params) } : {};
|
|
159
|
+
return runWithRequest(nextRequest, () => handler(nextRequest, context), params, {
|
|
160
|
+
responseHeaders,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Merge scope-collected headers (set-cookie appends, everything else sets) onto the response. */
|
|
165
|
+
function withResponseHeaders(response: Response, extra: Headers) {
|
|
166
|
+
const withSetCookie = extra as Headers & { getSetCookie?: () => string[] };
|
|
167
|
+
const scopeCookies = withSetCookie.getSetCookie?.() ?? [];
|
|
168
|
+
let hasOthers = false;
|
|
169
|
+
extra.forEach((_, key) => {
|
|
170
|
+
if (key !== 'set-cookie') hasOthers = true;
|
|
171
|
+
});
|
|
172
|
+
if (scopeCookies.length === 0 && !hasOthers) return response;
|
|
173
|
+
|
|
174
|
+
const apply = (headers: Headers) => {
|
|
175
|
+
extra.forEach((value, key) => {
|
|
176
|
+
if (key !== 'set-cookie') headers.set(key, value);
|
|
177
|
+
});
|
|
178
|
+
// Cookie-jar merge (Next's ResponseCookies semantics): scope cookies
|
|
179
|
+
// (cookies().set during the handler) first, the response's own Set-Cookie
|
|
180
|
+
// headers override same-name entries, and every cookie is normalized with
|
|
181
|
+
// a default Path=/.
|
|
182
|
+
const responseCookies =
|
|
183
|
+
(headers as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? [];
|
|
184
|
+
const jar = new Map<string, string>();
|
|
185
|
+
for (const cookie of [...scopeCookies, ...responseCookies]) {
|
|
186
|
+
jar.set(cookieName(cookie), normalizeSetCookie(cookie));
|
|
187
|
+
}
|
|
188
|
+
headers.delete('set-cookie');
|
|
189
|
+
for (const cookie of jar.values()) headers.append('set-cookie', cookie);
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
apply(response.headers);
|
|
193
|
+
return response;
|
|
194
|
+
} catch {
|
|
195
|
+
// Immutable headers (e.g. a Response forwarded from fetch): re-wrap.
|
|
196
|
+
const headers = new Headers(response.headers);
|
|
197
|
+
apply(headers);
|
|
198
|
+
return new Response(response.body, {
|
|
199
|
+
status: response.status,
|
|
200
|
+
statusText: response.statusText,
|
|
201
|
+
headers,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function cookieName(setCookie: string) {
|
|
207
|
+
return (setCookie.split('=')[0] ?? '').trim();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeSetCookie(setCookie: string) {
|
|
211
|
+
return /;\s*path=/i.test(setCookie) ? setCookie : `${setCookie}; Path=/`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function staticRouteParams(route: RouteManifestEntry, module: RouteHandlerModule) {
|
|
215
|
+
if (typeof module.GET !== 'function') return [];
|
|
216
|
+
// `dynamic = 'force-static'` and `dynamic = 'error'` both evaluate a static
|
|
217
|
+
// request; the latter lets request APIs fail at their call sites. A
|
|
218
|
+
// `revalidate` export opts a request-independent GET into static/ISR output.
|
|
219
|
+
const config = route.segmentConfig;
|
|
220
|
+
const optedStatic =
|
|
221
|
+
config?.dynamic === 'force-static' ||
|
|
222
|
+
config?.dynamic === 'error' ||
|
|
223
|
+
config?.revalidate === false ||
|
|
224
|
+
(typeof config?.revalidate === 'number' && config.revalidate > 0);
|
|
225
|
+
if (route.hasStaticParams) return module.params?.() ?? [];
|
|
226
|
+
if (route.usesRequest && !optedStatic) return [];
|
|
227
|
+
return route.mode === 'static' || optedStatic ? [{}] : [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function routeParamsFromPath(
|
|
231
|
+
route: Pick<RouteManifestEntry, 'catchAll' | 'params' | 'pattern'>,
|
|
232
|
+
pathname: string,
|
|
233
|
+
) {
|
|
234
|
+
const match = new RegExp(`^${route.pattern}$`).exec(pathname);
|
|
235
|
+
const params: Record<string, RouteParamValue> = {};
|
|
236
|
+
if (!match) return params;
|
|
237
|
+
|
|
238
|
+
for (let index = 0; index < route.params.length; index += 1) {
|
|
239
|
+
params[route.params[index]!] = safeDecode(match[index + 1] ?? '');
|
|
240
|
+
}
|
|
241
|
+
if (route.catchAll) {
|
|
242
|
+
params[route.catchAll] = (match[route.params.length + 1] ?? '')
|
|
243
|
+
.split('/')
|
|
244
|
+
.filter(Boolean)
|
|
245
|
+
.map(safeDecode);
|
|
246
|
+
}
|
|
247
|
+
return params;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function missingMethodResponse(module: RouteHandlerModule, method: string) {
|
|
251
|
+
const headers = { Allow: allowedMethods(module).join(', ') };
|
|
252
|
+
if (method === 'OPTIONS') return new Response(null, { status: 204, headers });
|
|
253
|
+
return new Response(null, { status: 405, headers });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function allowedMethods(module: RouteHandlerModule) {
|
|
257
|
+
const allowed = new Set(routeMethods.filter(method => typeof module[method] === 'function'));
|
|
258
|
+
// GET implies auto-HEAD; every route gets auto-OPTIONS. The Allow header is
|
|
259
|
+
// sorted (Next emits e.g. `OPTIONS, POST`).
|
|
260
|
+
if (allowed.has('GET')) allowed.add('HEAD');
|
|
261
|
+
allowed.add('OPTIONS');
|
|
262
|
+
return [...allowed].sort();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function safeDecode(value: string) {
|
|
266
|
+
try {
|
|
267
|
+
return decodeURIComponent(value);
|
|
268
|
+
} catch {
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { RouteParamValue } from '../types';
|
|
2
|
+
|
|
3
|
+
export type SearchValue = string | number | boolean | null | undefined;
|
|
4
|
+
export type SearchInput = URLSearchParams | Record<string, SearchValue | SearchValue[]>;
|
|
5
|
+
|
|
6
|
+
export interface HrefParts {
|
|
7
|
+
params?: Record<string, RouteParamValue>;
|
|
8
|
+
search?: SearchInput;
|
|
9
|
+
hash?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function routeHref(route: string, options: HrefParts = {}) {
|
|
13
|
+
const path = applyTrailingSlash(fillParams(route, options.params ?? {}));
|
|
14
|
+
const search = searchString(options.search);
|
|
15
|
+
const hash = hashString(options.hash);
|
|
16
|
+
return `${path}${search}${hash}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare global {
|
|
20
|
+
interface Window {
|
|
21
|
+
__PNEXT_TRAILING_SLASH__?: boolean;
|
|
22
|
+
}
|
|
23
|
+
var __PNEXT_TRAILING_SLASH__: boolean | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// With `trailingSlash: true` the server 308s bare page URLs to their slashed
|
|
27
|
+
// form; Links render the canonical (slashed) href directly, like Next. Set
|
|
28
|
+
// once per process by the server runtime from the resolved config. On the
|
|
29
|
+
// client the value is read from the injected window global (register-render's
|
|
30
|
+
// trailingSlashScript), mirroring basePath / skipTrailingSlash.
|
|
31
|
+
let trailingSlashUrls = false;
|
|
32
|
+
|
|
33
|
+
export function setTrailingSlashUrls(enabled: boolean) {
|
|
34
|
+
trailingSlashUrls = enabled;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isTrailingSlashEnabled(): boolean {
|
|
38
|
+
if (trailingSlashUrls) return true;
|
|
39
|
+
if (process.browser || typeof window !== 'undefined') return window.__PNEXT_TRAILING_SLASH__ === true;
|
|
40
|
+
return globalThis.__PNEXT_TRAILING_SLASH__ === true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getTrailingSlashUrls() {
|
|
44
|
+
return isTrailingSlashEnabled();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The configured basePath ('' when unset). Set once per process by the server
|
|
48
|
+
// runtime from the resolved config (registerServerRuntime), mirroring the
|
|
49
|
+
// trailing-slash seam. Core render code (metadata) reads it through
|
|
50
|
+
// withBasePath so file-convention asset hrefs (og-image, manifest) carry the
|
|
51
|
+
// prefix without a compat/next.config import in core.
|
|
52
|
+
let basePathPrefix = '';
|
|
53
|
+
|
|
54
|
+
export function setBasePathPrefix(value: string) {
|
|
55
|
+
basePathPrefix = value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getBasePathPrefix() {
|
|
59
|
+
return basePathPrefix;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Prefix a root-relative in-app path with the configured basePath (Next's
|
|
64
|
+
* addBasePath). Absolute/protocol-relative URLs, non-root-relative strings, and
|
|
65
|
+
* paths already under the basePath are returned unchanged. A no-basePath app is
|
|
66
|
+
* always a no-op. Any query/hash on the path rides along untouched.
|
|
67
|
+
*/
|
|
68
|
+
export function withBasePath(path: string) {
|
|
69
|
+
const base = basePathPrefix;
|
|
70
|
+
if (!base) return path;
|
|
71
|
+
if (!path.startsWith('/') || path.startsWith('//')) return path;
|
|
72
|
+
if (path === base || path.startsWith(`${base}/`)) return path;
|
|
73
|
+
return `${base}${path}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function applyTrailingSlash(pathname: string) {
|
|
77
|
+
const [, path = '', rest = ''] = /^([^?#]*)([\s\S]*)$/.exec(pathname) ?? [];
|
|
78
|
+
if (!path.startsWith('/')) return pathname;
|
|
79
|
+
if (trailingSlashUrls) {
|
|
80
|
+
// Add the canonical trailing slash (skip root, already-slashed, and files).
|
|
81
|
+
if (path === '/' || path.endsWith('/') || /\.[^/]+$/.test(path)) return pathname;
|
|
82
|
+
return `${path}/${rest}`;
|
|
83
|
+
}
|
|
84
|
+
// trailingSlash:false — Next strips a trailing slash from hrefs so the anchor
|
|
85
|
+
// matches the canonical (unslashed) form and avoids a 308 on click. Root and
|
|
86
|
+
// file paths (`/a.txt/` is not a thing) are left alone.
|
|
87
|
+
if (path === '/' || !path.endsWith('/') || /\.[^/]+\/$/.test(path)) return pathname;
|
|
88
|
+
return `${path.replace(/\/+$/, '')}${rest}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function fillParams(route: string, params: Record<string, RouteParamValue>) {
|
|
92
|
+
const filled = route
|
|
93
|
+
// Optional catch-all `[[...slug]]` must be matched before the plain
|
|
94
|
+
// catch-all form so its doubled brackets aren't mis-parsed.
|
|
95
|
+
.replace(/\[\[\.\.\.([^\]]+)\]\]/g, (_match, key: string) => encodeParam(params[key], true))
|
|
96
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, (_match, key: string) => encodeParam(params[key], true))
|
|
97
|
+
.replace(/\[([^\]]+)\]/g, (_match, key: string) => encodeParam(params[key], false));
|
|
98
|
+
// An absent optional catch-all (`[[...slug]]`) leaves a dangling `/`; drop it
|
|
99
|
+
// so /docs/:slug* with no params yields /docs, not /docs/.
|
|
100
|
+
return collapseTrailingSlash(filled);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function collapseTrailingSlash(pathname: string) {
|
|
104
|
+
if (pathname.length <= 1) return pathname;
|
|
105
|
+
return pathname.replace(/\/+$/, '') || '/';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function encodeParam(value: RouteParamValue | undefined, catchAll: boolean) {
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
const encoded = value.map(segment => encodeURIComponent(segment));
|
|
111
|
+
return catchAll ? encoded.join('/') : encoded[0] ?? '';
|
|
112
|
+
}
|
|
113
|
+
return encodeURIComponent(value ?? '');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function searchString(search: SearchInput | undefined) {
|
|
117
|
+
if (!search) return '';
|
|
118
|
+
const params = search instanceof URLSearchParams ? search : objectSearchParams(search);
|
|
119
|
+
const value = params.toString();
|
|
120
|
+
return value ? `?${value}` : '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function objectSearchParams(search: Record<string, SearchValue | SearchValue[]>) {
|
|
124
|
+
const params = new URLSearchParams();
|
|
125
|
+
for (const [key, value] of Object.entries(search)) {
|
|
126
|
+
const values = Array.isArray(value) ? value : [value];
|
|
127
|
+
for (const item of values) {
|
|
128
|
+
if (item == null) continue;
|
|
129
|
+
params.append(key, String(item));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return params;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hashString(hash: string | undefined) {
|
|
136
|
+
if (!hash) return '';
|
|
137
|
+
return hash.startsWith('#') ? hash : `#${hash}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Malformed percent-encoding in the request path (e.g. `/%2`, a lone `%` or an
|
|
142
|
+
* out-of-range escape) is a 400 "Bad Request" before any routing, matching
|
|
143
|
+
* Next. Returns the response when the path cannot be decoded, else null.
|
|
144
|
+
*/
|
|
145
|
+
export function malformedUrlResponse(request: Request): Response | null {
|
|
146
|
+
let pathname: string;
|
|
147
|
+
try {
|
|
148
|
+
pathname = new URL(request.url).pathname;
|
|
149
|
+
} catch {
|
|
150
|
+
return new Response('Bad Request', { status: 400 });
|
|
151
|
+
}
|
|
152
|
+
if (!pathname.includes('%')) return null;
|
|
153
|
+
try {
|
|
154
|
+
decodeURIComponent(pathname);
|
|
155
|
+
return null;
|
|
156
|
+
} catch {
|
|
157
|
+
return new Response('Bad Request', { status: 400 });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The canonical form of `pathname` under the given `trailingSlash` setting:
|
|
163
|
+
* with it on, a bare page path gains a trailing slash; with it off, a slashed
|
|
164
|
+
* path loses it. Root (`/`) and file-ish paths (an extension in the last
|
|
165
|
+
* segment) are always left untouched. Returns the pathname unchanged when it is
|
|
166
|
+
* already canonical.
|
|
167
|
+
*/
|
|
168
|
+
export function canonicalTrailingSlashPath(pathname: string, trailingSlash: boolean): string {
|
|
169
|
+
if (pathname === '/' || pathname === '') return pathname;
|
|
170
|
+
if (/\.[^/]+$/.test(pathname)) return pathname;
|
|
171
|
+
const slashed = pathname.endsWith('/');
|
|
172
|
+
if (trailingSlash ? slashed : !slashed) return pathname;
|
|
173
|
+
return trailingSlash
|
|
174
|
+
? `${pathname.replace(/\/+$/, '')}/`
|
|
175
|
+
: pathname.replace(/\/+$/, '') || '/';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Next-style trailing-slash normalization: with `trailingSlash: true` page
|
|
180
|
+
* URLs 308-redirect to their slashed form; with it off (the default) slashed
|
|
181
|
+
* URLs redirect to the bare form. File-ish paths (extensions) and internal
|
|
182
|
+
* paths are untouched. Returns null when the URL is already canonical.
|
|
183
|
+
*/
|
|
184
|
+
export function trailingSlashRedirect(
|
|
185
|
+
config: { trailingSlash?: boolean; skipTrailingSlashRedirect?: boolean },
|
|
186
|
+
url: URL,
|
|
187
|
+
method: string,
|
|
188
|
+
): Response | null {
|
|
189
|
+
// skipTrailingSlashRedirect: the app serves both slashed and unslashed URLs
|
|
190
|
+
// as-is, so no canonical redirect is ever emitted (route matching already
|
|
191
|
+
// tolerates a trailing slash via normalizePathname).
|
|
192
|
+
if (config.skipTrailingSlashRedirect) return null;
|
|
193
|
+
if (method !== 'GET' && method !== 'HEAD') return null;
|
|
194
|
+
const pathname = url.pathname;
|
|
195
|
+
if (pathname === '/' || pathname === '') return null;
|
|
196
|
+
if (pathname.startsWith('/__pnext') || pathname.startsWith('/assets/')) return null;
|
|
197
|
+
const target = canonicalTrailingSlashPath(pathname, Boolean(config.trailingSlash));
|
|
198
|
+
if (target === pathname) return null;
|
|
199
|
+
return new Response(null, {
|
|
200
|
+
status: 308,
|
|
201
|
+
headers: { location: `${target}${url.search}` },
|
|
202
|
+
});
|
|
203
|
+
}
|