vinext 1.0.0-beta.1 → 1.0.0-beta.3
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 +10 -10
- package/dist/build/client-build-config.js +8 -1
- package/dist/build/report.d.ts +8 -1
- package/dist/build/report.js +12 -2
- package/dist/check.js +19 -2
- package/dist/cli.js +3 -2
- package/dist/config/config-matchers.d.ts +13 -37
- package/dist/config/config-matchers.js +35 -122
- package/dist/config/next-config.d.ts +15 -5
- package/dist/config/next-config.js +7 -1
- package/dist/config/request-context.d.ts +14 -0
- package/dist/config/request-context.js +26 -0
- package/dist/entries/app-browser-entry.d.ts +4 -10
- package/dist/entries/app-browser-entry.js +20 -4
- package/dist/entries/app-rsc-entry.js +30 -9
- package/dist/entries/app-rsc-manifest.js +36 -0
- package/dist/entries/pages-client-entry.js +0 -1
- package/dist/entries/pages-server-entry.js +13 -5
- package/dist/index.js +194 -101
- package/dist/init-cloudflare.d.ts +3 -1
- package/dist/init-cloudflare.js +59 -5
- package/dist/init.d.ts +6 -4
- package/dist/init.js +20 -22
- package/dist/plugins/extensionless-dynamic-import.js +23 -18
- package/dist/plugins/ignore-dynamic-requests.js +3 -1
- package/dist/plugins/import-meta-url.js +17 -6
- package/dist/plugins/og-assets.js +1 -1
- package/dist/plugins/require-context.js +20 -15
- package/dist/plugins/transform-cache.d.ts +19 -0
- package/dist/plugins/transform-cache.js +36 -0
- package/dist/routing/app-route-graph.d.ts +27 -11
- package/dist/routing/app-route-graph.js +158 -22
- package/dist/routing/app-router.js +12 -6
- package/dist/routing/file-matcher.d.ts +11 -22
- package/dist/routing/file-matcher.js +21 -33
- package/dist/routing/pages-router.js +23 -29
- package/dist/routing/route-matching.d.ts +14 -1
- package/dist/routing/route-matching.js +13 -1
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +7 -4
- package/dist/routing/route-trie.d.ts +5 -1
- package/dist/routing/route-trie.js +5 -2
- package/dist/server/api-handler.d.ts +2 -0
- package/dist/server/api-handler.js +3 -3
- package/dist/server/app-browser-entry.js +110 -37
- package/dist/server/app-browser-history-controller.d.ts +1 -1
- package/dist/server/app-browser-history-controller.js +3 -3
- package/dist/server/app-browser-navigation-controller.d.ts +23 -21
- package/dist/server/app-browser-navigation-controller.js +2 -2
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +3 -0
- package/dist/server/app-fallback-renderer.js +4 -2
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-page-boundary-render.d.ts +12 -1
- package/dist/server/app-page-boundary-render.js +84 -2
- package/dist/server/app-page-cache-finalizer.d.ts +1 -1
- package/dist/server/app-page-cache-finalizer.js +1 -1
- package/dist/server/app-page-cache.d.ts +2 -2
- package/dist/server/app-page-cache.js +16 -2
- package/dist/server/app-page-dispatch.d.ts +19 -2
- package/dist/server/app-page-dispatch.js +45 -23
- package/dist/server/app-page-element-builder.d.ts +9 -2
- package/dist/server/app-page-element-builder.js +144 -33
- package/dist/server/app-page-head.d.ts +56 -4
- package/dist/server/app-page-head.js +216 -85
- package/dist/server/app-page-http-access-fallback-metadata.d.ts +29 -0
- package/dist/server/app-page-http-access-fallback-metadata.js +114 -0
- package/dist/server/app-page-probe.d.ts +4 -0
- package/dist/server/app-page-probe.js +6 -2
- package/dist/server/app-page-render.d.ts +2 -2
- package/dist/server/app-page-render.js +1 -1
- package/dist/server/app-page-request.d.ts +15 -0
- package/dist/server/app-page-request.js +134 -14
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +16 -0
- package/dist/server/app-page-route-wiring.js +254 -52
- package/dist/server/app-pages-bridge.d.ts +1 -1
- package/dist/server/app-pages-bridge.js +2 -1
- package/dist/server/app-post-middleware-context.d.ts +1 -1
- package/dist/server/app-post-middleware-context.js +1 -1
- package/dist/server/app-route-handler-cache.js +4 -0
- package/dist/server/app-route-handler-dispatch.js +40 -20
- package/dist/server/app-route-handler-execution.d.ts +6 -1
- package/dist/server/app-route-handler-execution.js +42 -16
- package/dist/server/app-route-handler-policy.d.ts +2 -0
- package/dist/server/app-route-handler-policy.js +3 -3
- package/dist/server/app-route-handler-response.js +1 -1
- package/dist/server/app-route-module-loader.d.ts +9 -1
- package/dist/server/app-route-module-loader.js +4 -0
- package/dist/server/app-router-entry.js +6 -3
- package/dist/server/app-rsc-cache-busting.d.ts +1 -1
- package/dist/server/app-rsc-errors.d.ts +1 -1
- package/dist/server/app-rsc-errors.js +22 -2
- package/dist/server/app-rsc-handler.d.ts +10 -5
- package/dist/server/app-rsc-handler.js +73 -33
- package/dist/server/app-rsc-request-normalization.d.ts +3 -2
- package/dist/server/app-rsc-request-normalization.js +8 -2
- package/dist/server/app-rsc-response-finalizer.d.ts +2 -2
- package/dist/server/app-rsc-response-finalizer.js +5 -11
- package/dist/server/app-rsc-route-matching.d.ts +29 -0
- package/dist/server/app-rsc-route-matching.js +93 -11
- package/dist/server/app-segment-config.js +13 -3
- package/dist/server/app-server-action-execution.d.ts +3 -1
- package/dist/server/app-server-action-execution.js +9 -7
- package/dist/server/app-ssr-entry.js +6 -6
- package/dist/server/app-ssr-stream.d.ts +1 -7
- package/dist/server/app-ssr-stream.js +2 -10
- package/dist/server/cache-control.d.ts +2 -2
- package/dist/server/cache-control.js +2 -1
- package/dist/server/config-headers.d.ts +24 -0
- package/dist/server/config-headers.js +52 -0
- package/dist/server/dev-initial-server-error.d.ts +1 -1
- package/dist/server/dev-server.d.ts +3 -1
- package/dist/server/dev-server.js +170 -321
- package/dist/server/headers.d.ts +2 -2
- package/dist/server/headers.js +4 -3
- package/dist/server/isr-cache.d.ts +11 -34
- package/dist/server/isr-cache.js +33 -43
- package/dist/server/isr-decision.d.ts +2 -2
- package/dist/server/middleware-matcher-pattern.d.ts +22 -0
- package/dist/server/middleware-matcher-pattern.js +219 -0
- package/dist/server/middleware-matcher.d.ts +2 -7
- package/dist/server/middleware-matcher.js +13 -60
- package/dist/server/middleware-path-to-regexp.d.ts +14 -0
- package/dist/server/middleware-path-to-regexp.js +228 -0
- package/dist/server/middleware-runtime.js +4 -3
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +2 -1
- package/dist/server/pages-api-route.d.ts +2 -0
- package/dist/server/pages-api-route.js +4 -1
- package/dist/server/pages-data-route.d.ts +11 -1
- package/dist/server/pages-data-route.js +16 -1
- package/dist/server/pages-dev-hydration.js +0 -1
- package/dist/server/pages-get-initial-props.d.ts +16 -3
- package/dist/server/pages-get-initial-props.js +17 -10
- package/dist/server/pages-i18n.js +64 -16
- package/dist/server/pages-node-compat.d.ts +2 -0
- package/dist/server/pages-node-compat.js +7 -3
- package/dist/server/pages-page-data.d.ts +64 -10
- package/dist/server/pages-page-data.js +349 -80
- package/dist/server/pages-page-handler.d.ts +9 -3
- package/dist/server/pages-page-handler.js +131 -36
- package/dist/server/pages-page-response.d.ts +5 -3
- package/dist/server/pages-page-response.js +24 -27
- package/dist/server/pages-readiness.d.ts +3 -3
- package/dist/server/pages-request-pipeline.d.ts +8 -0
- package/dist/server/pages-request-pipeline.js +30 -12
- package/dist/server/pages-revalidate.d.ts +1 -1
- package/dist/server/pages-revalidate.js +54 -8
- package/dist/server/pages-router-entry.d.ts +1 -0
- package/dist/server/pages-router-entry.js +16 -4
- package/dist/server/prerender-route-params.js +2 -2
- package/dist/server/prod-server.d.ts +3 -5
- package/dist/server/prod-server.js +72 -35
- package/dist/server/request-pipeline.d.ts +15 -29
- package/dist/server/request-pipeline.js +24 -59
- package/dist/server/revalidation-host.d.ts +11 -0
- package/dist/server/revalidation-host.js +19 -0
- package/dist/server/rsc-stream-hints.js +122 -18
- package/dist/server/worker-revalidation-context.d.ts +13 -0
- package/dist/server/worker-revalidation-context.js +29 -0
- package/dist/shims/app.d.ts +1 -1
- package/dist/shims/cache-handler.d.ts +1 -1
- package/dist/shims/cache-handler.js +10 -7
- package/dist/shims/cache-request-state.d.ts +27 -1
- package/dist/shims/cache-request-state.js +60 -1
- package/dist/shims/cache-runtime.d.ts +18 -1
- package/dist/shims/cache-runtime.js +26 -8
- package/dist/shims/cache.d.ts +5 -5
- package/dist/shims/cache.js +27 -11
- package/dist/shims/document.d.ts +42 -83
- package/dist/shims/document.js +62 -53
- package/dist/shims/dynamic.d.ts +5 -25
- package/dist/shims/dynamic.js +29 -8
- package/dist/shims/error.d.ts +1 -1
- package/dist/shims/error.js +2 -2
- package/dist/shims/fetch-cache.js +13 -4
- package/dist/shims/font-google-base.d.ts +11 -4
- package/dist/shims/font-google-base.js +17 -8
- package/dist/shims/font-local.d.ts +10 -3
- package/dist/shims/font-local.js +41 -10
- package/dist/shims/head.d.ts +13 -2
- package/dist/shims/head.js +3 -3
- package/dist/shims/headers.d.ts +37 -43
- package/dist/shims/headers.js +183 -74
- package/dist/shims/image.d.ts +27 -37
- package/dist/shims/image.js +21 -14
- package/dist/shims/internal/cookie-serialize.d.ts +12 -9
- package/dist/shims/internal/cookie-serialize.js +10 -7
- package/dist/shims/internal/hybrid-client-route-owner.js +3 -1
- package/dist/shims/internal/interpolate-as.d.ts +15 -1
- package/dist/shims/internal/interpolate-as.js +27 -2
- package/dist/shims/internal/utils.d.ts +3 -3
- package/dist/shims/legacy-image.d.ts +3 -37
- package/dist/shims/legacy-image.js +6 -16
- package/dist/shims/link.d.ts +15 -22
- package/dist/shims/link.js +34 -13
- package/dist/shims/metadata.d.ts +7 -17
- package/dist/shims/metadata.js +74 -24
- package/dist/shims/navigation-context-state.d.ts +1 -1
- package/dist/shims/navigation.d.ts +7 -3
- package/dist/shims/navigation.js +25 -14
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/next-shims-augmentations.d.ts +1 -0
- package/dist/shims/next-shims-public.d.ts +1 -0
- package/dist/shims/public-shim-map.json.js +103 -0
- package/dist/shims/readonly-url-search-params.d.ts +3 -3
- package/dist/shims/readonly-url-search-params.js +3 -3
- package/dist/shims/request-context.d.ts +4 -1
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/router.d.ts +55 -21
- package/dist/shims/router.js +180 -44
- package/dist/shims/script.d.ts +6 -10
- package/dist/shims/script.js +7 -4
- package/dist/shims/server.d.ts +50 -26
- package/dist/shims/server.js +193 -55
- package/dist/shims/unified-request-context.d.ts +22 -3
- package/dist/shims/unified-request-context.js +84 -1
- package/dist/shims/url-safety.d.ts +1 -1
- package/dist/shims/url-safety.js +2 -2
- package/dist/typegen.d.ts +8 -2
- package/dist/typegen.js +76 -15
- package/dist/utils/cache-control-metadata.d.ts +2 -1
- package/dist/utils/cache-control-metadata.js +5 -1
- package/dist/utils/domain-locale.d.ts +2 -2
- package/dist/utils/external-url.d.ts +5 -0
- package/dist/utils/external-url.js +7 -0
- package/dist/utils/html-limited-bots.js +1 -1
- package/dist/utils/protocol-headers.d.ts +7 -1
- package/dist/utils/protocol-headers.js +7 -1
- package/dist/utils/regex-safety.d.ts +8 -0
- package/dist/utils/regex-safety.js +737 -0
- package/package.json +13 -2
package/dist/server/headers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
|
|
1
|
+
import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
|
|
2
2
|
|
|
3
3
|
//#region src/server/headers.d.ts
|
|
4
4
|
/**
|
|
@@ -107,4 +107,4 @@ declare const INTERNAL_HEADERS: string[];
|
|
|
107
107
|
/** Vinext-only internal headers stripped alongside Next.js protocol internals. */
|
|
108
108
|
declare const VINEXT_INTERNAL_HEADERS: string[];
|
|
109
109
|
//#endregion
|
|
110
|
-
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
110
|
+
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
package/dist/server/headers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
|
|
1
|
+
import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
|
|
2
2
|
//#region src/server/headers.ts
|
|
3
3
|
/**
|
|
4
4
|
* Internal HTTP header name constants used throughout vinext.
|
|
@@ -127,7 +127,8 @@ const INTERNAL_HEADERS = [
|
|
|
127
127
|
const VINEXT_INTERNAL_HEADERS = [
|
|
128
128
|
VINEXT_PRERENDER_ROUTE_PARAMS_HEADER,
|
|
129
129
|
VINEXT_PRERENDER_SPECULATIVE_HEADER,
|
|
130
|
-
VINEXT_PRERENDER_CACHE_LIFE_HEADER
|
|
130
|
+
VINEXT_PRERENDER_CACHE_LIFE_HEADER,
|
|
131
|
+
VINEXT_REVALIDATE_HOST_HEADER
|
|
131
132
|
];
|
|
132
133
|
//#endregion
|
|
133
|
-
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
134
|
+
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
@@ -1,38 +1,11 @@
|
|
|
1
1
|
import { OnRequestErrorContext } from "./instrumentation.js";
|
|
2
2
|
import { RenderObservation } from "./cache-proof.js";
|
|
3
|
+
import { AppRscRenderMode } from "./app-rsc-render-mode.js";
|
|
4
|
+
import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER } from "../utils/protocol-headers.js";
|
|
3
5
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
4
6
|
import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache-handler.js";
|
|
5
|
-
import { AppRscRenderMode } from "./app-rsc-render-mode.js";
|
|
6
7
|
|
|
7
8
|
//#region src/server/isr-cache.d.ts
|
|
8
|
-
/**
|
|
9
|
-
* Header set on the internal request that `res.revalidate()` issues to
|
|
10
|
-
* trigger on-demand ISR regeneration of a Pages Router route. Mirrors Next.js's
|
|
11
|
-
* `PRERENDER_REVALIDATE_HEADER` (`x-prerender-revalidate`) — see
|
|
12
|
-
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
13
|
-
*
|
|
14
|
-
* SECURITY: in Next.js this header is NOT a presence flag — it carries the
|
|
15
|
-
* secret `previewModeId`, and `checkIsOnDemandRevalidate`
|
|
16
|
-
* (`.nextjs-ref/packages/next/src/server/api-utils/index.ts`) only treats a
|
|
17
|
-
* request as on-demand revalidation when the value *equals* that secret. If we
|
|
18
|
-
* gated on presence alone, any external client could send
|
|
19
|
-
* `x-prerender-revalidate: <anything>` to force synchronous regeneration of any
|
|
20
|
-
* ISR page, bypassing the fresh/stale cache short-circuits — a
|
|
21
|
-
* cache-stampede/DoS vector. We therefore validate the value against
|
|
22
|
-
* {@link getRevalidateSecret} (a build-time secret shared across all Workers
|
|
23
|
-
* isolates) with a constant-time comparison, and only the matching value (sent
|
|
24
|
-
* by our own `res.revalidate()`) is honored.
|
|
25
|
-
*/
|
|
26
|
-
declare const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
|
|
27
|
-
/**
|
|
28
|
-
* Companion header to {@link PRERENDER_REVALIDATE_HEADER}. When set,
|
|
29
|
-
* `res.revalidate(path, { unstable_onlyGenerated: true })` only revalidates the
|
|
30
|
-
* path if it was already generated, and a 404 response counts as a successful
|
|
31
|
-
* no-op. Mirrors Next.js's `PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER`
|
|
32
|
-
* (`x-prerender-revalidate-if-generated`) — see
|
|
33
|
-
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
34
|
-
*/
|
|
35
|
-
declare const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
|
|
36
9
|
declare function getRevalidateSecret(): string;
|
|
37
10
|
declare function isRevalidateSecret(value: string | null | undefined): boolean;
|
|
38
11
|
/**
|
|
@@ -44,20 +17,22 @@ declare function isRevalidateSecret(value: string | null | undefined): boolean;
|
|
|
44
17
|
declare function isOnDemandRevalidateRequest(headerValue: string | string[] | null | undefined): boolean;
|
|
45
18
|
type ISRCacheEntry = {
|
|
46
19
|
value: CacheHandlerValue;
|
|
47
|
-
isStale: boolean;
|
|
20
|
+
isStale: boolean; /** The entry crossed its hard expire boundary and must not be served. */
|
|
21
|
+
isExpired?: boolean;
|
|
48
22
|
};
|
|
49
23
|
/**
|
|
50
24
|
* Get a cache entry with staleness information.
|
|
51
25
|
*
|
|
52
26
|
* Returns { value, isStale: false } for fresh entries,
|
|
53
|
-
* { value, isStale: true } for
|
|
54
|
-
*
|
|
27
|
+
* { value, isStale: true } for stale-but-usable entries,
|
|
28
|
+
* { value, isStale: true, isExpired: true } for entries that must be retained
|
|
29
|
+
* as regeneration input but not served, or null for cache misses.
|
|
55
30
|
*/
|
|
56
31
|
declare function isrGet(key: string): Promise<ISRCacheEntry | null>;
|
|
57
32
|
/**
|
|
58
33
|
* Store a value in the ISR cache with a revalidation period.
|
|
59
34
|
*/
|
|
60
|
-
declare function isrSet(key: string, data: IncrementalCacheValue, revalidateSeconds: number, tags?: string[], expireSeconds?: number): Promise<void>;
|
|
35
|
+
declare function isrSet(key: string, data: IncrementalCacheValue | null, revalidateSeconds: number | false, tags?: string[], expireSeconds?: number): Promise<void>;
|
|
61
36
|
declare function isrSetPrerenderedAppPage(key: string, data: CachedAppPageValue, metadata: {
|
|
62
37
|
expireSeconds?: number;
|
|
63
38
|
revalidateSeconds?: number;
|
|
@@ -70,6 +45,8 @@ declare function isrSetPrerenderedAppPage(key: string, data: CachedAppPageValue,
|
|
|
70
45
|
*/
|
|
71
46
|
tags?: string[];
|
|
72
47
|
}): Promise<void>;
|
|
48
|
+
/** Coalesce same-key synchronous on-demand revalidations. */
|
|
49
|
+
declare function coalesceOnDemandRevalidation<T>(key: string, renderFn: () => Promise<T>): Promise<T>;
|
|
73
50
|
/**
|
|
74
51
|
* Trigger a background regeneration for a cache key.
|
|
75
52
|
*
|
|
@@ -133,4 +110,4 @@ declare function setRevalidateDuration(key: string, seconds: number): void;
|
|
|
133
110
|
*/
|
|
134
111
|
declare function getRevalidateDuration(key: string): number | undefined;
|
|
135
112
|
//#endregion
|
|
136
|
-
export { ISRCacheEntry, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
|
113
|
+
export { ISRCacheEntry, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
package/dist/server/isr-cache.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
2
|
+
import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER } from "../utils/protocol-headers.js";
|
|
2
3
|
import { reportRequestError } from "./instrumentation.js";
|
|
3
4
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
4
5
|
import { fnv1a64 } from "../utils/hash.js";
|
|
@@ -7,34 +8,6 @@ import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./
|
|
|
7
8
|
import { normalizeAppPageInterceptionProofPathname } from "./app-page-render-identity.js";
|
|
8
9
|
//#region src/server/isr-cache.ts
|
|
9
10
|
/**
|
|
10
|
-
* Header set on the internal request that `res.revalidate()` issues to
|
|
11
|
-
* trigger on-demand ISR regeneration of a Pages Router route. Mirrors Next.js's
|
|
12
|
-
* `PRERENDER_REVALIDATE_HEADER` (`x-prerender-revalidate`) — see
|
|
13
|
-
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
14
|
-
*
|
|
15
|
-
* SECURITY: in Next.js this header is NOT a presence flag — it carries the
|
|
16
|
-
* secret `previewModeId`, and `checkIsOnDemandRevalidate`
|
|
17
|
-
* (`.nextjs-ref/packages/next/src/server/api-utils/index.ts`) only treats a
|
|
18
|
-
* request as on-demand revalidation when the value *equals* that secret. If we
|
|
19
|
-
* gated on presence alone, any external client could send
|
|
20
|
-
* `x-prerender-revalidate: <anything>` to force synchronous regeneration of any
|
|
21
|
-
* ISR page, bypassing the fresh/stale cache short-circuits — a
|
|
22
|
-
* cache-stampede/DoS vector. We therefore validate the value against
|
|
23
|
-
* {@link getRevalidateSecret} (a build-time secret shared across all Workers
|
|
24
|
-
* isolates) with a constant-time comparison, and only the matching value (sent
|
|
25
|
-
* by our own `res.revalidate()`) is honored.
|
|
26
|
-
*/
|
|
27
|
-
const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
|
|
28
|
-
/**
|
|
29
|
-
* Companion header to {@link PRERENDER_REVALIDATE_HEADER}. When set,
|
|
30
|
-
* `res.revalidate(path, { unstable_onlyGenerated: true })` only revalidates the
|
|
31
|
-
* path if it was already generated, and a 404 response counts as a successful
|
|
32
|
-
* no-op. Mirrors Next.js's `PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER`
|
|
33
|
-
* (`x-prerender-revalidate-if-generated`) — see
|
|
34
|
-
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
35
|
-
*/
|
|
36
|
-
const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
|
|
37
|
-
/**
|
|
38
11
|
* Build-time secret that authenticates on-demand revalidation requests, the
|
|
39
12
|
* vinext analog of Next.js's prerender-manifest `previewModeId`.
|
|
40
13
|
*
|
|
@@ -54,19 +27,22 @@ const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-ge
|
|
|
54
27
|
*
|
|
55
28
|
* When the build-time define is absent — dev mode, and any path that doesn't
|
|
56
29
|
* run through `vinext build` — we fall back to a lazily-generated random secret.
|
|
57
|
-
* Those paths are single-process,
|
|
58
|
-
* and
|
|
30
|
+
* Those paths are single-process, but Vite can evaluate this module separately
|
|
31
|
+
* in its RSC and SSR module graphs. Store the fallback on `globalThis` under a
|
|
32
|
+
* registry symbol so every module copy in the process reads the same value.
|
|
59
33
|
*/
|
|
60
|
-
|
|
34
|
+
const _DEV_REVALIDATE_SECRET_KEY = Symbol.for("vinext.isrCache.devRevalidateSecret");
|
|
61
35
|
function getRevalidateSecret() {
|
|
62
36
|
const baked = process.env.__VINEXT_REVALIDATE_SECRET;
|
|
63
37
|
if (baked) return baked;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
38
|
+
const globals = globalThis;
|
|
39
|
+
const existing = globals[_DEV_REVALIDATE_SECRET_KEY];
|
|
40
|
+
if (typeof existing === "string") return existing;
|
|
41
|
+
const bytes = /* @__PURE__ */ new Uint8Array(32);
|
|
42
|
+
crypto.getRandomValues(bytes);
|
|
43
|
+
const secret = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
44
|
+
globals[_DEV_REVALIDATE_SECRET_KEY] = secret;
|
|
45
|
+
return secret;
|
|
70
46
|
}
|
|
71
47
|
/**
|
|
72
48
|
* Constant-time string equality. Avoids leaking secret length / prefix via
|
|
@@ -98,16 +74,18 @@ function isOnDemandRevalidateRequest(headerValue) {
|
|
|
98
74
|
* Get a cache entry with staleness information.
|
|
99
75
|
*
|
|
100
76
|
* Returns { value, isStale: false } for fresh entries,
|
|
101
|
-
* { value, isStale: true } for
|
|
102
|
-
*
|
|
77
|
+
* { value, isStale: true } for stale-but-usable entries,
|
|
78
|
+
* { value, isStale: true, isExpired: true } for entries that must be retained
|
|
79
|
+
* as regeneration input but not served, or null for cache misses.
|
|
103
80
|
*/
|
|
104
81
|
async function isrGet(key) {
|
|
105
82
|
const result = await getCdnCacheAdapter().get(key);
|
|
106
|
-
if (!result
|
|
107
|
-
|
|
83
|
+
if (!result) return null;
|
|
84
|
+
const isExpired = result.cacheState === "expired";
|
|
108
85
|
return {
|
|
109
86
|
value: result,
|
|
110
|
-
isStale: result.cacheState === "stale"
|
|
87
|
+
isStale: isExpired || result.cacheState === "stale",
|
|
88
|
+
...isExpired ? { isExpired: true } : {}
|
|
111
89
|
};
|
|
112
90
|
}
|
|
113
91
|
/**
|
|
@@ -142,6 +120,18 @@ async function isrSetPrerenderedAppPage(key, data, metadata) {
|
|
|
142
120
|
const _PENDING_REGEN_KEY = Symbol.for("vinext.isrCache.pendingRegenerations");
|
|
143
121
|
const _g = globalThis;
|
|
144
122
|
const pendingRegenerations = _g[_PENDING_REGEN_KEY] ??= /* @__PURE__ */ new Map();
|
|
123
|
+
const _PENDING_ON_DEMAND_REGEN_KEY = Symbol.for("vinext.isrCache.pendingOnDemandRegenerations");
|
|
124
|
+
const pendingOnDemandRegenerations = _g[_PENDING_ON_DEMAND_REGEN_KEY] ??= /* @__PURE__ */ new Map();
|
|
125
|
+
/** Coalesce same-key synchronous on-demand revalidations. */
|
|
126
|
+
function coalesceOnDemandRevalidation(key, renderFn) {
|
|
127
|
+
const pending = pendingOnDemandRegenerations.get(key);
|
|
128
|
+
if (pending) return pending;
|
|
129
|
+
const promise = Promise.resolve().then(renderFn).finally(() => {
|
|
130
|
+
if (pendingOnDemandRegenerations.get(key) === promise) pendingOnDemandRegenerations.delete(key);
|
|
131
|
+
});
|
|
132
|
+
pendingOnDemandRegenerations.set(key, promise);
|
|
133
|
+
return promise;
|
|
134
|
+
}
|
|
145
135
|
/**
|
|
146
136
|
* Trigger a background regeneration for a cache key.
|
|
147
137
|
*
|
|
@@ -283,4 +273,4 @@ function getRevalidateDuration(key) {
|
|
|
283
273
|
return revalidateDurations.get(key);
|
|
284
274
|
}
|
|
285
275
|
//#endregion
|
|
286
|
-
export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
|
276
|
+
export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
|
@@ -33,7 +33,7 @@ type DecideIsrOptions = {
|
|
|
33
33
|
* For `"dev"` call sites this is the only source of the revalidate value —
|
|
34
34
|
* dev never has metadata attached to a cache entry.
|
|
35
35
|
*/
|
|
36
|
-
revalidateSeconds: number;
|
|
36
|
+
revalidateSeconds: number | false;
|
|
37
37
|
/**
|
|
38
38
|
* The expire ceiling (seconds from epoch) read from the route config.
|
|
39
39
|
* Absent when the route pre-dates expire metadata support.
|
|
@@ -63,7 +63,7 @@ declare function decideIsr(options: DecideIsrOptions): IsrDecision;
|
|
|
63
63
|
* therefore never has `cacheControlMeta`. `expireSeconds` here is the route
|
|
64
64
|
* config ceiling passed directly from the caller (not a per-entry fallback).
|
|
65
65
|
*/
|
|
66
|
-
declare function buildMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
|
|
66
|
+
declare function buildMissIsrCacheControl(revalidateSeconds: number | false, expireSeconds?: number): string;
|
|
67
67
|
/**
|
|
68
68
|
* Build the `Cache-Control` string for a fresh (MISS) app-route response.
|
|
69
69
|
*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HasCondition } from "../config/next-config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/server/middleware-matcher-pattern.d.ts
|
|
4
|
+
type CompiledMiddlewareMatcherPattern = {
|
|
5
|
+
regexp: RegExp;
|
|
6
|
+
error?: never;
|
|
7
|
+
} | {
|
|
8
|
+
regexp?: never;
|
|
9
|
+
error: string;
|
|
10
|
+
kind: "invalid" | "unsafe";
|
|
11
|
+
};
|
|
12
|
+
type MiddlewareMatcherObject = {
|
|
13
|
+
source: string;
|
|
14
|
+
locale?: false;
|
|
15
|
+
has?: HasCondition[];
|
|
16
|
+
missing?: HasCondition[];
|
|
17
|
+
};
|
|
18
|
+
declare function isValidMiddlewareMatcherObjectConfig(value: unknown): value is MiddlewareMatcherObject;
|
|
19
|
+
declare function compileMiddlewareMatcherPattern(source: string): CompiledMiddlewareMatcherPattern;
|
|
20
|
+
declare function validateMiddlewareMatcherPatterns(value: unknown): void;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { CompiledMiddlewareMatcherPattern, MiddlewareMatcherObject, compileMiddlewareMatcherPattern, isValidMiddlewareMatcherObjectConfig, validateMiddlewareMatcherPatterns };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { analyzeRegexSafety, regexAtomsMayOverlap } from "../utils/regex-safety.js";
|
|
2
|
+
import { middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath } from "./middleware-path-to-regexp.js";
|
|
3
|
+
//#region src/server/middleware-matcher-pattern.ts
|
|
4
|
+
const MATCHER_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
5
|
+
"source",
|
|
6
|
+
"locale",
|
|
7
|
+
"has",
|
|
8
|
+
"missing"
|
|
9
|
+
]);
|
|
10
|
+
const CONDITION_TYPES_WITH_KEY = /* @__PURE__ */ new Set([
|
|
11
|
+
"header",
|
|
12
|
+
"query",
|
|
13
|
+
"cookie"
|
|
14
|
+
]);
|
|
15
|
+
function invalidConditionReason(value) {
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return "has and missing entries must be objects";
|
|
17
|
+
const condition = value;
|
|
18
|
+
const type = condition.type;
|
|
19
|
+
if (typeof type === "string" && CONDITION_TYPES_WITH_KEY.has(type)) {
|
|
20
|
+
for (const key of Object.keys(condition)) if (key !== "type" && key !== "key" && key !== "value") return `condition contains unsupported field "${key}"`;
|
|
21
|
+
if (typeof condition.key !== "string") return `condition type "${type}" requires a string key`;
|
|
22
|
+
if (condition.value !== void 0 && typeof condition.value !== "string") return `condition type "${type}" requires value to be a string`;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (type === "host") {
|
|
26
|
+
for (const key of Object.keys(condition)) if (key !== "type" && key !== "value") return `host condition contains unsupported field "${key}"`;
|
|
27
|
+
return typeof condition.value === "string" ? null : "host condition requires a string value";
|
|
28
|
+
}
|
|
29
|
+
return "condition type must be header, query, cookie, or host";
|
|
30
|
+
}
|
|
31
|
+
function invalidConditionsReason(value, field) {
|
|
32
|
+
if (value === void 0) return null;
|
|
33
|
+
if (!Array.isArray(value)) return `${field} must be an array`;
|
|
34
|
+
for (const condition of value) {
|
|
35
|
+
const reason = invalidConditionReason(condition);
|
|
36
|
+
if (reason) return `${field} ${reason}`;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function matcherObjectSource(value) {
|
|
41
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { error: "matcher entries must be strings or objects" };
|
|
42
|
+
const matcher = value;
|
|
43
|
+
for (const key of Object.keys(matcher)) if (!MATCHER_OBJECT_KEYS.has(key)) return { error: `matcher object contains unsupported field "${key}"` };
|
|
44
|
+
if (typeof matcher.source !== "string") return { error: "matcher object requires a string source" };
|
|
45
|
+
if (matcher.locale !== void 0 && matcher.locale !== false) return { error: "matcher object locale must be false when provided" };
|
|
46
|
+
const invalidHas = invalidConditionsReason(matcher.has, "has");
|
|
47
|
+
if (invalidHas) return { error: invalidHas };
|
|
48
|
+
const invalidMissing = invalidConditionsReason(matcher.missing, "missing");
|
|
49
|
+
if (invalidMissing) return { error: invalidMissing };
|
|
50
|
+
return { source: matcher.source };
|
|
51
|
+
}
|
|
52
|
+
function isValidMiddlewareMatcherObjectConfig(value) {
|
|
53
|
+
return matcherObjectSource(value).error === void 0;
|
|
54
|
+
}
|
|
55
|
+
function patternMatches(pattern, value) {
|
|
56
|
+
try {
|
|
57
|
+
return new RegExp(`^(?:${pattern})$`).test(value);
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function atomsOverlap(left, right) {
|
|
63
|
+
return regexAtomsMayOverlap(left, right, true);
|
|
64
|
+
}
|
|
65
|
+
function groupCanMatchEmpty(group) {
|
|
66
|
+
if (/^\?(?:[=!]|<[=!])/.test(group)) return true;
|
|
67
|
+
return patternMatches(group.startsWith("?:") ? group.slice(2) : group, "");
|
|
68
|
+
}
|
|
69
|
+
function hasOverlappingSequentialRepetition(pattern) {
|
|
70
|
+
const repeatedAtDepth = [[]];
|
|
71
|
+
const groupStarts = [];
|
|
72
|
+
let depth = 0;
|
|
73
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
74
|
+
const character = pattern[index];
|
|
75
|
+
if (character === "(") {
|
|
76
|
+
groupStarts.push(index);
|
|
77
|
+
depth++;
|
|
78
|
+
repeatedAtDepth[depth] = [];
|
|
79
|
+
if (pattern[index + 1] === "?") {
|
|
80
|
+
index++;
|
|
81
|
+
if (pattern[index + 1] === "<" && /[=!]/.test(pattern[index + 2] ?? "")) index += 2;
|
|
82
|
+
else if (/[=:!]/.test(pattern[index + 1] ?? "")) index++;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (character === ")") {
|
|
87
|
+
const groupStart = groupStarts.pop();
|
|
88
|
+
repeatedAtDepth[depth] = [];
|
|
89
|
+
depth = Math.max(0, depth - 1);
|
|
90
|
+
if (groupStart !== void 0) {
|
|
91
|
+
const modifier = pattern[index + 1];
|
|
92
|
+
if (!(modifier === "*" || modifier === "?" || modifier === "{" && /^\{0(?:,\d*)?\}/.test(pattern.slice(index + 1))) && !groupCanMatchEmpty(pattern.slice(groupStart + 1, index))) repeatedAtDepth[depth] = [];
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (character === "|") {
|
|
97
|
+
repeatedAtDepth[depth] = [];
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (character === "^" || character === "$") continue;
|
|
101
|
+
if (character === "*" || character === "+" || character === "?" || character === "{") continue;
|
|
102
|
+
let atom = character;
|
|
103
|
+
if (character === "\\") {
|
|
104
|
+
if (index + 1 >= pattern.length) return true;
|
|
105
|
+
atom += pattern[++index];
|
|
106
|
+
} else if (character === "[") {
|
|
107
|
+
let classEnd = index + 1;
|
|
108
|
+
if (pattern[classEnd] === "^") classEnd++;
|
|
109
|
+
while (classEnd < pattern.length && pattern[classEnd] !== "]") {
|
|
110
|
+
if (pattern[classEnd] === "\\") classEnd++;
|
|
111
|
+
classEnd++;
|
|
112
|
+
}
|
|
113
|
+
if (classEnd >= pattern.length) return true;
|
|
114
|
+
atom = pattern.slice(index, classEnd + 1);
|
|
115
|
+
index = classEnd;
|
|
116
|
+
} else if (character === ".") atom = ".";
|
|
117
|
+
const quantifierStart = index + 1;
|
|
118
|
+
const quantifier = pattern[quantifierStart];
|
|
119
|
+
let unbounded = quantifier === "*" || quantifier === "+";
|
|
120
|
+
let canBeEmpty = quantifier === "*";
|
|
121
|
+
let quantifierEnd = quantifierStart;
|
|
122
|
+
if (quantifier === "{") {
|
|
123
|
+
const match = /^\{(\d+),\}/.exec(pattern.slice(quantifierStart));
|
|
124
|
+
if (match) {
|
|
125
|
+
unbounded = true;
|
|
126
|
+
canBeEmpty = Number(match[1]) === 0;
|
|
127
|
+
quantifierEnd += match[0].length - 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!unbounded) {
|
|
131
|
+
if (quantifier !== "?") repeatedAtDepth[depth] = [];
|
|
132
|
+
if (quantifier === "?") index = quantifierStart;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (repeatedAtDepth[depth].some((previous) => atomsOverlap(previous, atom))) return true;
|
|
136
|
+
repeatedAtDepth[depth] = canBeEmpty ? [...repeatedAtDepth[depth], atom] : [atom];
|
|
137
|
+
index = quantifierEnd;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
function unsafeTokenReason(token) {
|
|
142
|
+
const regexSafetyIssue = analyzeRegexSafety(token.pattern, { ignoreCase: true });
|
|
143
|
+
if (regexSafetyIssue) {
|
|
144
|
+
if (regexSafetyIssue === "analysis budget exceeded") return `parameter "${token.name}" exceeds the regex analysis budget`;
|
|
145
|
+
return `parameter "${token.name}" contains ${regexSafetyIssue}`;
|
|
146
|
+
}
|
|
147
|
+
if (hasOverlappingSequentialRepetition(token.pattern)) return `parameter "${token.name}" contains overlapping sequential repetition`;
|
|
148
|
+
if (token.modifier !== "*" && token.modifier !== "+") return null;
|
|
149
|
+
if (patternMatches(token.pattern, "") || patternMatches(token.pattern, "/")) return `repeated parameter "${token.name}" may match an empty value or path delimiter`;
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function validateTokens(tokens) {
|
|
153
|
+
for (const token of tokens) {
|
|
154
|
+
if (typeof token === "string") continue;
|
|
155
|
+
const reason = unsafeTokenReason(token);
|
|
156
|
+
if (reason) return reason;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function compileMiddlewareMatcherPattern(source) {
|
|
161
|
+
if (!source.startsWith("/")) return {
|
|
162
|
+
kind: "invalid",
|
|
163
|
+
error: "source must start with /"
|
|
164
|
+
};
|
|
165
|
+
if (source.length > 4096) return {
|
|
166
|
+
kind: "invalid",
|
|
167
|
+
error: "source exceeds max built length of 4096"
|
|
168
|
+
};
|
|
169
|
+
let tokens;
|
|
170
|
+
try {
|
|
171
|
+
tokens = parseMiddlewarePath(source);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return {
|
|
174
|
+
kind: "invalid",
|
|
175
|
+
error: error instanceof Error ? error.message : "matcher could not be parsed"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const unsafeReason = validateTokens(tokens);
|
|
179
|
+
if (unsafeReason) return {
|
|
180
|
+
kind: "unsafe",
|
|
181
|
+
error: unsafeReason
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
return { regexp: middlewarePathTokensToRegExp(tokens) };
|
|
185
|
+
} catch {
|
|
186
|
+
const normalizedTokens = normalizeMiddlewarePathTokens(tokens);
|
|
187
|
+
const normalizedUnsafeReason = validateTokens(normalizedTokens);
|
|
188
|
+
if (normalizedUnsafeReason) return {
|
|
189
|
+
kind: "unsafe",
|
|
190
|
+
error: normalizedUnsafeReason
|
|
191
|
+
};
|
|
192
|
+
try {
|
|
193
|
+
return { regexp: middlewarePathTokensToRegExp(normalizedTokens) };
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return {
|
|
196
|
+
kind: "invalid",
|
|
197
|
+
error: error instanceof Error ? error.message : "matcher could not be compiled"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function validateMiddlewareMatcherPatterns(value) {
|
|
203
|
+
const sources = [];
|
|
204
|
+
if (typeof value === "string") sources.push(value);
|
|
205
|
+
else if (Array.isArray(value)) for (const matcher of value) if (typeof matcher === "string") sources.push(matcher);
|
|
206
|
+
else {
|
|
207
|
+
const result = matcherObjectSource(matcher);
|
|
208
|
+
if (result.error) throw new Error(`Invalid middleware matcher config: ${result.error}.`);
|
|
209
|
+
sources.push(result.source);
|
|
210
|
+
}
|
|
211
|
+
else throw new Error("Invalid middleware matcher config: matcher must be a string or an array of strings or objects.");
|
|
212
|
+
for (const source of sources) {
|
|
213
|
+
const result = compileMiddlewareMatcherPattern(source);
|
|
214
|
+
if (result.regexp) continue;
|
|
215
|
+
throw new Error(`Invalid middleware matcher "${source}": ${result.error}.`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
export { compileMiddlewareMatcherPattern, isValidMiddlewareMatcherObjectConfig, validateMiddlewareMatcherPatterns };
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NextI18nConfig } from "../config/next-config.js";
|
|
2
|
+
import { MiddlewareMatcherObject } from "./middleware-matcher-pattern.js";
|
|
2
3
|
|
|
3
4
|
//#region src/server/middleware-matcher.d.ts
|
|
4
|
-
type MiddlewareMatcherObject = {
|
|
5
|
-
source: string;
|
|
6
|
-
locale?: false;
|
|
7
|
-
has?: HasCondition[];
|
|
8
|
-
missing?: HasCondition[];
|
|
9
|
-
};
|
|
10
5
|
type MatcherConfig = string | Array<string | MiddlewareMatcherObject>;
|
|
11
6
|
declare function matchesMiddleware(pathname: string, matcher: MatcherConfig | undefined, request?: Request, i18nConfig?: NextI18nConfig | null): boolean;
|
|
12
7
|
declare function matchPattern(pathname: string, pattern: string): boolean;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { removeTrailingSlash } from "../utils/base-path.js";
|
|
2
|
-
import {
|
|
2
|
+
import { requestContextFromRequest } from "../config/request-context.js";
|
|
3
|
+
import { checkHasConditions } from "../config/config-matchers.js";
|
|
4
|
+
import { compileMiddlewareMatcherPattern, isValidMiddlewareMatcherObjectConfig } from "./middleware-matcher-pattern.js";
|
|
3
5
|
//#region src/server/middleware-matcher.ts
|
|
4
6
|
const EMPTY_MIDDLEWARE_REQUEST_CONTEXT = {
|
|
5
7
|
headers: new Headers(),
|
|
@@ -12,30 +14,20 @@ const _mwPatternCache = /* @__PURE__ */ new Map();
|
|
|
12
14
|
function matchesMiddleware(pathname, matcher, request, i18nConfig) {
|
|
13
15
|
if (!matcher) return true;
|
|
14
16
|
if (typeof matcher === "string") return matchMatcherPattern(pathname, matcher, i18nConfig);
|
|
15
|
-
if (!Array.isArray(matcher)) return
|
|
17
|
+
if (!Array.isArray(matcher)) return true;
|
|
16
18
|
const requestContext = request ? requestContextFromRequest(request) : EMPTY_MIDDLEWARE_REQUEST_CONTEXT;
|
|
17
19
|
for (const m of matcher) {
|
|
18
20
|
if (typeof m === "string") {
|
|
19
21
|
if (matchMatcherPattern(pathname, m, i18nConfig)) return true;
|
|
20
22
|
continue;
|
|
21
23
|
}
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
24
|
+
if (!isValidMiddlewareMatcherObjectConfig(m)) return true;
|
|
25
|
+
if (!matchObjectMatcher(pathname, m, i18nConfig)) continue;
|
|
26
|
+
if (!checkHasConditions(m.has, m.missing, requestContext)) continue;
|
|
27
|
+
return true;
|
|
27
28
|
}
|
|
28
29
|
return false;
|
|
29
30
|
}
|
|
30
|
-
function isValidMiddlewareMatcherObject(value) {
|
|
31
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
32
|
-
if (!("source" in value) || typeof value.source !== "string") return false;
|
|
33
|
-
for (const key of Object.keys(value)) if (key !== "source" && key !== "locale" && key !== "has" && key !== "missing") return false;
|
|
34
|
-
if ("locale" in value && value.locale !== void 0 && value.locale !== false) return false;
|
|
35
|
-
if ("has" in value && value.has !== void 0 && !Array.isArray(value.has)) return false;
|
|
36
|
-
if ("missing" in value && value.missing !== void 0 && !Array.isArray(value.missing)) return false;
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
31
|
function matchMatcherPattern(pathname, pattern, i18nConfig) {
|
|
40
32
|
if (!i18nConfig) return matchPattern(pathname, pattern);
|
|
41
33
|
return matchPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
|
|
@@ -58,54 +50,15 @@ function matchPattern(pathname, pattern) {
|
|
|
58
50
|
_mwPatternCache.set(normalizedPattern, cached);
|
|
59
51
|
}
|
|
60
52
|
if (cached === UNSAFE_MATCHER_PATTERN) return true;
|
|
61
|
-
if (cached === null) return removeTrailingSlash(pathname) === normalizedPattern;
|
|
62
53
|
if (cached.test(pathname)) return true;
|
|
63
54
|
return pathname.endsWith("/") && cached.test(removeTrailingSlash(pathname));
|
|
64
55
|
}
|
|
65
|
-
function extractConstraint(str, re) {
|
|
66
|
-
if (str[re.lastIndex] !== "(") return null;
|
|
67
|
-
const start = re.lastIndex + 1;
|
|
68
|
-
let depth = 1;
|
|
69
|
-
let i = start;
|
|
70
|
-
while (i < str.length && depth > 0) {
|
|
71
|
-
if (str[i] === "(") depth++;
|
|
72
|
-
else if (str[i] === ")") depth--;
|
|
73
|
-
i++;
|
|
74
|
-
}
|
|
75
|
-
if (depth !== 0) return null;
|
|
76
|
-
re.lastIndex = i;
|
|
77
|
-
return str.slice(start, i - 1);
|
|
78
|
-
}
|
|
79
56
|
function compileMatcherPattern(pattern) {
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
while ((tok = tokenRe.exec(pattern)) !== null) if (tok[1] !== void 0) {
|
|
86
|
-
const constraint = hasConstraints ? extractConstraint(pattern, tokenRe) : null;
|
|
87
|
-
regexStr += constraint !== null ? `(?:/(${constraint}))?` : "(?:/.*)?";
|
|
88
|
-
} else if (tok[2] !== void 0) {
|
|
89
|
-
const constraint = hasConstraints ? extractConstraint(pattern, tokenRe) : null;
|
|
90
|
-
regexStr += constraint !== null ? `(?:/(${constraint}))` : "(?:/.+)";
|
|
91
|
-
} else if (tok[3] !== void 0) {
|
|
92
|
-
const constraint = hasConstraints ? extractConstraint(pattern, tokenRe) : null;
|
|
93
|
-
const isOptional = pattern[tokenRe.lastIndex] === "?";
|
|
94
|
-
if (isOptional) tokenRe.lastIndex += 1;
|
|
95
|
-
const group = constraint !== null ? `(${constraint})` : "([^/]+)";
|
|
96
|
-
if (isOptional && regexStr.endsWith("/")) regexStr = regexStr.slice(0, -1) + `(?:/${group})?`;
|
|
97
|
-
else if (isOptional) regexStr += `${group}?`;
|
|
98
|
-
else regexStr += group;
|
|
99
|
-
} else if (tok[0] === ".") regexStr += "\\.";
|
|
100
|
-
else regexStr += tok[0];
|
|
101
|
-
return compileMatcherRegExp("^" + regexStr + "$", pattern);
|
|
102
|
-
}
|
|
103
|
-
function compileMatcherRegExp(regexPattern, sourcePattern) {
|
|
104
|
-
if (!isSafeRegex(regexPattern)) {
|
|
105
|
-
console.warn(`[vinext] Rejecting potentially unsafe middleware matcher (ReDoS risk): ${sourcePattern}\n Middleware will run for all paths to avoid bypassing request guards.\n Simplify the matcher to avoid nested repetition.`);
|
|
106
|
-
return UNSAFE_MATCHER_PATTERN;
|
|
107
|
-
}
|
|
108
|
-
return safeRegExp(regexPattern);
|
|
57
|
+
const result = compileMiddlewareMatcherPattern(pattern);
|
|
58
|
+
if (result.regexp) return result.regexp;
|
|
59
|
+
const problem = result.kind === "unsafe" ? "potentially unsafe" : "invalid";
|
|
60
|
+
console.warn(`[vinext] Rejecting ${problem} middleware matcher: ${pattern}\n ${result.error}.\n Middleware will run for all paths to avoid bypassing request guards.`);
|
|
61
|
+
return UNSAFE_MATCHER_PATTERN;
|
|
109
62
|
}
|
|
110
63
|
//#endregion
|
|
111
64
|
export { matchPattern, matchesMiddleware };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/server/middleware-path-to-regexp.d.ts
|
|
2
|
+
type MiddlewarePathToken = string | MiddlewarePathKey;
|
|
3
|
+
type MiddlewarePathKey = {
|
|
4
|
+
name: string | number;
|
|
5
|
+
prefix: string;
|
|
6
|
+
suffix: string;
|
|
7
|
+
pattern: string;
|
|
8
|
+
modifier: string;
|
|
9
|
+
};
|
|
10
|
+
declare function parseMiddlewarePath(value: string): MiddlewarePathToken[];
|
|
11
|
+
declare function normalizeMiddlewarePathTokens(tokens: MiddlewarePathToken[]): MiddlewarePathToken[];
|
|
12
|
+
declare function middlewarePathTokensToRegExp(tokens: MiddlewarePathToken[]): RegExp;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { MiddlewarePathKey, MiddlewarePathToken, middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath };
|